diff --git a/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/BLAuthClient.kt b/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/BLAuthClient.kt index b441c85b..264b5966 100644 --- a/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/BLAuthClient.kt +++ b/packages/kotlin-platform-sdk/src/main/kotlin/com/bytelyst/platform/BLAuthClient.kt @@ -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) + /** List devices for current user. */ suspend fun listDevices(): List { val response = client.request("GET", "/api/auth/devices") - return client.json.decodeFromString>(response) + return client.json.decodeFromString(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) + /** Get login events for the current user. */ suspend fun getLoginHistory(limit: Int = 20): List { - val response = client.request("GET", "/api/auth/login-events/me?limit=$limit") - return client.json.decodeFromString>(response) + val response = client.request("GET", "/api/auth/login-events?limit=$limit") + return client.json.decodeFromString(response).events } // ── TOTP Secret Retrieval (Phase 5C) ───────────────────── diff --git a/packages/swift-platform-sdk/Sources/BLAuthClient.swift b/packages/swift-platform-sdk/Sources/BLAuthClient.swift index 7d1be559..89b06169 100644 --- a/packages/swift-platform-sdk/Sources/BLAuthClient.swift +++ b/packages/swift-platform-sdk/Sources/BLAuthClient.swift @@ -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)