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
|
||||
data class Device(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val platform: String,
|
||||
val fingerprint: String,
|
||||
val trustLevel: String,
|
||||
val deviceInfo: DeviceInfo? = null,
|
||||
val lastIp: String? = null,
|
||||
val lastLocation: 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
|
||||
@ -518,10 +534,13 @@ class BLAuthClient(
|
||||
|
||||
// ── Devices (SmartAuth v2) ───────────────────────────────
|
||||
|
||||
@Serializable
|
||||
private data class DevicesResponse(val devices: List<Device>)
|
||||
|
||||
/** List devices for current user. */
|
||||
suspend fun listDevices(): List<Device> {
|
||||
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). */
|
||||
@ -529,14 +548,14 @@ class BLAuthClient(
|
||||
client.request("POST", "/api/auth/devices/trust")
|
||||
}
|
||||
|
||||
/** Revoke trust on a specific device. */
|
||||
suspend fun revokeDevice(deviceId: String) {
|
||||
client.request("DELETE", "/api/auth/devices/$deviceId")
|
||||
/** Revoke trust on a specific device by fingerprint. */
|
||||
suspend fun revokeDevice(fingerprint: String) {
|
||||
client.request("DELETE", "/api/auth/devices/$fingerprint")
|
||||
}
|
||||
|
||||
/** Revoke all devices (requires step-up). */
|
||||
/** Revoke all device trust. */
|
||||
suspend fun revokeAllDevices() {
|
||||
client.request("DELETE", "/api/auth/devices")
|
||||
client.request("POST", "/api/auth/devices/revoke-all")
|
||||
}
|
||||
|
||||
// ── Step-Up Auth (SmartAuth v2) ──────────────────────────
|
||||
@ -550,10 +569,13 @@ class BLAuthClient(
|
||||
|
||||
// ── Login History (SmartAuth v2) ─────────────────────────
|
||||
|
||||
@Serializable
|
||||
private data class EventsResponse(val events: List<LoginEvent>)
|
||||
|
||||
/** Get login events for the current user. */
|
||||
suspend fun getLoginHistory(limit: Int = 20): List<LoginEvent> {
|
||||
val response = client.request("GET", "/api/auth/login-events/me?limit=$limit")
|
||||
return client.json.decodeFromString<List<LoginEvent>>(response)
|
||||
val response = client.request("GET", "/api/auth/login-events?limit=$limit")
|
||||
return client.json.decodeFromString<EventsResponse>(response).events
|
||||
}
|
||||
|
||||
// ── TOTP Secret Retrieval (Phase 5C) ─────────────────────
|
||||
|
||||
@ -93,12 +93,35 @@ public struct BLPasskey: Codable, Sendable {
|
||||
|
||||
/// Trusted/remembered device.
|
||||
public struct BLDevice: Codable, Sendable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let platform: String
|
||||
public let fingerprint: String
|
||||
public let trustLevel: String
|
||||
public let deviceInfo: DeviceInfo?
|
||||
public let lastIp: String?
|
||||
public let lastLocation: 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.
|
||||
@ -505,7 +528,9 @@ public final class BLAuthClient {
|
||||
|
||||
/// List devices for current user.
|
||||
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).
|
||||
@ -513,14 +538,14 @@ public final class BLAuthClient {
|
||||
_ = try await client.rawRequest(path: "/api/auth/devices/trust", method: "POST")
|
||||
}
|
||||
|
||||
/// Revoke trust on a specific device.
|
||||
public func revokeDevice(deviceId: String) async throws {
|
||||
_ = try await client.rawRequest(path: "/api/auth/devices/\(deviceId)", method: "DELETE")
|
||||
/// Revoke trust on a specific device by fingerprint.
|
||||
public func revokeDevice(fingerprint: String) async throws {
|
||||
_ = 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 {
|
||||
_ = 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)
|
||||
@ -542,10 +567,12 @@ public final class BLAuthClient {
|
||||
|
||||
/// Get login events for the current user.
|
||||
public func getLoginHistory(limit: Int = 20) async throws -> [BLLoginEvent] {
|
||||
return try await client.request(
|
||||
path: "/api/auth/login-events/me?limit=\(limit)",
|
||||
responseType: [BLLoginEvent].self
|
||||
struct EventsResponse: Codable { let events: [BLLoginEvent] }
|
||||
let result = try await client.request(
|
||||
path: "/api/auth/login-events?limit=\(limit)",
|
||||
responseType: EventsResponse.self
|
||||
)
|
||||
return result.events
|
||||
}
|
||||
|
||||
// MARK: - TOTP Secret Retrieval (Phase 5C)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user