fix(auth): SDK device/login-events response wrappers + correct API paths
- Swift + Kotlin SDKs: listDevices() now unwraps { devices: [...] }
- Swift + Kotlin SDKs: getLoginHistory() now unwraps { events: [...] }
- Swift + Kotlin SDKs: revokeDevice() uses fingerprint param (not doc ID)
- Swift + Kotlin SDKs: revokeAllDevices() uses POST /revoke-all (not DELETE)
- Swift + Kotlin SDKs: getLoginHistory() path /login-events (not /login-events/me)
- Swift + Kotlin SDKs: Device model updated to match backend response fields
- All 53 auth tests passing
This commit is contained in:
parent
b0e1a54481
commit
b8f22be677
@ -85,12 +85,28 @@ class BLAuthClient(
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Device(
|
data class Device(
|
||||||
val id: String,
|
val fingerprint: String,
|
||||||
val name: String,
|
|
||||||
val platform: String,
|
|
||||||
val trustLevel: String,
|
val trustLevel: String,
|
||||||
|
val deviceInfo: DeviceInfo? = null,
|
||||||
|
val lastIp: String? = null,
|
||||||
|
val lastLocation: String? = null,
|
||||||
val trustExpiresAt: String? = null,
|
val trustExpiresAt: String? = null,
|
||||||
val lastLoginAt: String,
|
val createdAt: String,
|
||||||
|
val lastSeenAt: String,
|
||||||
|
val isTrusted: Boolean,
|
||||||
|
) {
|
||||||
|
/** Convenience: display name from device info. */
|
||||||
|
val name: String get() = deviceInfo?.model ?: deviceInfo?.platform ?: fingerprint.take(8)
|
||||||
|
/** Convenience: platform string. */
|
||||||
|
val platform: String get() = deviceInfo?.platform ?: "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DeviceInfo(
|
||||||
|
val userAgent: String? = null,
|
||||||
|
val platform: String? = null,
|
||||||
|
val model: String? = null,
|
||||||
|
val os: String? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -518,10 +534,13 @@ class BLAuthClient(
|
|||||||
|
|
||||||
// ── Devices (SmartAuth v2) ───────────────────────────────
|
// ── Devices (SmartAuth v2) ───────────────────────────────
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data class DevicesResponse(val devices: List<Device>)
|
||||||
|
|
||||||
/** List devices for current user. */
|
/** List devices for current user. */
|
||||||
suspend fun listDevices(): List<Device> {
|
suspend fun listDevices(): List<Device> {
|
||||||
val response = client.request("GET", "/api/auth/devices")
|
val response = client.request("GET", "/api/auth/devices")
|
||||||
return client.json.decodeFromString<List<Device>>(response)
|
return client.json.decodeFromString<DevicesResponse>(response).devices
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Trust the current device (promotes to trusted, skips MFA for 90 days). */
|
/** Trust the current device (promotes to trusted, skips MFA for 90 days). */
|
||||||
@ -529,14 +548,14 @@ class BLAuthClient(
|
|||||||
client.request("POST", "/api/auth/devices/trust")
|
client.request("POST", "/api/auth/devices/trust")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Revoke trust on a specific device. */
|
/** Revoke trust on a specific device by fingerprint. */
|
||||||
suspend fun revokeDevice(deviceId: String) {
|
suspend fun revokeDevice(fingerprint: String) {
|
||||||
client.request("DELETE", "/api/auth/devices/$deviceId")
|
client.request("DELETE", "/api/auth/devices/$fingerprint")
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Revoke all devices (requires step-up). */
|
/** Revoke all device trust. */
|
||||||
suspend fun revokeAllDevices() {
|
suspend fun revokeAllDevices() {
|
||||||
client.request("DELETE", "/api/auth/devices")
|
client.request("POST", "/api/auth/devices/revoke-all")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Step-Up Auth (SmartAuth v2) ──────────────────────────
|
// ── Step-Up Auth (SmartAuth v2) ──────────────────────────
|
||||||
@ -550,10 +569,13 @@ class BLAuthClient(
|
|||||||
|
|
||||||
// ── Login History (SmartAuth v2) ─────────────────────────
|
// ── Login History (SmartAuth v2) ─────────────────────────
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
private data class EventsResponse(val events: List<LoginEvent>)
|
||||||
|
|
||||||
/** Get login events for the current user. */
|
/** Get login events for the current user. */
|
||||||
suspend fun getLoginHistory(limit: Int = 20): List<LoginEvent> {
|
suspend fun getLoginHistory(limit: Int = 20): List<LoginEvent> {
|
||||||
val response = client.request("GET", "/api/auth/login-events/me?limit=$limit")
|
val response = client.request("GET", "/api/auth/login-events?limit=$limit")
|
||||||
return client.json.decodeFromString<List<LoginEvent>>(response)
|
return client.json.decodeFromString<EventsResponse>(response).events
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── TOTP Secret Retrieval (Phase 5C) ─────────────────────
|
// ── TOTP Secret Retrieval (Phase 5C) ─────────────────────
|
||||||
|
|||||||
@ -93,12 +93,35 @@ public struct BLPasskey: Codable, Sendable {
|
|||||||
|
|
||||||
/// Trusted/remembered device.
|
/// Trusted/remembered device.
|
||||||
public struct BLDevice: Codable, Sendable {
|
public struct BLDevice: Codable, Sendable {
|
||||||
public let id: String
|
public let fingerprint: String
|
||||||
public let name: String
|
|
||||||
public let platform: String
|
|
||||||
public let trustLevel: String
|
public let trustLevel: String
|
||||||
|
public let deviceInfo: DeviceInfo?
|
||||||
|
public let lastIp: String?
|
||||||
|
public let lastLocation: String?
|
||||||
public let trustExpiresAt: String?
|
public let trustExpiresAt: String?
|
||||||
public let lastLoginAt: String
|
public let createdAt: String
|
||||||
|
public let lastSeenAt: String
|
||||||
|
public let isTrusted: Bool
|
||||||
|
|
||||||
|
public struct DeviceInfo: Codable, Sendable {
|
||||||
|
public let userAgent: String?
|
||||||
|
public let platform: String?
|
||||||
|
public let model: String?
|
||||||
|
public let os: String?
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: display name derived from device info.
|
||||||
|
public var name: String {
|
||||||
|
deviceInfo?.model ?? deviceInfo?.platform ?? fingerprint.prefix(8).description
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: platform string.
|
||||||
|
public var platform: String {
|
||||||
|
deviceInfo?.platform ?? "unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stable identifier for SwiftUI ForEach.
|
||||||
|
public var id: String { fingerprint }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Login event for security log.
|
/// Login event for security log.
|
||||||
@ -505,7 +528,9 @@ public final class BLAuthClient {
|
|||||||
|
|
||||||
/// List devices for current user.
|
/// List devices for current user.
|
||||||
public func listDevices() async throws -> [BLDevice] {
|
public func listDevices() async throws -> [BLDevice] {
|
||||||
return try await client.request(path: "/api/auth/devices", responseType: [BLDevice].self)
|
struct DevicesResponse: Codable { let devices: [BLDevice] }
|
||||||
|
let result = try await client.request(path: "/api/auth/devices", responseType: DevicesResponse.self)
|
||||||
|
return result.devices
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trust the current device (promotes to trusted, skips MFA for 90 days).
|
/// Trust the current device (promotes to trusted, skips MFA for 90 days).
|
||||||
@ -513,14 +538,14 @@ public final class BLAuthClient {
|
|||||||
_ = try await client.rawRequest(path: "/api/auth/devices/trust", method: "POST")
|
_ = try await client.rawRequest(path: "/api/auth/devices/trust", method: "POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revoke trust on a specific device.
|
/// Revoke trust on a specific device by fingerprint.
|
||||||
public func revokeDevice(deviceId: String) async throws {
|
public func revokeDevice(fingerprint: String) async throws {
|
||||||
_ = try await client.rawRequest(path: "/api/auth/devices/\(deviceId)", method: "DELETE")
|
_ = try await client.rawRequest(path: "/api/auth/devices/\(fingerprint)", method: "DELETE")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Revoke all devices (requires step-up).
|
/// Revoke all device trust.
|
||||||
public func revokeAllDevices() async throws {
|
public func revokeAllDevices() async throws {
|
||||||
_ = try await client.rawRequest(path: "/api/auth/devices", method: "DELETE")
|
_ = try await client.rawRequest(path: "/api/auth/devices/revoke-all", method: "POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Step-Up Auth (SmartAuth v2)
|
// MARK: - Step-Up Auth (SmartAuth v2)
|
||||||
@ -542,10 +567,12 @@ public final class BLAuthClient {
|
|||||||
|
|
||||||
/// Get login events for the current user.
|
/// Get login events for the current user.
|
||||||
public func getLoginHistory(limit: Int = 20) async throws -> [BLLoginEvent] {
|
public func getLoginHistory(limit: Int = 20) async throws -> [BLLoginEvent] {
|
||||||
return try await client.request(
|
struct EventsResponse: Codable { let events: [BLLoginEvent] }
|
||||||
path: "/api/auth/login-events/me?limit=\(limit)",
|
let result = try await client.request(
|
||||||
responseType: [BLLoginEvent].self
|
path: "/api/auth/login-events?limit=\(limit)",
|
||||||
|
responseType: EventsResponse.self
|
||||||
)
|
)
|
||||||
|
return result.events
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TOTP Secret Retrieval (Phase 5C)
|
// MARK: - TOTP Secret Retrieval (Phase 5C)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user