From 27928a25f7aa3ccb31b007b2c7b5c10cc2bb3e28 Mon Sep 17 00:00:00 2001
From: Andrew Min
Date: Mon, 4 Dec 2023 17:24:52 -0500
Subject: [PATCH 1/4] release v2023.12.0
---
api/public_api.swagger.json | 129 +++++++++++++++++++++++++++++++++++-
1 file changed, 127 insertions(+), 2 deletions(-)
diff --git a/api/public_api.swagger.json b/api/public_api.swagger.json
index 738061f..93edb2f 100644
--- a/api/public_api.swagger.json
+++ b/api/public_api.swagger.json
@@ -897,6 +897,32 @@
"tags": ["Policies"]
}
},
+ "/public/v1/submit/email_auth": {
+ "post": {
+ "summary": "Email Auth",
+ "description": "Authenticate a user via Email",
+ "operationId": "EmailAuth",
+ "responses": {
+ "200": {
+ "description": "A successful response.",
+ "schema": {
+ "$ref": "#/definitions/ActivityResponse"
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/EmailAuthRequest"
+ }
+ }
+ ],
+ "tags": ["Email Auth"]
+ }
+ },
"/public/v1/submit/export_private_key": {
"post": {
"summary": "Export Private Key",
@@ -1482,7 +1508,8 @@
"ACTIVITY_TYPE_SIGN_TRANSACTION_V2",
"ACTIVITY_TYPE_EXPORT_PRIVATE_KEY",
"ACTIVITY_TYPE_EXPORT_WALLET",
- "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V4"
+ "ACTIVITY_TYPE_CREATE_SUB_ORGANIZATION_V4",
+ "ACTIVITY_TYPE_EMAIL_AUTH"
]
},
"AddressFormat": {
@@ -1515,6 +1542,11 @@
},
"updatedAt": {
"$ref": "#/definitions/external.data.v1.Timestamp"
+ },
+ "expirationSeconds": {
+ "type": "string",
+ "format": "uint64",
+ "description": "Optional window (in seconds) indicating how long the API Key should last."
}
},
"required": [
@@ -1535,6 +1567,10 @@
"publicKey": {
"type": "string",
"description": "The public component of a cryptographic key pair used to sign messages and transactions."
+ },
+ "expirationSeconds": {
+ "type": "string",
+ "description": "Optional window (in seconds) indicating how long the API Key should last."
}
},
"required": ["apiKeyName", "publicKey"]
@@ -2346,6 +2382,10 @@
"disableEmailRecovery": {
"type": "boolean",
"description": "Disable email recovery for the sub-organization"
+ },
+ "disableEmailAuth": {
+ "type": "boolean",
+ "description": "Disable email auth for the sub-organization"
}
},
"required": ["subOrganizationName", "rootUsers", "rootQuorumThreshold"]
@@ -2995,6 +3035,84 @@
"type": "string",
"enum": ["EFFECT_ALLOW", "EFFECT_DENY"]
},
+ "EmailAuthIntent": {
+ "type": "object",
+ "properties": {
+ "email": {
+ "type": "string",
+ "description": "Email of the authenticating user."
+ },
+ "targetPublicKey": {
+ "type": "string",
+ "description": "Client-side public key generated by the user, to which the email auth bundle (credentials) will be encrypted."
+ },
+ "apiKeyName": {
+ "type": "string",
+ "description": "Optional human-readable name for an API Key. If none provided, default to Email Auth - \u003cTimestamp\u003e"
+ },
+ "expirationSeconds": {
+ "type": "string",
+ "description": "Optional window (in seconds) indicating how long the API Key should last. Default to 30 minutes."
+ },
+ "emailCustomization": {
+ "$ref": "#/definitions/EmailCustomization",
+ "description": "Optional parameters for customizing emails. If not provided, use defaults."
+ }
+ },
+ "required": ["email", "targetPublicKey"]
+ },
+ "EmailAuthRequest": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["ACTIVITY_TYPE_EMAIL_AUTH"]
+ },
+ "timestampMs": {
+ "type": "string",
+ "description": "Timestamp (in milliseconds) of the request, used to verify liveness of user requests."
+ },
+ "organizationId": {
+ "type": "string",
+ "description": "Unique identifier for a given Organization."
+ },
+ "parameters": {
+ "$ref": "#/definitions/EmailAuthIntent"
+ }
+ },
+ "required": ["type", "timestampMs", "organizationId", "parameters"]
+ },
+ "EmailAuthResult": {
+ "type": "object",
+ "properties": {
+ "userId": {
+ "type": "string",
+ "description": "Unique identifier for the authenticating User."
+ },
+ "apiKeyId": {
+ "type": "string",
+ "description": "Unique identifier for the created API key."
+ }
+ },
+ "required": ["userId", "apiKeyId"]
+ },
+ "EmailCustomization": {
+ "type": "object",
+ "properties": {
+ "subject": {
+ "type": "string"
+ },
+ "body": {
+ "type": "string"
+ },
+ "styling": {
+ "type": "string"
+ },
+ "urlPrefix": {
+ "type": "string"
+ }
+ }
+ },
"ExportPrivateKeyIntent": {
"type": "object",
"properties": {
@@ -3112,7 +3230,8 @@
"type": "string",
"enum": [
"FEATURE_NAME_ROOT_USER_EMAIL_RECOVERY",
- "FEATURE_NAME_WEBAUTHN_ORIGINS"
+ "FEATURE_NAME_WEBAUTHN_ORIGINS",
+ "FEATURE_NAME_EMAIL_AUTH"
]
},
"GetActivitiesRequest": {
@@ -3696,6 +3815,9 @@
},
"createSubOrganizationIntentV4": {
"$ref": "#/definitions/CreateSubOrganizationIntentV4"
+ },
+ "emailAuthIntent": {
+ "$ref": "#/definitions/EmailAuthIntent"
}
},
"required": ["createOrganizationIntent"]
@@ -4270,6 +4392,9 @@
},
"createSubOrganizationResultV4": {
"$ref": "#/definitions/CreateSubOrganizationResultV4"
+ },
+ "emailAuthResult": {
+ "$ref": "#/definitions/EmailAuthResult"
}
}
},
From 23bdc79a52783ed06055a5edaf1960316a95432e Mon Sep 17 00:00:00 2001
From: Andrew Min
Date: Mon, 4 Dec 2023 17:25:01 -0500
Subject: [PATCH 2/4] update default duration time
---
docs/getting-started/email-recovery.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/getting-started/email-recovery.md b/docs/getting-started/email-recovery.md
index 4a0802a..68831dc 100644
--- a/docs/getting-started/email-recovery.md
+++ b/docs/getting-started/email-recovery.md
@@ -47,7 +47,7 @@ Authorization for email recovery is based on our usual activity authorization: o
-Important note: recovery credentials automatically expire after **30 minutes** and are overridden when multiple `INIT_USER_EMAIL_RECOVERY` activities target the same user. Only the most recent recovery credential is valid.
+Important note: recovery credentials automatically expire after **15 minutes** and are overridden when multiple `INIT_USER_EMAIL_RECOVERY` activities target the same user. Only the most recent recovery credential is valid.
## Email recovery in your sub-organizations
From 577df55580b0f68999ea87593cc767e789b415ef Mon Sep 17 00:00:00 2001
From: Andrew Min
Date: Mon, 4 Dec 2023 18:07:21 -0500
Subject: [PATCH 3/4] api key tip
---
docs/getting-started/Quickstart.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/getting-started/Quickstart.md b/docs/getting-started/Quickstart.md
index 5e69647..d327691 100644
--- a/docs/getting-started/Quickstart.md
+++ b/docs/getting-started/Quickstart.md
@@ -62,6 +62,8 @@ Navigate to your user page by clicking on "User Details" in the user dropdown me
Click on "Create API keys" and follow the prompts to add the generated public API key. You'll be required to authenticate with the same authenticator used during onboarding. After this succeeds, you should be all set to interact with our API.
+NOTE: if you would like to manually your locally stored public/private API key files (e.g. `key.public`, `key.private`), you will have to save the files without newlines. For example, for VIM, use `:set binary noeol` or `:set binary noendofline` before writing.
+
## Create a Wallet
Wallets are collections of cryptographic key pairs typically used for sending and receiving digital assets. To create one, we need to provide a name:
From cd7ee61befc0fdd59835306d04d89322f254589d Mon Sep 17 00:00:00 2001
From: Andrew Min
Date: Tue, 5 Dec 2023 18:19:07 -0500
Subject: [PATCH 4/4] email auth
---
docs/FAQ.md | 39 +++----
docs/getting-started/Quickstart.md | 2 +-
docs/getting-started/email-auth.md | 94 +++++++++++++++++
.../email-auth-for-sub-organizations.md | 85 +++++++++++++++
.../email-recovery-in-sub-organizations.md | 97 +++++++++---------
static/img/auth_email.png | Bin 0 -> 128615 bytes
.../img/diagrams/email_auth_authorization.png | Bin 0 -> 1148764 bytes
static/img/email_auth_cryptography.png | Bin 0 -> 391428 bytes
static/img/email_auth_steps.png | Bin 0 -> 2341515 bytes
9 files changed, 252 insertions(+), 65 deletions(-)
create mode 100644 docs/getting-started/email-auth.md
create mode 100644 docs/integration-guides/email-auth-for-sub-organizations.md
create mode 100644 static/img/auth_email.png
create mode 100644 static/img/diagrams/email_auth_authorization.png
create mode 100644 static/img/email_auth_cryptography.png
create mode 100644 static/img/email_auth_steps.png
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 0c1bcfa..7d8597f 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -2,6 +2,7 @@
sidebar_position: 8
slug: /faq
---
+
# FAQ
### Why do you require a public / private key pair to access Turnkey API?
@@ -38,26 +39,28 @@ We have limits on the number of resources within a single organization to avoid
Currently, the resource limits within a single organization are as follows:
-| Resource | Maximum number allowed |
-| :---------------------- | :--------------------- |
-| Private keys | 1,000 |
-| Wallets | 100 |
-| Users | 100 |
-| Policies | 100 |
-| Invitations | 100 |
-| Tags | 100 |
-| Authenticators per user | 10 |
-| API keys per user | 10 |
-| Sub-Organizations | unlimited |
+| Resource | Maximum number allowed |
+| :----------------------------- | :--------------------- |
+| Private keys | 1,000 |
+| Wallets | 100 |
+| Users | 100 |
+| Policies | 100 |
+| Invitations | 100 |
+| Tags | 100 |
+| Authenticators per user | 10 |
+| API keys per user (long-lived) | 10 |
+| API keys per user (expiring) | 10 |
+| Sub-Organizations | unlimited |
If you are approaching any of these limits in your implementation and require support, reach out to the Turnkey team ().
### Do you have any rate limits in place in your public API?
Our public API currently limits users to a maximum of 60 RPS (Requests Per Second). Specific headers are returned to indicate current quota:
-* `ratelimit-limit`: indicates the total quota (60)
-* `ratelimit-remaining`: indicates the current quota
-* `x-rate-limit-request-forwarded-for` and `x-rate-limit-request-remote-addr`: echo back your remote IP and forwarded-for IP for debugging purposes
+
+- `ratelimit-limit`: indicates the total quota (60)
+- `ratelimit-remaining`: indicates the current quota
+- `x-rate-limit-request-forwarded-for` and `x-rate-limit-request-remote-addr`: echo back your remote IP and forwarded-for IP for debugging purposes
When rate limits are exceeded, an error with HTTP 429 is returned with the following message: `Too many requests. Please wait and try again in a few seconds`.
@@ -104,16 +107,16 @@ We require a recent timestamp in the `timestampMs` field for each new activity s
Our secure enclaves have their own, independent, secure source of time. We currently require request timestamps to be **less than an hour old**, and **up to 5 minutes in the future**.
-### How do pricing and billing work?
+### How do pricing and billing work?
-Turnkey is priced per signature, i.e. any transaction or raw payload successfully signed by a private key created on Turnkey. Turnkey offers 25 free signatures each month. To execute more than 25 transactions in a given month, you are required to have a credit card on file or active enterprise plan on your account. To upgrade your plan, navigate to Account Settings from the menu in the top right-hand corner in the Turnkey dashboard and follow the instructions.
+Turnkey is priced per signature, i.e. any transaction or raw payload successfully signed by a private key created on Turnkey. Turnkey offers 25 free signatures each month. To execute more than 25 transactions in a given month, you are required to have a credit card on file or active enterprise plan on your account. To upgrade your plan, navigate to Account Settings from the menu in the top right-hand corner in the Turnkey dashboard and follow the instructions.
For more information about pricing and billing, check out the [pricing page](https://www.turnkey.com/pricing).
### Where else can I get help with my Turnkey implementation?
-If you get stuck or have a one-off question, post it to our [developer forum](https://github.com/orgs/tkhq/discussions) or reach out directly to help@turnkey.com. Teams that are looking for more in-depth integration support can upgrade to an Enterprise plan via hello@turnkey.com.
+If you get stuck or have a one-off question, post it to our [developer forum](https://github.com/orgs/tkhq/discussions) or reach out directly to help@turnkey.com. Teams that are looking for more in-depth integration support can upgrade to an Enterprise plan via hello@turnkey.com.
### Is my country supported?
-Turnkey is not currently available to users in any countries currently subject to US OFAC sanctions.
+Turnkey is not currently available to users in any countries currently subject to US OFAC sanctions.
diff --git a/docs/getting-started/Quickstart.md b/docs/getting-started/Quickstart.md
index d327691..90fe3f3 100644
--- a/docs/getting-started/Quickstart.md
+++ b/docs/getting-started/Quickstart.md
@@ -62,7 +62,7 @@ Navigate to your user page by clicking on "User Details" in the user dropdown me
Click on "Create API keys" and follow the prompts to add the generated public API key. You'll be required to authenticate with the same authenticator used during onboarding. After this succeeds, you should be all set to interact with our API.
-NOTE: if you would like to manually your locally stored public/private API key files (e.g. `key.public`, `key.private`), you will have to save the files without newlines. For example, for VIM, use `:set binary noeol` or `:set binary noendofline` before writing.
+NOTE: if you would like to manually your locally stored public/private API key files (e.g. `key.public`, `key.private`), you will have to save the files without newlines (which occupy extra bytes). For example, for VIM, use `:set binary noeol` or `:set binary noendofline` before writing.
## Create a Wallet
diff --git a/docs/getting-started/email-auth.md b/docs/getting-started/email-auth.md
new file mode 100644
index 0000000..bd6c795
--- /dev/null
+++ b/docs/getting-started/email-auth.md
@@ -0,0 +1,94 @@
+---
+sidebar_position: 8
+description: Learn about Email Auth on Turnkey
+slug: /getting-started/email-auth
+---
+
+# Email Auth
+
+Email Auth enables a user to authenticate their Turnkey account via email. In this process, the user is granted an expiring API key that is held in local storage. This expiring API key can be used by the user to access their wallet, similar to a session key. An example utilizing Email Auth for an organization can be found in our SDK repo [here](https://github.com/tkhq/sdk/tree/main/examples/email-auth).
+
+## User Experience
+
+Email auth starts with a new activity posted to Turnkey. This activity has the type `ACTIVITY_TYPE_EMAIL_AUTH` and takes the following as parameters:
+
+- `email`: the email of the user who would like to authenticate. This email must be the email already attached to the user in organization data (i.e., previously approved by the user). This prevents malicious account takeover. If you try to pass a different email address, the activity will fail.
+- `targetPublicKey`: the public key to which the auth credential is encrypted (more on this later)
+- `apiKeyName`: an optional name for the API Key. If none is provided, we will default to `Email Auth - `
+- `expirationSeconds`: an optional window (in seconds) indicating how long the API Key should last. Default to 30 minutes.
+- `emailCustomization`: optional parameters for customizing emails. If not provided, use defaults. This is currently a WIP. 🚧
+
+This activity generates a new API key pair (an "auth credential"), saves the public key in organization data under the target user, and sends an email with the encrypted auth credential:
+
+
+
+
+
+Calling email auth requires proper permissions via policies or being a parent organization. See [Authorization](#authorization) for more details.
+
+## Authorization
+
+Authorization for email auth is based on our usual activity authorization: our [policy engine](../policy-management/Policy-overview.md) controls who can and cannot execute auth-related activities.
+
+- `ACTIVITY_TYPE_EMAIL_AUTH` can be performed by the root user or by any user in an organization if authorized by policy, but **only if the feature is enabled**. The activity can target **any user** in this organization **or any sub-organization user**. The activity will fail if a parent user tries to perform email auth for a sub-organization which has [opted out of this feature](#opting-out-of-email-auth).
+
+
+
+
+
+## Email auth in your sub-organizations
+
+Email auth works well with [sub-organizations](./Sub-Organizations.md).
+
+
+
+If you're looking for a more concrete guide, head to our [Sub-Organization Email Auth implementation guide](../integration-guides/email-auth-for-sub-organizations.md) for more details.
+
+## Email auth in your organization
+
+If you want to use email auth in the context of an organization accessed via our dashboard, first, you must ensure that the organization feature (`FEATURE_NAME_EMAIL_AUTH`) is enabled. Additionally, the user attempting to initiate email auth must have appropriate permissions (via root user status, or via policy).
+
+## Opting out of email auth
+
+Similar to email recovery, depending on your threat model, it may be unacceptable to rely on email as an authentication factor. We envision this to be the case when an organization has a mature set of root users with multiple authenticators, or when a sub-organization "graduates" from one to many redundant passkeys or API keys. When you're ready, you can disable email auth with `ACTIVITY_TYPE_REMOVE_ORGANIZATION_FEATURE` (see Remove [Organization Feature](/api#tag/Features/operation/RemoveOrganizationFeature)). The feature name to remove is `FEATURE_NAME_EMAIL_AUTH`.
+
+If you _never_ want to have email auth enabled for sub-organizations, our `CREATE_SUB_ORGANIZATION` activity takes a `disableEmailAuth` boolean in its parameters. Set it to `true` and the sub-organization will be created without the organization feature.
+
+## Cryptographic details
+
+Note: if the following section looks familiar, it is! It shares the same cryptographic innerworkings as Email Recovery.
+
+Unlike typical email auth functionality, Turnkey's email auth doesn't send unencrypted tokens via emails. This ensures no man-in-the-middle attack can happen: even if the content of the auth email is leaked, an attacker wouldn't be able to decrypt the auth credential. The following diagram summarizes the flow:
+
+
+
+Our email auth flow works by anchoring auth in a **target encryption key** (TEK). This target encryption key is a standard P-256 key pair and can be created in many ways: completely offline, or online inside of script using the web crypto APIs.
+
+The public part of this key pair is passed as a parameter inside of a signed `EMAIL_AUTH` activity. The signature on the activity has to come from a user who is [authorized](#authorization) to initiate email auth.
+
+Our enclave creates a fresh P256 key pair ("auth credential") and encrypts the private key to the recovering user's TEK using the **Hybrid Public Key Encryption standard**, also known as **HPKE** or [RFC 9180](https://datatracker.ietf.org/doc/rfc9180/).
+
+Once the encrypted auth credential is received via email, it's decrypted where the target public key was originally created. The auth credential is then ready to be used to sign an activity, which is then submitted to Turnkey.
+
+## Implementation notes
+
+Users currently have a limit of 10 long-lived API keys, and 10 expiring API keys. In the case that the limit of expiring API keys is breached, the oldest (by creation date) will be discarded.
+
+NOTE: feature must be enabled. For top-level orgs, by default, Email Auth is not enabled. It must be enabled via the `ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE` activity. Here's an example, using our CLI:
+
+```
+turnkey request --host api.turnkey.com --path /public/v1/submit/email_auth --body '{
+ "timestampMs": "'"$(date +%s)"'000",
+ "type": "ACTIVITY_TYPE_SET_ORGANIZATION_FEATURE",
+ "organizationId": "",
+ "parameters": {
+ "name": "FEATURE_NAME_EMAIL_AUTH"
+ }
+}' --organization
+```
+
+Suborgs have Email Auth enabled as a feature by default. It can be conveniently disabled during creation, using the `CreateSubOrganizationIntentV4` activity parameter `disableEmailAuth`.
diff --git a/docs/integration-guides/email-auth-for-sub-organizations.md b/docs/integration-guides/email-auth-for-sub-organizations.md
new file mode 100644
index 0000000..d754a22
--- /dev/null
+++ b/docs/integration-guides/email-auth-for-sub-organizations.md
@@ -0,0 +1,85 @@
+---
+sidebar_position: 3
+description: Learn about Email Auth on Turnkey
+slug: /integration-guides/sub-organization-auth
+---
+# Sub-Organization Email Auth
+
+Email auth is a powerful feature to couple with [sub-organizations](../getting-started/Sub-Organizations.md) for your users. This approach empowers your users to authenticate their Turnkey in a simple way (via email!), while minimizing your involvement: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to.
+
+
+
+## Pre-requisites
+
+Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email auth. Check out our [Quickstart guide](../getting-started/Quickstart.md) if you need help getting started. To allow an API user to initiate email auth, you'll need the following policy in your main organization:
+```json JSON
+{
+ "effect": "EFFECT_ALLOW",
+ "consensus": "approvers.any(user, user.id == '')",
+ "condition": "activity.resource == 'AUTH' && activity.action == 'CREATE'"
+}
+```
+
+## Helper packages
+
+* We have released open-source code to create target encryption keys, decrypt auth credentials, and sign Turnkey activities. We've deployed this a static HTML page hosted on `auth.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the auth credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
+* We have also built a package to help you insert this iframe and interact with it in the context of email auth: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+
+In the rest of this guide we'll assume you are using these helpers.
+
+## Email Auth step-by-step
+
+Here's a diagram summarizing the email auth flow step-by-step ([direct link](/img/email_auth_steps.png)):
+
+
+
+Let's review these steps in detail:
+
+1. User on `yoursite.xyz` clicks "auth", and a new auth UI is shown. We recommend this auth UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to successfully authenticate. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
+ ```js
+ const iframeStamper = new IframeStamper({
+ iframeUrl: "https://auth.turnkey.com",
+ // Configure how the iframe element is inserted on the page
+ iframeContainerId: "your-container",
+ iframeElementId: "turnkey-iframe",
+ });
+
+ // Inserts the iframe in the DOM. This creates the new encryption target key
+ const publicKey = await iframeStamper.init();
+ ```
+
+2. Your code receives the iframe public key and shows the auth form, and the user enters their email address.
+3. Your app can now create and sign a new `EMAIL_AUTH` activity with the user email and the iframe public key in the parameters. Optional arguments include a custom name for the API key, and a specific duration (denoted in seconds) for it. Note: you'll need to retrieve the sub-organization ID based on the user email.
+4. Email is received by the user.
+5. User copies and pastes their auth code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe.
+6. Your app injects the auth code into the iframe for decryption:
+ ```js
+ await iframeStamper.injectCredentialBundle(code);
+ ```
+7. At this point, the user is authenticated! Your app should use [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new activity, e.g. `CREATE_WALLET`:
+ ```js
+ // New client instantiated with our iframe stamper
+ const client = new TurnkeyClient(
+ { baseUrl: "https://api.turnkey.com" },
+ iframeStamper,
+ );
+
+ // Sign and submits the CREATE_WALLET activity
+ const response = await client.recoverUser({
+ type: "ACTIVITY_TYPE_CREATE_WALLET",
+ timestampMs: String(Date.now()),
+ organizationId: authResponse.organizationId,
+ parameters: {
+ walletName: "Default Wallet",
+ accounts: [{
+ curve: "CURVE_SECP256K1",
+ pathFormat: "PATH_FORMAT_BIP32",
+ path: "m/44'/60'/0'/0/0",
+ addressFormat: "ADDRESS_FORMAT_ETHEREUM",
+ }],
+ },
+ });
+ ```
+
+Congrats! You've succcessfully performed Email Auth! 🥳
diff --git a/docs/integration-guides/email-recovery-in-sub-organizations.md b/docs/integration-guides/email-recovery-in-sub-organizations.md
index 1d75d5c..b70c5bc 100644
--- a/docs/integration-guides/email-recovery-in-sub-organizations.md
+++ b/docs/integration-guides/email-recovery-in-sub-organizations.md
@@ -3,6 +3,7 @@ sidebar_position: 2
description: Learn about Email Recovery on Turnkey
slug: /integration-guides/sub-organization-recovery
---
+
# Sub-Organization Recovery
Email recovery shines if you are leveraging [sub-organizations](../getting-started/Sub-Organizations.md) for each of your users. This allows your users to recover their Turnkey account if something goes wrong with their passkeys, and keeps you out of the loop: we engineered this feature to ensure your organization is unable to take over sub-organizations even if it wanted to.
@@ -12,18 +13,19 @@ Our Demo Passkey Wallet application (https://wallet.tx.xyz) has recovery functio
## Pre-requisites
Make sure you have set up your primary Turnkey organization with at least one API user that can programmatically initiate email recovery. Check out our [Quickstart guide](../getting-started/Quickstart.md) if you need help getting started. To allow an API user to initiate email recovery, you'll need the following policy in your main organization:
+
```json JSON
-{
- "effect": "EFFECT_ALLOW",
- "consensus": "approvers.any(user, user.id == '')",
- "condition": "activity.resource == 'RECOVERY' && activity.action == 'CREATE'"
+{
+ "effect": "EFFECT_ALLOW",
+ "consensus": "approvers.any(user, user.id == '')",
+ "condition": "activity.resource == 'RECOVERY' && activity.action == 'CREATE'"
}
```
## Helper packages
-* We have released open-source code to create target encryption keys, decrypt recovery credentials, and sign Turnkey activities. We've deployed this a static HTML page hosted on `recovery.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the recovery credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
-* We have also built a package to help you insert this iframe and interact with it in the context of email recovery: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
+- We have released open-source code to create target encryption keys, decrypt recovery credentials, and sign Turnkey activities. We've deployed this a static HTML page hosted on `recovery.turnkey.com` meant to be embedded as an iframe element (see the code [here](https://github.com/tkhq/frames)). This ensures the recovery credentials are encrypted to keys that your organization doesn't have access to (because they live in the iframe, on a separate domain)
+- We have also built a package to help you insert this iframe and interact with it in the context of email recovery: [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper)
In the rest of this guide we'll assume you are using these helpers.
@@ -38,52 +40,55 @@ Here's a diagram summarizing the email recovery flow step-by-step ([direct link]
Let's review these steps in detail:
1. User on `yoursite.xyz` clicks "recovery", and a new recovery UI is shown. We recommend this recovery UI be a new hosted page of your site or application, which contains language explaining to the user what steps they will need to take next to complete recovery. While the UI is in a loading state your frontend uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to insert a new iframe element:
- ```js
- const iframeStamper = new IframeStamper({
- iframeUrl: "https://recovery.turnkey.com",
- // Configure how the iframe element is inserted on the page
- iframeContainerId: "your-container",
- iframeElementId: "turnkey-iframe",
- });
-
- // Inserts the iframe in the DOM. This creates the new encryption target key
- const publicKey = await iframeStamper.init();
- ```
+
+ ```js
+ const iframeStamper = new IframeStamper({
+ iframeUrl: 'https://recovery.turnkey.com',
+ // Configure how the iframe element is inserted on the page
+ iframeContainerId: 'your-container',
+ iframeElementId: 'turnkey-iframe',
+ });
+
+ // Inserts the iframe in the DOM. This creates the new encryption target key
+ const publicKey = await iframeStamper.init();
+ ```
+
2. Your code receives the iframe public key and shows the recovery form, and the user enters their email address.
3. Your app can now create and sign a new `INIT_USER_EMAIL_RECOVERY` activity with the user email and the iframe public key in the parameters. Note: you'll need to retrieve the sub-organization ID based on the user email.
4. Email is received by the user.
5. User copies and pastes their recovery code into your app. Remember: this code is an encrypted credential which can only be decrypted within the iframe.
6. Your app injects the recovery code into the iframe for decryption:
- ```js
- await iframeStamper.injectRecoveryBundle(code);
- ```
+ ```js
+ await iframeStamper.injectRecoveryBundle(code);
+ ```
7. Your app prompts the user to create a new passkey (using our SDK functionality):
- ```js
- // Creates a new passkey
- let attestation = await getWebAuthnAttestation(...params...)
- ```
+ ```js
+ // Creates a new passkey
+ let attestation = await getWebAuthnAttestation(...params...)
+ ```
8. Your app uses [`@turnkey/iframe-stamper`](https://www.npmjs.com/package/@turnkey/iframe-stamper) to sign a new `RECOVER_USER` activity:
- ```js
- // New client instantiated with our iframe stamper
- const client = new TurnkeyClient(
- { baseUrl: "https://api.turnkey.com" },
- iframeStamper,
- );
-
- // Sign and submits the RECOVER_USER activity
- const response = await client.recoverUser({
- type: "ACTIVITY_TYPE_RECOVER_USER",
- timestampMs: String(Date.now()),
- organizationId: initRecoveryResponse.organizationId,
- parameters: {
- userId: initRecoveryResponse.userId,
- authenticator: {
- authenticatorName: data.authenticatorName,
- challenge: base64UrlEncode(challenge),
- attestation: attestation,
- },
- },
- });
- ```
+
+ ```js
+ // New client instantiated with our iframe stamper
+ const client = new TurnkeyClient(
+ { baseUrl: 'https://api.turnkey.com' },
+ iframeStamper
+ );
+
+ // Sign and submits the RECOVER_USER activity
+ const response = await client.recoverUser({
+ type: 'ACTIVITY_TYPE_RECOVER_USER',
+ timestampMs: String(Date.now()),
+ organizationId: initRecoveryResponse.organizationId,
+ parameters: {
+ userId: initRecoveryResponse.userId,
+ authenticator: {
+ authenticatorName: data.authenticatorName,
+ challenge: base64UrlEncode(challenge),
+ attestation: attestation,
+ },
+ },
+ });
+ ```
Once the `RECOVER_USER` activity is successfully posted, the recovery is complete! If this activity succeeds, your frontend can redirect to login/sign-in or perform crypto signing with the new passkey.
diff --git a/static/img/auth_email.png b/static/img/auth_email.png
new file mode 100644
index 0000000000000000000000000000000000000000..62a04bb96b5d6566093e38339fc192426ab66bde
GIT binary patch
literal 128615
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