From f9d96c96950ac7eb671b8b7620e5cdb33c9e5dcd Mon Sep 17 00:00:00 2001
From: Ben Follington <5009316+bfollington@users.noreply.github.com>
Date: Tue, 25 Feb 2025 11:57:37 +1000
Subject: [PATCH 1/9] Rework auth flow
---
.../packages/common-identity/src/pass-key.ts | 33 +-
.../src/contexts/AuthenticationContext.tsx | 90 ++---
.../jumble/src/views/AuthenticationView.tsx | 344 +++++++++++++++---
3 files changed, 367 insertions(+), 100 deletions(-)
diff --git a/typescript/packages/common-identity/src/pass-key.ts b/typescript/packages/common-identity/src/pass-key.ts
index 5c82db47b..20e77249c 100644
--- a/typescript/packages/common-identity/src/pass-key.ts
+++ b/typescript/packages/common-identity/src/pass-key.ts
@@ -15,11 +15,12 @@ const ALGS: PublicKeyCredentialParameters[] = [
{ type: "public-key", alg: -8 }, // ed25519
{ type: "public-key", alg: -7 }, // es256
{ type: "public-key", alg: -257 }, // rs256
-]
+];
export interface PassKeyGetOptions {
- mediation?: "conditional",
- userVerification?: "required" | "preferred" | "discouraged"
+ mediation?: "conditional";
+ userVerification?: "required" | "preferred" | "discouraged";
+ allowCredentials?: PublicKeyCredentialDescriptor[];
}
// A `PassKey` represents an authentication via a WebAuthn authenticator.
@@ -66,7 +67,7 @@ export class PassKey {
// Different data is available within `PublicKeyCredentials` depending
// on whether it was created or retrieved. We need the PRF assertion
// only available on "get" requests, so we don't return a `PassKey` here.
- static async create(name: string, displayName: string): Promise {
+ static async create(name: string, displayName: string): Promise {
const challenge = random(32);
const userId = random(32);
const user = {
@@ -74,7 +75,7 @@ export class PassKey {
name,
displayName,
};
-
+
let publicKey: PublicKeyCredentialCreationOptions = {
challenge,
rp: { id: RP_ID, name: RP },
@@ -93,10 +94,10 @@ export class PassKey {
userVerification: "preferred", // default
},
pubKeyCredParams: ALGS,
- extensions: { prf: { eval: { first: PRF_SALT }}},
+ extensions: { prf: { eval: { first: PRF_SALT } } },
timeout: TIMEOUT,
};
-
+
let result = (await navigator.credentials.create({ publicKey })) as PublicKeyCredential | null;
if (!result) {
throw new Error("common-identity: Could not create passkey");
@@ -105,23 +106,29 @@ export class PassKey {
if (!extResults?.prf?.enabled) {
throw new Error("common-identity: prf extension not supported.");
}
+
+ return result;
}
// Retrieve a `PassKey` from a Web Authn authenticator.
// In browsers, must be called via a user gesture.
- static async get({ userVerification, mediation }: PassKeyGetOptions = {}): Promise {
+ static async get({
+ userVerification,
+ mediation,
+ allowCredentials = [],
+ }: PassKeyGetOptions = {}): Promise {
// Select any credential available with the same `RP_ID`.
- let credential = await navigator.credentials.get({
+ let credential = (await navigator.credentials.get({
publicKey: {
- allowCredentials: [],
+ allowCredentials,
challenge: random(32),
rpId: RP_ID,
userVerification: userVerification ?? "preferred",
- extensions: { prf: { eval: { first: PRF_SALT }}},
+ extensions: { prf: { eval: { first: PRF_SALT } } },
timeout: TIMEOUT,
},
mediation,
- }) as PublicKeyCredential | null;
+ })) as PublicKeyCredential | null;
if (!credential) {
throw new Error("common-identity: Could not create credentials.");
@@ -136,7 +143,7 @@ export class PassKey {
}
return new PassKey(credential);
}
-
+
private getCredentials(): PublicKeyCredential {
return this.credentials;
}
diff --git a/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx b/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
index 140893043..5d4ccd4e4 100644
--- a/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
+++ b/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
@@ -10,9 +10,9 @@ interface AuthenticationContextType {
// The authenticated user/persona.
user: Identity | void;
// Call PassKey registration.
- passkeyRegister: (name: string, displayName: string) => Promise;
+ passkeyRegister: (name: string, displayName: string) => Promise;
// Authenticate the user via passkey.
- passkeyAuthenticate: () => Promise;
+ passkeyAuthenticate: (descriptor?: PublicKeyCredentialDescriptor) => Promise;
// Generate a passphrase for a new user
passphraseRegister: () => Promise;
// Authenticate via passphrase.
@@ -21,7 +21,7 @@ interface AuthenticationContextType {
clearAuthentication: () => Promise;
// Internal: Root key.
root: Identity | void;
- // Internal: Persistent storage for keys.
+ // Internal: Persistent storage for keys.
keyStore: KeyStore | void;
}
@@ -36,19 +36,19 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
useEffect(() => {
let ignore = false;
async function getKeyStoreAndRoot() {
- let keyStore = await KeyStore.open();
- let root = await keyStore.get(ROOT_KEY);
+ const keyStore = await KeyStore.open();
+ const root = await keyStore.get(ROOT_KEY);
if (!ignore) {
setKeyStore(keyStore);
setRoot(root);
}
}
- getKeyStoreAndRoot();
+ getKeyStoreAndRoot();
return () => {
ignore = true;
setKeyStore(undefined);
setRoot(undefined);
- }
+ };
}, []);
// When root changes, update `user` to the default persona
@@ -59,7 +59,7 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
if (!root) {
return;
}
- let user = await root.derive(DEFAULT_PERSONA);
+ const user = await root.derive(DEFAULT_PERSONA);
if (!ignore) {
setUser(user);
}
@@ -68,7 +68,7 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
return () => {
ignore = true;
setUser(undefined);
- }
+ };
}, [root]);
// This calls out to WebAuthn to register a user. The state of whether
@@ -77,44 +77,50 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
//
// Must be called within a user gesture.
const passkeyRegister = useCallback(async (name: string, displayName: string) => {
- return PassKey.create(name, displayName);
+ const credential = await PassKey.create(name, displayName);
+ return credential;
}, []);
-
// This should be called when a passkey (possibly) exists for the user already,
// and no root key has yet been stored (e.g. first login). Subsequent page loads
// should load key from storage and not require this callback.
//
// Must be called within a user gesture.
- const passkeyAuthenticate = useCallback(async () => {
- if (!keyStore) {
- return;
- }
- let passkey = await PassKey.get();
- let root = await passkey.createRootKey();
- await keyStore.set(ROOT_KEY, root);
- setRoot(root);
- }, [keyStore]);
-
+ const passkeyAuthenticate = useCallback(
+ async (key?: PublicKeyCredentialDescriptor) => {
+ if (!keyStore) {
+ return;
+ }
+ // Pass the keyName to PassKey.get() if provided
+ const passkey = await PassKey.get({ allowCredentials: key ? [key] : [] });
+ const root = await passkey.createRootKey();
+ await keyStore.set(ROOT_KEY, root);
+ setRoot(root);
+ },
+ [keyStore],
+ );
const passphraseRegister = useCallback(async () => {
// Don't store the root identity here. Return only the
// mnemonic so that the UI can present guidance on handling
// the private key. The root will be derived from the mnemonic
// on authentication.
- let [_, mnemonic] = await Identity.generateMnemonic();
+ const [_, mnemonic] = await Identity.generateMnemonic();
return mnemonic;
}, []);
- const passphraseAuthenticate = useCallback(async (mnemonic: string) => {
- if (!keyStore) {
- return;
- }
- let root = await Identity.fromMnemonic(mnemonic);
- await keyStore.set(ROOT_KEY, root);
- setRoot(root);
- }, [keyStore]);
-
+ const passphraseAuthenticate = useCallback(
+ async (mnemonic: string) => {
+ if (!keyStore) {
+ return;
+ }
+ const root = await Identity.fromMnemonic(mnemonic);
+ await keyStore.set(ROOT_KEY, root);
+ setRoot(root);
+ },
+ [keyStore],
+ );
+
const clearAuthentication = useCallback(async () => {
if (!keyStore) {
return;
@@ -125,16 +131,18 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
}, [keyStore]);
return (
-
+
{children}
);
diff --git a/typescript/packages/jumble/src/views/AuthenticationView.tsx b/typescript/packages/jumble/src/views/AuthenticationView.tsx
index 2c9d694f5..75744ddef 100644
--- a/typescript/packages/jumble/src/views/AuthenticationView.tsx
+++ b/typescript/packages/jumble/src/views/AuthenticationView.tsx
@@ -1,62 +1,314 @@
import { useAuthentication } from "@/contexts/AuthenticationContext.tsx";
import { useCallback, useRef, useState } from "react";
+import ShapeLogo from "@/assets/ShapeLogo.svg";
+import { useAuthentication } from "@/contexts/AuthenticationContext";
+import { useCallback, useEffect, useState } from "react";
+import {
+ LuArrowLeft,
+ LuKey,
+ LuKeyRound,
+ LuCirclePlus,
+ LuLock,
+ LuTextCursorInput,
+ LuCopy,
+ LuTrash2,
+ LuCheck,
+} from "react-icons/lu";
-const BTN_STYLE=`bg-gray-50 border-2 p-2 w-full flex-1 cursor-pointer`;
-const PW_STYLE=`bg-gray-50 border-2 p-2 w-full flex-1`;
+const BTN_PRIMARY = `w-full px-4 py-2 bg-black text-white hover:bg-gray-800 disabled:opacity-50 flex items-center justify-center gap-2`;
+const LIST_ITEM = `w-full p-2 text-left text-sm border-2 border-black hover:-translate-y-[2px] hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,0.5)] shadow-[1px_1px_0px_0px_rgba(0,0,0,0.3)] transition-all duration-100 ease-in-out cursor-pointer flex items-center gap-2`;
+const INPUT_STYLE = `w-full p-2 border rounded`;
+
+type AuthMethod = "passkey" | "passphrase";
+type AuthFlow = "register" | "login";
+
+interface ErrorCalloutProps {
+ error: string;
+ onDismiss: () => void;
+}
+
+interface SuccessRegistrationProps {
+ mnemonic?: string;
+ onLogin: () => void;
+ method: AuthMethod;
+}
+
+interface StoredCredential {
+ id: string;
+ type: "public-key" | "passphrase";
+ method: AuthMethod;
+}
+
+function ErrorCallout({ error, onDismiss }: ErrorCalloutProps) {
+ return (
+
+ );
+}
+function SuccessRegistration({ mnemonic, onLogin, method }: SuccessRegistrationProps) {
+ const [copied, setCopied] = useState(false);
+
+ const copyToClipboard = () => {
+ if (mnemonic) {
+ navigator.clipboard.writeText(mnemonic);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 750);
+ }
+ };
+
+ return (
+
+ {mnemonic && (
+
+
Your Secret Recovery Phrase:
+
+ +
+
+
+ Please save this phrase securely. You'll need it to log in.
+
+
+ )}
+
+
+ );
+}
export function AuthenticationView() {
- const { user, passkeyRegister, passkeyAuthenticate, passphraseRegister, passphraseAuthenticate } = useAuthentication();
- if (user) {
- throw new Error("Displaying authentication view when already authenticated.");
+ const auth = useAuthentication();
+ const [flow, setFlow] = useState(null);
+ const [method, setMethod] = useState(null);
+ const [error, setError] = useState(null);
+ const [mnemonic, setMnemonic] = useState(null);
+ const [availableMethods, setAvailableMethods] = useState([]);
+ const [storedCredential, setStoredCredential] = useState(() => {
+ const stored = localStorage.getItem("storedCredential");
+ return stored ? JSON.parse(stored) : null;
+ });
+
+ useEffect(() => {
+ const methods: AuthMethod[] = ["passphrase"]; // Passphrase always available
+
+ // Add passkey if available
+ const isPasskeyAvailable =
+ window.location.hostname !== "localhost" && window.PublicKeyCredential !== undefined;
+
+ if (isPasskeyAvailable) {
+ methods.push("passkey");
+ }
+
+ setAvailableMethods(methods);
+ // Only set default method if there's just one option
+ if (methods.length === 1) {
+ setMethod(methods[0]);
+ }
+ }, []);
+
+ async function handleAuth(action: () => Promise) {
+ try {
+ setError(null);
+ return await action();
+ } catch (e) {
+ setError(e instanceof Error ? e.message : "Authentication failed");
+ // Reset both flow and method to return to initial state
+ setFlow(null);
+ setMethod(null);
+ }
}
- const [mnemonic, setMnemonic] = useState("");
- const passphraseInput = useRef(null);
+ const handleRegister = useCallback(
+ async (selectedMethod: string) => {
+ if (selectedMethod === "passkey") {
+ const credential = await handleAuth(() =>
+ auth.passkeyRegister("Common Tools User", "commontoolsuser"),
+ );
+ if (!credential) throw new Error("Credential not found");
- const createMnemonic = useCallback(async () => {
- setMnemonic(await passphraseRegister());
+ const storedCred: StoredCredential = {
+ id: credential.id,
+ type: "public-key",
+ method: "passkey",
+ };
+ localStorage.setItem("storedCredential", JSON.stringify(storedCred));
+ } else {
+ const mnemonic = await auth.passphraseRegister();
+ // Store passphrase credential info
+ const storedCred: StoredCredential = {
+ id: crypto.randomUUID(), // Generate a unique ID for the passphrase credential
+ type: "passphrase",
+ method: "passphrase",
+ };
+ localStorage.setItem("storedCredential", JSON.stringify(storedCred));
+ setMnemonic(mnemonic);
+ }
+ },
+ [handleAuth, auth],
+ );
+
+ const handleLogin = useCallback(
+ async (selectedMethod: string, passphrase?: string) => {
+ if (selectedMethod === "passkey") {
+ if (storedCredential && storedCredential.type == "public-key") {
+ const credentialDescriptor: PublicKeyCredentialDescriptor = {
+ id: Uint8Array.from(atob(storedCredential.id), (c) => c.charCodeAt(0)),
+ type: storedCredential.type,
+ };
+ await handleAuth(() => auth.passkeyAuthenticate(credentialDescriptor));
+ } else {
+ await handleAuth(() => auth.passkeyAuthenticate());
+ }
+ } else if (passphrase) {
+ await handleAuth(() => auth.passphraseAuthenticate(passphrase));
+ }
+ },
+ [storedCredential, handleAuth, auth],
+ );
+
+ const handleMethodSelect = useCallback(
+ async (selectedMethod: AuthMethod) => {
+ debugger;
+ setMethod(selectedMethod);
+ if (flow === "register") {
+ await handleRegister(selectedMethod);
+ } else {
+ await handleLogin(selectedMethod);
+ }
+ },
+ [flow, setMethod, handleRegister, handleLogin],
+ );
+
+ const clearStoredCredential = useCallback(() => {
+ localStorage.removeItem("storedCredential");
+ setStoredCredential(null);
}, []);
- const authWithMnemonicInput = useCallback(async () => {
- if (passphraseInput.current == null) { return; }
- let passphrase = (passphraseInput.current as HTMLInputElement).value;
- await passphraseAuthenticate(passphrase);
- setMnemonic("");
- }, [passphraseInput]);
-
- const authWithMnemonicState = useCallback(async () => {
- let passphrase = mnemonic;
- setMnemonic("");
- await passphraseAuthenticate(passphrase);
- }, [mnemonic]);
-
- if (mnemonic) {
- return (
-
-
-
Your Secret Recovery Key
-
-
-
-
- )
+ if (auth.user) {
+ throw new Error("Already authenticated");
}
return (
-
-
-
via Passkey
-
-
+
+
+
-
-
via Passphrase
-
-
-
+
+ {error &&
setError(null)} />}
+
+ {mnemonic ? (
+ {
+ setMnemonic(null);
+ setFlow("login");
+ }}
+ />
+ ) : flow === null ? (
+
+ {storedCredential ? (
+ <>
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+ >
+ )}
+
+ ) : flow !== null && method === null ? (
+
+
{flow === "login" ? "Login with" : "Register with"}
+ {availableMethods.map((m) => (
+
+ ))}
+
+
+ ) : method === "passphrase" ? (
+
+ {flow === "register" ? (
+
+ ) : (
+
+ )}
+
+
+ ) : null}
);
From edf2aef848d381cdd41f62cbeb72712817b8abc9 Mon Sep 17 00:00:00 2001
From: Ben Follington <5009316+bfollington@users.noreply.github.com>
Date: Tue, 25 Feb 2025 12:31:24 +1000
Subject: [PATCH 2/9] Expose id from PassKey class
---
typescript/packages/common-identity/src/pass-key.ts | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/typescript/packages/common-identity/src/pass-key.ts b/typescript/packages/common-identity/src/pass-key.ts
index 20e77249c..c310b7314 100644
--- a/typescript/packages/common-identity/src/pass-key.ts
+++ b/typescript/packages/common-identity/src/pass-key.ts
@@ -32,6 +32,10 @@ export class PassKey {
this.credentials = credentials;
}
+ id() {
+ return this.credentials.id;
+ }
+
// Generate a root key from a `PassKey`.
// A root key identity is deterministically derived from a `PassKey`'s
// PRF output, a 32-byte hash, which is used as ed25519 key material.
@@ -67,7 +71,7 @@ export class PassKey {
// Different data is available within `PublicKeyCredentials` depending
// on whether it was created or retrieved. We need the PRF assertion
// only available on "get" requests, so we don't return a `PassKey` here.
- static async create(name: string, displayName: string): Promise
{
+ static async create(name: string, displayName: string): Promise {
const challenge = random(32);
const userId = random(32);
const user = {
@@ -107,7 +111,7 @@ export class PassKey {
throw new Error("common-identity: prf extension not supported.");
}
- return result;
+ return new PassKey(result);
}
// Retrieve a `PassKey` from a Web Authn authenticator.
From aed155e4daf461044798c83b623b25b77693f04c Mon Sep 17 00:00:00 2001
From: Ben Follington <5009316+bfollington@users.noreply.github.com>
Date: Tue, 25 Feb 2025 12:31:45 +1000
Subject: [PATCH 3/9] Testing every permutation of methods and sequences
---
.../src/contexts/AuthenticationContext.tsx | 7 +-
.../packages/jumble/src/utils/credentials.ts | 46 ++++
.../jumble/src/views/AuthenticationView.tsx | 211 +++++++++++-------
3 files changed, 182 insertions(+), 82 deletions(-)
create mode 100644 typescript/packages/jumble/src/utils/credentials.ts
diff --git a/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx b/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
index 5d4ccd4e4..73b44171d 100644
--- a/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
+++ b/typescript/packages/jumble/src/contexts/AuthenticationContext.tsx
@@ -10,9 +10,9 @@ interface AuthenticationContextType {
// The authenticated user/persona.
user: Identity | void;
// Call PassKey registration.
- passkeyRegister: (name: string, displayName: string) => Promise;
+ passkeyRegister: (name: string, displayName: string) => Promise;
// Authenticate the user via passkey.
- passkeyAuthenticate: (descriptor?: PublicKeyCredentialDescriptor) => Promise;
+ passkeyAuthenticate: (descriptor?: PublicKeyCredentialDescriptor) => Promise;
// Generate a passphrase for a new user
passphraseRegister: () => Promise;
// Authenticate via passphrase.
@@ -89,13 +89,14 @@ export const AuthenticationProvider: React.FC<{ children: React.ReactNode }> = (
const passkeyAuthenticate = useCallback(
async (key?: PublicKeyCredentialDescriptor) => {
if (!keyStore) {
- return;
+ throw new Error("Key store not initialized");
}
// Pass the keyName to PassKey.get() if provided
const passkey = await PassKey.get({ allowCredentials: key ? [key] : [] });
const root = await passkey.createRootKey();
await keyStore.set(ROOT_KEY, root);
setRoot(root);
+ return passkey;
},
[keyStore],
);
diff --git a/typescript/packages/jumble/src/utils/credentials.ts b/typescript/packages/jumble/src/utils/credentials.ts
new file mode 100644
index 000000000..6c4d617ec
--- /dev/null
+++ b/typescript/packages/jumble/src/utils/credentials.ts
@@ -0,0 +1,46 @@
+export interface StoredCredential {
+ id: string;
+ type: "public-key" | "passphrase";
+ method: "passkey" | "passphrase";
+}
+
+export function getStoredCredential(): StoredCredential | null {
+ const stored = localStorage.getItem("storedCredential");
+ return stored ? JSON.parse(stored) : null;
+}
+
+export function saveCredential(credential: StoredCredential): void {
+ localStorage.setItem("storedCredential", JSON.stringify(credential));
+}
+
+export function clearStoredCredential(): void {
+ localStorage.removeItem("storedCredential");
+}
+
+export function createPasskeyCredential(id: string): StoredCredential {
+ return {
+ id,
+ type: "public-key",
+ method: "passkey",
+ };
+}
+
+export function createPassphraseCredential(): StoredCredential {
+ return {
+ id: crypto.randomUUID(),
+ type: "passphrase",
+ method: "passphrase",
+ };
+}
+
+export function getPublicKeyCredentialDescriptor(
+ storedCredential: StoredCredential | null,
+): PublicKeyCredentialDescriptor | undefined {
+ if (storedCredential?.type === "public-key") {
+ return {
+ id: Uint8Array.from(atob(storedCredential.id), (c) => c.charCodeAt(0)),
+ type: "public-key" as PublicKeyCredentialType,
+ };
+ }
+ return undefined;
+}
diff --git a/typescript/packages/jumble/src/views/AuthenticationView.tsx b/typescript/packages/jumble/src/views/AuthenticationView.tsx
index 75744ddef..9e5315840 100644
--- a/typescript/packages/jumble/src/views/AuthenticationView.tsx
+++ b/typescript/packages/jumble/src/views/AuthenticationView.tsx
@@ -14,10 +14,18 @@ import {
LuTrash2,
LuCheck,
} from "react-icons/lu";
+import {
+ type StoredCredential,
+ getStoredCredential,
+ saveCredential,
+ clearStoredCredential,
+ createPasskeyCredential,
+ createPassphraseCredential,
+ getPublicKeyCredentialDescriptor,
+} from "@/utils/credentials";
const BTN_PRIMARY = `w-full px-4 py-2 bg-black text-white hover:bg-gray-800 disabled:opacity-50 flex items-center justify-center gap-2`;
const LIST_ITEM = `w-full p-2 text-left text-sm border-2 border-black hover:-translate-y-[2px] hover:shadow-[4px_4px_0px_0px_rgba(0,0,0,0.5)] shadow-[1px_1px_0px_0px_rgba(0,0,0,0.3)] transition-all duration-100 ease-in-out cursor-pointer flex items-center gap-2`;
-const INPUT_STYLE = `w-full p-2 border rounded`;
type AuthMethod = "passkey" | "passphrase";
type AuthFlow = "register" | "login";
@@ -31,12 +39,7 @@ interface SuccessRegistrationProps {
mnemonic?: string;
onLogin: () => void;
method: AuthMethod;
-}
-
-interface StoredCredential {
- id: string;
- type: "public-key" | "passphrase";
- method: AuthMethod;
+ credentialId?: string;
}
function ErrorCallout({ error, onDismiss }: ErrorCalloutProps) {
@@ -49,7 +52,13 @@ function ErrorCallout({ error, onDismiss }: ErrorCalloutProps) {
);
}
-function SuccessRegistration({ mnemonic, onLogin, method }: SuccessRegistrationProps) {
+
+function SuccessRegistration({
+ mnemonic,
+ onLogin,
+ method,
+ credentialId,
+}: SuccessRegistrationProps) {
const [copied, setCopied] = useState(false);
const copyToClipboard = () => {
@@ -62,29 +71,38 @@ function SuccessRegistration({ mnemonic, onLogin, method }: SuccessRegistrationP
return (
- {mnemonic && (
+ {method === "passkey" ? (
-
Your Secret Recovery Phrase:
-
- -
-
-
- Please save this phrase securely. You'll need it to log in.
-
+
Passkey successfully registered!
+ {credentialId && (
+
Key ID: ...{credentialId.slice(-4)}
+ )}
+ ) : (
+ mnemonic && (
+
+
Your Secret Recovery Phrase:
+
+
+
+
+
+ Please save this phrase securely. You'll need it to log in.
+
+
+ )
)}