Last active
May 14, 2025 23:46
-
-
Save jarodl/81622cf0cb420790d16edca1724f48f9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
private var passkeyRegistrationTask: Task<Void, Never>? | |
private var authClient: TurnkeyClient! | |
@objc private func handlePasskeyRegistrationCompleted(_ notification: Notification) { | |
guard let result = notification.userInfo?["result"] as? PasskeyRegistrationResult else { | |
return | |
} | |
guard let displayName = displayName else { | |
return | |
} | |
let ephemeralPrivateKey = P256.KeyAgreement.PrivateKey() | |
let publicKey = ephemeralPrivateKey.publicKey | |
guard let apiPublicKey = try? publicKey.toString( | |
representation: PublicKeyRepresentation.x963), | |
let apiPublicKeyCompressed = try? publicKey.toString( | |
representation: PublicKeyRepresentation.compressed | |
), | |
let apiPrivateKey = try? ephemeralPrivateKey.toString( | |
representation: PrivateKeyRepresentation.raw) else { | |
Logger.error("Missing api keys") | |
return | |
} | |
let authClient = TurnkeyClient( | |
apiPrivateKey: apiPrivateKey, | |
apiPublicKey: apiPublicKeyCompressed | |
) | |
self.authClient = authClient | |
passkeyRegistrationTask = Task { | |
do { | |
let expirationSeconds: Int = 3600 | |
let result = try await sendCreateSubOrgRequest( | |
ephemeralPublicKey: apiPublicKeyCompressed, | |
passkeyRegistrationResult: result, | |
displayName: displayName | |
) | |
Logger.info("Private key: \(apiPrivateKey) Public key: \(apiPublicKey)") | |
guard let result else { | |
throw TurnkeyAuthServiceError.failedCreatingSubOrganization | |
} | |
if let whoami = try? await authClient.getWhoami( | |
organizationId: result.subOrgId) { | |
Logger.info("Whoami result: \(whoami)") | |
switch whoami { | |
case .undocumented(statusCode: let status, let payload): | |
if let body = payload.body { | |
// Convert the HTTPBody to a string | |
let bodyString = try await String(collecting: body, upTo: .max) | |
print("Whoami bodyString: \(bodyString)") | |
// XCTFail("Undocumented response body: \(bodyString)") | |
} | |
print("Status code: \(status) payload: \(payload)") | |
default: | |
break | |
} | |
} | |
Logger.info("Create sub organization result from Convos backend: \(result)") | |
let sessionResponse = try await self.authClient.createReadWriteSession( | |
organizationId: result.subOrgId, | |
targetPublicKey: apiPublicKey, | |
userId: nil, | |
apiKeyName: "session-key", | |
expirationSeconds: String(expirationSeconds) | |
) | |
switch sessionResponse { | |
case .undocumented(statusCode: let status, let payload): | |
if let body = payload.body { | |
// Convert the HTTPBody to a string | |
let bodyString = try await String(collecting: body, upTo: .max) | |
print("bodyString: \(bodyString)") | |
// XCTFail("Undocumented response body: \(bodyString)") | |
} | |
print("status: \(status) payload: \(payload)") | |
case .ok(let output): | |
print("output: \(output)") | |
} | |
Logger.info("Session response: \(sessionResponse)") | |
let responseBody = try sessionResponse.ok.body.json | |
guard let result = responseBody.activity.result.createReadWriteSessionResultV2 else { | |
throw NSError( | |
domain: "TurnkeyClient", | |
code: 1, | |
userInfo: [NSLocalizedDescriptionKey: "Missing createReadWriteSessionResultV2"] | |
) | |
} | |
let organizationId = result.organizationId | |
let userId = result.userId | |
let (decryptedPrivateKey, decryptedPublicKey) = try AuthManager.decryptBundle( | |
encryptedBundle: result.credentialBundle, | |
ephemeralPrivateKey: ephemeralPrivateKey | |
) | |
let tempApiPublicKey = try decryptedPublicKey.toString( | |
representation: PublicKeyRepresentation.compressed) | |
let tempApiPrivateKey = try decryptedPrivateKey.toString( | |
representation: PrivateKeyRepresentation.raw) | |
// Instantiate temporary TurnkeyClient using decrypted keys | |
let tempClient = TurnkeyClient(apiPrivateKey: tempApiPrivateKey, apiPublicKey: tempApiPublicKey) | |
// Generate Secure Enclave key pair (new session key) | |
let enclaveKeyManager = SecureEnclaveKeyManager() | |
let keyTag = try enclaveKeyManager.createKeypair() | |
let enclavePublicKeyData = try enclaveKeyManager.publicKey(tag: keyTag) | |
let enclavePublicKeyHex = enclavePublicKeyData.toHexString() | |
// Create permanent API key via temporary client | |
let apiKeyParams = [ | |
Components.Schemas.ApiKeyParamsV2( | |
apiKeyName: "Session Key \(Int(Date().timeIntervalSince1970))", | |
publicKey: enclavePublicKeyHex, | |
curveType: .API_KEY_CURVE_P256, | |
expirationSeconds: String(expirationSeconds) | |
) | |
] | |
let _ = try await tempClient.createApiKeys( | |
organizationId: organizationId, | |
apiKeys: apiKeyParams, | |
userId: userId | |
) | |
// Derive session expiration and persist session | |
let sessionExpiry = Date().addingTimeInterval(TimeInterval(expirationSeconds)) | |
let newSession = Session( | |
keyTag: keyTag, expiresAt: sessionExpiry, userId: userId, organizationId: organizationId) | |
try SessionManager.shared.save(session: newSession) | |
let finalClient = TurnkeyClient() | |
let whoamiResponse = try await finalClient.getWhoami( | |
organizationId: Secrets.TURNKEY_PUBLIC_ORGANIZATION_ID) | |
Logger.info("Turnkey Whoami: \(whoamiResponse)") | |
let whoami = try whoamiResponse.ok.body.json | |
Logger.info("Finished registering with Turnkey: \(whoami)") | |
} catch { | |
Logger.error("Error registering with Turnkey: \(error)") | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment