Skip to content

Commit 19c1632

Browse files
committed
Merge pull-request #20
2 parents 26a96fc + 150f4b0 commit 19c1632

File tree

3 files changed

+62
-61
lines changed

3 files changed

+62
-61
lines changed

Examples/swift-demo-wallet/example-server/src/handler.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export async function verifyOtp(
103103
export async function createSubOrg(
104104
req: Request<{}, {}, CreateSubOrgParams>
105105
): Promise<CreateSubOrgResponse> {
106-
const { email, phone, passkey, oauth, apiKeys } = req.body;
106+
const { passkey, apiKeys } = req.body;
107107

108108
const authenticators = passkey
109109
? [
@@ -115,39 +115,17 @@ export async function createSubOrg(
115115
]
116116
: [];
117117

118-
const oauthProviders = oauth
119-
? [
120-
{
121-
providerName: oauth.providerName,
122-
oidcToken: oauth.oidcToken,
123-
},
124-
]
125-
: [];
126-
127-
let userEmail = email;
128-
129-
if (oauth) {
130-
const decoded = decodeJwt(oauth.oidcToken);
131-
if (decoded?.email) {
132-
userEmail = decoded.email;
133-
}
134-
}
135-
136-
const userPhoneNumber = phone;
137-
const subOrganizationName = `Sub Org - ${email || phone}`;
138-
const userName = email ? email.split("@")[0] || email : "";
118+
const subOrganizationName = `Sub Org - ${new Date().toISOString()}`;
139119

140120
const result = await turnkey.createSubOrganization({
141121
organizationId: turnkeyConfig.defaultOrganizationId,
142122
subOrganizationName,
143123
rootUsers: [
144124
{
145-
userName,
146-
userEmail,
147-
userPhoneNumber,
148-
oauthProviders,
125+
userName: "Passkey User",
149126
authenticators,
150127
apiKeys: apiKeys ?? [],
128+
oauthProviders: [],
151129
},
152130
],
153131
rootQuorumThreshold: 1,

Examples/swift-demo-wallet/example-server/src/types.ts

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,15 @@ export type SendOtpResponse = {
3737
};
3838

3939
export type CreateSubOrgParams = {
40-
email?: string;
41-
phone?: string;
4240
passkey?: {
4341
name?: string;
4442
challenge: string;
4543
attestation: Attestation;
4644
};
47-
oauth?: OAuthProviderParams;
4845
apiKeys?: {
4946
apiKeyName: string;
5047
publicKey: string;
51-
curveType: CurveType;
48+
curveType: ApiKeyCurveType;
5249
expirationSeconds?: string;
5350
}[];
5451
};
@@ -70,10 +67,5 @@ export type VerifyOtpResponse = {
7067
token?: string;
7168
};
7269

73-
export type OAuthProviderParams = {
74-
providerName: string;
75-
oidcToken: string;
76-
};
77-
7870
export type Attestation = TurnkeyApiTypes["v1Attestation"];
79-
export type CurveType = TurnkeyApiTypes["v1ApiKeyCurve"];
71+
export type ApiKeyCurveType = TurnkeyApiTypes["v1ApiKeyCurve"];

Examples/swift-demo-wallet/swift-demo-wallet/Contexts/AuthContext.swift

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)