@@ -31,13 +31,18 @@ final class AuthContext: ObservableObject {
3131
3232 enum OtpType : String , Codable {
3333 case email = " OTP_TYPE_EMAIL "
34- case sms = " OTP_TYPE_SMS "
34+ case sms = " OTP_TYPE_SMS "
3535 }
3636
3737 struct CreateSubOrgRequest : Codable {
38- let passkey : PasskeyRegistrationResult ?
39- init ( registration: PasskeyRegistrationResult ) {
40- self . passkey = registration
38+ var passkey : PasskeyRegistrationResult ?
39+ var apiKeys : [ ApiKeyPayload ] ?
40+
41+ struct ApiKeyPayload : Codable {
42+ var apiKeyName : String
43+ var publicKey : String
44+ var curveType : Components . Schemas . ApiKeyCurve
45+ var expirationSeconds : String ?
4146 }
4247 }
4348
@@ -74,7 +79,12 @@ final class AuthContext: ObservableObject {
7479
7580 let publicKey = try turnkey. createKeyPair ( )
7681
77- let body = SendOtpRequest ( otpType: type, contact: contact, userIdentifier: publicKey)
82+ let body = SendOtpRequest (
83+ otpType: type,
84+ contact: contact,
85+ userIdentifier: publicKey
86+ )
87+
7888 var request = URLRequest ( url: backendURL. appendingPathComponent ( " /auth/sendOtp " ) )
7989 request. httpMethod = " POST "
8090 request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
@@ -128,19 +138,42 @@ final class AuthContext: ObservableObject {
128138 defer { stopLoading ( ) }
129139
130140 let registration = try await createPasskey (
131- user: PasskeyUser ( id: UUID ( ) . uuidString,
132- name: " Anonymous User " ,
133- displayName: " Anonymous User " ) ,
141+ user: PasskeyUser ( id: UUID ( ) . uuidString, name: " Anonymous User " , displayName: " Anonymous User " ) ,
134142 rp: RelyingParty ( id: Constants . App. rpId, name: Constants . App. appName) ,
135143 presentationAnchor: anchor
136144 )
137145
138- let subOrgId = try await createSubOrganization ( registration: registration)
146+ // for one-tap passkey sign-up, we generate a temporary API key pair
147+ // which is added as an authentication method for the new sub-org user
148+ // this allows us to stamp the session creation request immediately after
149+ // without prompting the user
150+ let ( _, publicKeyCompressed, privateKey) = TurnkeyCrypto . generateP256KeyPair ( )
151+
152+ let apiKey = CreateSubOrgRequest . ApiKeyPayload (
153+ apiKeyName: " Tempoarary API Key " ,
154+ publicKey: publicKeyCompressed,
155+ curveType: Components . Schemas. ApiKeyCurve. API_KEY_CURVE_P256,
156+ expirationSeconds: Constants . Turnkey. sessionDuration
157+ )
158+
159+ let requestBody = CreateSubOrgRequest (
160+ passkey: registration,
161+ apiKeys: [ apiKey]
162+ )
163+
164+ let subOrgId = try await createSubOrganization ( body: requestBody)
165+
166+ let ephemeralClient = TurnkeyClient (
167+ apiPrivateKey: privateKey,
168+ apiPublicKey: publicKeyCompressed,
169+ baseUrl: Constants . Turnkey. apiUrl
170+ )
139171
140172 try await stampLoginAndCreateSession (
141173 anchor: anchor,
142174 organizationId: subOrgId,
143- expiresInSeconds: Constants . Turnkey. sessionDuration
175+ expiresInSeconds: Constants . Turnkey. sessionDuration,
176+ client: ephemeralClient
144177 )
145178 }
146179
@@ -151,16 +184,12 @@ final class AuthContext: ObservableObject {
151184
152185 try await stampLoginAndCreateSession (
153186 anchor: anchor,
154-
155- // parent orgId
156187 organizationId: Constants . Turnkey. organizationId,
157188 expiresInSeconds: Constants . Turnkey. sessionDuration
158189 )
159190 }
160191
161- private func createSubOrganization( registration: PasskeyRegistrationResult ) async throws -> String {
162- let body = CreateSubOrgRequest ( registration: registration)
163-
192+ private func createSubOrganization( body: CreateSubOrgRequest ) async throws -> String {
164193 var request = URLRequest ( url: backendURL. appendingPathComponent ( " /auth/createSubOrg " ) )
165194 request. httpMethod = " POST "
166195 request. setValue ( " application/json " , forHTTPHeaderField: " Content-Type " )
@@ -177,29 +206,31 @@ final class AuthContext: ObservableObject {
177206 private func stampLoginAndCreateSession(
178207 anchor: ASPresentationAnchor ,
179208 organizationId: String ,
180- expiresInSeconds: String
209+ expiresInSeconds: String ,
210+ client: TurnkeyClient ? = nil
181211 ) async throws {
182-
183- let client = TurnkeyClient (
212+ let client = client ?? TurnkeyClient (
184213 rpId: Constants . App. rpId,
185214 presentationAnchor: anchor,
186215 baseUrl: Constants . Turnkey. apiUrl
187216 )
188217
218+ let publicKey = try turnkey. createKeyPair ( )
219+
189220 do {
190- let publicKey = try turnkey. createKeyPair ( )
191-
192221 let resp = try await client. stampLogin (
193- organizationId: organizationId,
194- publicKey: publicKey,
195- expirationSeconds: expiresInSeconds,
196- invalidateExisting: true
222+ organizationId: organizationId,
223+ publicKey: publicKey,
224+ expirationSeconds: expiresInSeconds,
225+ invalidateExisting: true
197226 )
198227
199228 guard
200229 case let . json( body) = resp. body,
201230 let jwt = body. activity. result. stampLoginResult? . session
202- else { throw AuthError . serverError }
231+ else {
232+ throw AuthError . serverError
233+ }
203234
204235 try await turnkey. createSession ( jwt: jwt)
205236
0 commit comments