Skip to content

Commit bd8993b

Browse files
Merge pull request #4 from tkhq/taylor/update-example
Update examples and docs
2 parents 7602b65 + 338f53a commit bd8993b

File tree

6 files changed

+149
-94
lines changed

6 files changed

+149
-94
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ The TurnkeySDK is built to support macOS, iOS, tvOS, watchOS, and visionOS, maki
1111
To integrate the TurnkeySDK into your Swift project, you need to add it as a dependency in your Package.swift file:
1212

1313
```swift
14-
.package(url: "https://github.com/tkhq/swift-sdk", from: "1.0.0")
14+
.package(url: "https://github.com/tkhq/swift-sdk", from: "1.1.0")
1515
```
1616

1717
## Usage

Sources/TurnkeySDK/TurnkeyClient.generated.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ public struct TurnkeyClient {
4848
/// - apiPublicKey: The public key obtained from Turnkey, used to identify the client.
4949
/// - baseUrl: The base URL of the Turnkey API. Defaults to "https://api.turnkey.com".
5050
///
51-
/// - Note: For client-side usage where all authenticated requests need secure key management,
52-
/// it is recommended to use the `AuthKeyManager` for creating, storing, and securely using key pairs.
53-
/// For more details, refer to the [AuthKeyManager](#AuthKeyManager).
54-
///
5551
/// - Example:
5652
/// ```
5753
/// let client = TurnkeyClient(apiPrivateKey: "your_api_private_key", apiPublicKey: "your_api_public_key")

docs/email-auth.md

Lines changed: 92 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Email Authentication
22

3-
This guide provides a walkthrough for implementing email authentication in a Swift application using the [TurnkeyClient](../Sources/TurnkeySDK/TurnkeyClient.generated.swift). This process involves generating key pairs, handling encrypted bundles, and verifying user identity.
3+
This guide provides a walkthrough for implementing email authentication in a Swift application using the [TurnkeyClient](../Sources/TurnkeySDK/TurnkeyClient.generated.swift). This process involves handling encrypted bundles and verifying user identity.
44

55
For a more detailed explanation of the email authentication process, please refer to the [Turnkey API documentation](https://docs.turnkey.com/features/email-auth).
66

@@ -24,104 +24,140 @@ let client = TurnkeyClient(proxyURL: proxyURL)
2424

2525
You may also forgo the use of the provided proxy middleware and make the request yourself.
2626

27-
## Step 2: Generate Ephemeral Key Pair
28-
29-
Next we'll generate an ephemeral key pair, which is will be used to decrypt the encrypted bundle sent
30-
that the user will receive in their email.
31-
32-
```swift
33-
// Create a new ephemeral private key using P-256 curve for key agreement.
34-
let ephemeralPrivateKey = P256.KeyAgreement.PrivateKey()
35-
36-
// Extract the public key from the private key and convert it to a string using the x963 representation.
37-
let targetPublicKey = try ephemeralPrivateKey.publicKey.toString(representation: .x963)
38-
```
39-
40-
## Step 3: Define Authentication Parameters
27+
## Step 2: Define Authentication Parameters
4128

4229
```swift
4330
let organizationId = "your_organization_id"
4431
let email = "[email protected]"
45-
let targetPublicKey = publicKey.toString(representation: .raw)
4632
let expirationSeconds = "3600"
4733
let emailCustomization = Components.Schemas.EmailCustomizationParams() // Customize as needed
4834
```
4935

50-
## Step 4: Send Email Authentication Request
36+
## Step 3: Send Email Authentication Request
37+
38+
With the TurnkeyClient initialized, you can now send an email authentication request. This involves using the `emailAuth` method of the TurnkeyClient, passing in the necessary parameters.
39+
40+
### Detailed Explanation
5141

52-
With the TurnkeyClient initialized and the ephemeral key pair generated, you can now send an email authentication request. This involves using the `emailAuth` method of the TurnkeyClient, passing in the necessary parameters.
42+
- **Ephemeral Key Generation**: The `emailAuth` method generates an ephemeral private key, which is used to create a public key for the authentication process. This ephemeral key is stored in memory and is used to decrypt the encrypted bundle sent to the user's email.
43+
44+
- **Tuple Response**: The `emailAuth` method returns a tuple containing two elements:
45+
1. `Operations.EmailAuth.Output`: This is the output of the email authentication operation, which includes the response from the Turnkey API.
46+
2. `verify`: A closure function that takes an encrypted bundle as input and returns an `AuthResult`. This closure uses the ephemeral private key to decrypt the bundle and verify the authentication.
5347

5448
```swift
55-
let emailAuthResult = try await client.emailAuth(
49+
let (output, verify) = try await client.emailAuth(
5650
organizationId: organizationId,
5751
email: email,
58-
targetPublicKey: targetPublicKey,
5952
apiKeyName: "your_api_key_name",
6053
expirationSeconds: expirationSeconds,
6154
emailCustomization: emailCustomization
6255
)
56+
57+
// Assert the response
58+
switch output {
59+
case let .ok(response):
60+
switch response.body {
61+
case let .json(emailAuthResponse):
62+
print(emailAuthResponse.activity.organizationId)
63+
// We successfully initiated the email authentication request
64+
// We'll use the verify function to verify the encrypted bundle in the next step
65+
}
66+
case let .undocumented(statusCode, undocumentedPayload):
67+
// Handle the undocumented response
68+
if let body = undocumentedPayload.body {
69+
let bodyString = try await String(collecting: body, upTo: .max)
70+
print("Undocumented response body: \(bodyString)")
71+
}
72+
print("Undocumented response: \(statusCode)")
73+
}
6374
```
6475

65-
After sending the email authentication request, it's important to handle the response appropriately. If the authentication is successful, you should save the user's sub-organizationId from the response for future use. You'll need this organizationId later to verify the user's keys.
76+
## Step 4: Verify Encrypted Bundle
77+
78+
After your user receives the encrypted bundle from Turnkey, via email, you need to verify this bundle to retrieve the necessary keys for further authentication steps. We'll use the `verify` function returned from the previous step.
79+
80+
### Detailed Explanation
81+
82+
- **AuthResult**: The `verify` function returns an `AuthResult` object, which contains:
83+
84+
- `whoamiResponse`: The result of calling `getWhoami`, which verifies the authentication and retrieves user details.
85+
- `apiPublicKey` and `apiPrivateKey`: The keys obtained from the decrypted bundle, used for further authenticated requests.
86+
87+
- **getWhoami Call**: The `verify` function internally calls the `getWhoami` method to ensure the credentials are valid and to fetch user details from the Turnkey API.
6688

6789
```swift
68-
switch emailAuthResult {
69-
case .ok(let response):
70-
// The user's sub-organizationId:
71-
let organizationId = response.activity.organizationId
72-
// Proceed with user session creation
73-
case .undocumented(let statusCode, let undocumentedPayload):
74-
// Handle error, possibly retry or log
90+
do {
91+
let authResult = try await verify(bundle)
92+
print("Verification successful: \(authResult)")
93+
} catch {
94+
print("Error occurred during verification: \(error)")
7595
}
7696
```
7797

78-
## Step 6: Verify Encrypted Bundle
98+
This method will verify the encrypted bundle and provide you with the necessary authentication result.
7999

80-
After your user receives the encrypted bundle from Turnkey, via email, you need to decrypt this bundle to retrieve the necessary keys for further authentication steps. Use the [`decryptBundle`](../Sources/Shared/AuthManager.swift) method from the `AuthManager` to handle this.
100+
## Step 5: Initialize the TurnkeyClient with API Keys
81101

82-
```swift
83-
let (privateKey, publicKey) = try AuthManager.decryptBundle(encryptedBundle)
84-
```
102+
After successfully verifying the encrypted bundle and retrieving the private and public API keys, you can initialize a TurnkeyClient instance using these keys for further authenticated requests:
85103

86-
This method will decrypt the encrypted bundle and provide you with the private and public keys needed for the session.
87-
At this point in the authentication process, you have two options:
104+
```swift
105+
// Use the apiPublicKey and apiPrivateKey from the authResult
106+
let apiPublicKey = authResult.apiPublicKey
107+
let apiPrivateKey = authResult.apiPrivateKey
88108

89-
1. Prompt the user for passkey authentication (using the `PasskeyManager`) and add a passkey as an authenticator.
90-
2. Save the API private key in the keychain and use that for subsequent authentication requests.
109+
// Initialize a new TurnkeyClient instance with the provided privateKey and publicKey
110+
let turnkeyClient = TurnkeyClient(apiPrivateKey: apiPrivateKey, apiPublicKey: apiPublicKey)
111+
```
91112

92-
Note: Since the decrypted API key is similar to a session key, it should be handled with the same level of security as authentication tokens.
113+
## Step 6: Create Read Only Session
93114

94-
## Step 7: Initialize the TurnkeyClient and Verify the user
115+
### Extract API Keys and Sub-Organization ID
95116

96-
After successfully decrypting the encrypted bundle and retrieving the private and public API keys, you can initialize a TurnkeyClient instance using these keys for further authenticated requests:
117+
First, get the `apiPublicKey` and `apiPrivateKey` from the `authResult`, and retrieve the `organizationId` from the `whoamiResponse`. Then, instantiate the `TurnkeyClient`.
97118

98119
```swift
99-
// ...
100-
101-
let apiPublicKey = try publicKey.toString(representation: .compressed)
102-
let apiPrivateKey = try privateKey.toString(representation: .raw)
120+
// Use the apiPublicKey and apiPrivateKey from the authResult
121+
let apiPublicKey = authResult.apiPublicKey
122+
let apiPrivateKey = authResult.apiPrivateKey
123+
124+
// Get the organizationId from the whoamiResponse
125+
let whoamiResponse = authResult.whoamiResponse
126+
var subOrganizationId: String?
127+
128+
switch whoamiResponse {
129+
case let .ok(response):
130+
switch response.body {
131+
case let .json(whoamiResponse):
132+
subOrganizationId = whoamiResponse.organizationId
133+
print("Sub-Organization ID: \(subOrganizationId ?? "N/A")")
134+
}
135+
case let .undocumented(statusCode, undocumentedPayload):
136+
if let body = undocumentedPayload.body {
137+
let bodyString = try await String(collecting: body, upTo: .max)
138+
print("Undocumented response body: \(bodyString)")
139+
}
140+
print("Undocumented response: \(statusCode)")
141+
}
103142

104143
// Initialize a new TurnkeyClient instance with the provided privateKey and publicKey
105144
let turnkeyClient = TurnkeyClient(apiPrivateKey: apiPrivateKey, apiPublicKey: apiPublicKey)
106145
```
107146

108-
### Verifying User Credentials with getWhoami
147+
### Create Read Only Session
109148

110-
After initializing the TurnkeyClient with the decrypted API keys, it is recommended to verify the validity of these credentials. This can be done using the `getWhoami` method, which checks the active status of the credentials against the Turnkey API.
111-
112-
Note: We're using the `organizationId` from the email authentication result as the `organizationId` for the `getWhoami` request.
149+
Next, use the `subOrganizationId` to call the `createReadOnlySession` method on the `TurnkeyClient`.
113150

114151
```swift
115152
do {
116-
let whoamiResponse = try await turnkeyClient.getWhoami(organizationId: organizationId)
117-
118-
switch whoamiResponse {
119-
case .ok(let response):
120-
print("Credential verification successful: \(whoamiResponse)")
121-
case .undocumented(let statusCode, let undocumentedPayload):
122-
print("Error during credential verification: \(error)")
153+
// Use the user's sub-organization ID to create a read-only session
154+
if let orgId = subOrganizationId {
155+
let readOnlySessionOutput = try await turnkeyClient.createReadOnlySession(organizationId: orgId)
156+
print("Read-only session created successfully: \(readOnlySessionOutput)")
157+
} else {
158+
print("Failed to extract organization ID.")
123159
}
124160
} catch {
125-
print("Error during credential verification: \(error)")
161+
print("Error occurred while creating read-only session: \(error)")
126162
}
127163
```

docs/proxy-middleware.md

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Prox yMiddleware
1+
# Proxy Middleware
22

33
The [`ProxyMiddleware`](/Sources/Middleware/ProxyMiddleware.swift) is integrated into the TurnkeyClient through its initializer that accepts a proxy server URL. This setup is particularly useful for handling scenarios where direct authenticated requests are not feasible, such as during onboarding flows or when additional server-side processing is required before reaching Turnkey's backend.
44

@@ -8,7 +8,7 @@ Here's how you can initialize the TurnkeyClient with a proxy server URL:
88
import TurnkeySDK
99

1010
// Initialize the TurnkeyClient with a proxy server URL
11-
let turnkeyClient = TurnkeyClient(proxyURL: "https://your-proxy-server.com")
11+
let turnkeyClient = TurnkeyClient(proxyURL: "https://your-proxy-server.com/api/turnkey-proxy")
1212
```
1313

1414
This initializer configures the TurnkeyClient to route all requests through the specified proxy server. The proxy server is then responsible for forwarding these requests to a backend capable of authenticating them using an API private key. After authentication, the proxy server forwards the requests to Turnkey's backend and relays the response back to the client.
@@ -21,8 +21,53 @@ This setup is especially useful for operations like:
2121

2222
## Important Notes
2323

24-
- **Response Matching**: It is crucial that the response from the developer's backend matches exactly with what would be expected from Turnkey's backend. Any discrepancy in the response format or data can cause the request to fail.
25-
- **Security**: Ensure that the proxy server is secure and only accessible to authorized entities to prevent unauthorized access and data breaches.
24+
#### X-Forwarded-For Header
25+
26+
The middleware adds an `X-Forwarded-For` header to each request, which contains the original request URL. This is used to forward the request to Turnkey's backend.
27+
28+
Example implementation of a proxy server:
29+
30+
```javascript
31+
const express = require('express');
32+
const app = express();
33+
34+
app.use(express.json());
35+
36+
app.post('/api/turnkey-proxy', async (req, res) => {
37+
// The original request URL e.g. https://api.turnkey.com/public/v1/submit/email_auth
38+
const turnkeyApiRequestURL = req.headers['x-forwarded-for'];
39+
40+
// Remove the 'x-forwarded-for' header
41+
delete req.headers['x-forwarded-for'];
42+
43+
try {
44+
// Forward the request to the original URL
45+
const response = await fetch(turnkeyApiRequestURL, {
46+
method: 'POST',
47+
headers: req.headers,
48+
body: JSON.stringify(req.body),
49+
});
50+
51+
// Get the response data
52+
const data = await response.json();
53+
54+
// Send the response back to the client
55+
res.status(response.status).json(data);
56+
} catch (error) {
57+
console.error('Error forwarding request:', error);
58+
res.status(500).send('Internal Server Error');
59+
}
60+
});
61+
62+
// Start the server
63+
app.listen(3000, () => {
64+
console.log('Server is running on port 3000');
65+
});
66+
```
67+
68+
#### Response Matching
69+
70+
It is crucial that the response from the developer's backend matches exactly with what would be expected from Turnkey's backend. Any discrepancy in the response format or data can cause the request to fail.
2671

2772
## Conclusion
2873

example/TurnkeyiOSExample/TurnkeyiOSExample/AccountManager.swift

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -153,33 +153,15 @@ class AccountManager: NSObject, ASAuthorizationControllerPresentationContextProv
153153

154154
func verifyEncryptedBundle(bundle: String) async {
155155
do {
156-
let (privateKey, publicKey) = try authKeyManager.decryptBundle(bundle)
157-
158-
let apiPublicKey = try publicKey.toString(representation: .compressed)
159-
let apiPrivateKey = try privateKey.toString(representation: .raw)
160-
161-
print("apiPrivateKey: \(apiPrivateKey) - apiPublicKey:\(apiPublicKey)")
162-
// Initialize a new TurnkeyClient instance with the provided privateKey and publicKey
163-
let turnkeyClient = TurnkeyClient(apiPrivateKey: apiPrivateKey, apiPublicKey: apiPublicKey)
164-
let response = try await turnkeyClient.getWhoami(organizationId: parentOrgId)
165-
166-
// Assert the response
167-
switch response {
168-
case let .ok(response):
169-
switch response.body {
170-
case let .json(emailAuthResponse):
171-
print(emailAuthResponse)
172-
}
173-
case let .undocumented(statusCode, undocumentedPayload):
174-
// Handle the undocumented response
175-
if let body = undocumentedPayload.body {
176-
let bodyString = try await String(collecting: body, upTo: .max)
177-
print("Undocumented response body: \(bodyString)")
178-
}
179-
print("Undocumented response: \(statusCode)")
156+
// Use the stored verify closure
157+
if let verify = verifyClosure {
158+
let authResult = try await verify(bundle)
159+
print("Verification successful: \(authResult)")
160+
} else {
161+
print("Verify closure is not set.")
180162
}
181163
} catch {
182-
print("Error occurred: \(error)")
164+
print("Error occurred during verification: \(error)")
183165
}
184166
}
185167

templates/TurnkeyClient.stencil

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ public struct TurnkeyClient {
5454
/// - apiPublicKey: The public key obtained from Turnkey, used to identify the client.
5555
/// - baseUrl: The base URL of the Turnkey API. Defaults to "https://api.turnkey.com".
5656
///
57-
/// - Note: For client-side usage where all authenticated requests need secure key management,
58-
/// it is recommended to use the `AuthKeyManager` for creating, storing, and securely using key pairs.
59-
/// For more details, refer to the [AuthKeyManager](#AuthKeyManager).
60-
///
6157
/// - Example:
6258
/// ```
6359
/// let client = TurnkeyClient(apiPrivateKey: "your_api_private_key", apiPublicKey: "your_api_public_key")

0 commit comments

Comments
 (0)