Skip to content

Instantly share code, notes, and snippets.

@jarodl
Last active May 14, 2025 23:46
Show Gist options
  • Save jarodl/81622cf0cb420790d16edca1724f48f9 to your computer and use it in GitHub Desktop.
Save jarodl/81622cf0cb420790d16edca1724f48f9 to your computer and use it in GitHub Desktop.
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