Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for non webauthn browsers #90

Merged
merged 13 commits into from
Oct 25, 2023
23 changes: 13 additions & 10 deletions src/components/AddDevice/AddDevice.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createKey } from '@near-js/biometric-ed25519/lib';
import { createKey, isPassKeyAvailable } from '@near-js/biometric-ed25519/lib';
import BN from 'bn.js';
import { sendSignInLinkToEmail } from 'firebase/auth';
import React, { useCallback, useEffect, useState } from 'react';
Expand Down Expand Up @@ -72,14 +72,15 @@ const InputContainer = styled.div`
export const handleCreateAccount = async ({
accountId, email, isRecovery, success_url, failure_url, public_key, contract_id, methodNames
}) => {
const keyPair = await createKey(email);
const publicKeyWebAuthn = keyPair.getPublicKey().toString();
if (!publicKeyWebAuthn) {
throw new Error('No public key found');
const passkeyAvailable = await isPassKeyAvailable();
let publicKeyWebAuthn; let keyPair;
if (passkeyAvailable) {
keyPair = await createKey(email);
publicKeyWebAuthn = keyPair.getPublicKey().toString();
}

const searchParams = new URLSearchParams({
publicKeyFak: publicKeyWebAuthn,
...(publicKeyWebAuthn ? { publicKeyFak: publicKeyWebAuthn } : {}),
...(accountId ? { accountId } : {}),
...(isRecovery ? { isRecovery } : {}),
...(success_url ? { success_url } : {}),
Expand All @@ -89,7 +90,9 @@ export const handleCreateAccount = async ({
...(methodNames ? { methodNames } : {})
});

window.localStorage.setItem(`temp_fastauthflow_${publicKeyWebAuthn}`, keyPair.toString());
if (publicKeyWebAuthn) {
window.localStorage.setItem(`temp_fastauthflow_${publicKeyWebAuthn}`, keyPair.toString());
}

await sendSignInLinkToEmail(firebaseAuth, email, {
url: encodeURI(
Expand All @@ -99,7 +102,7 @@ export const handleCreateAccount = async ({
});
window.localStorage.setItem('emailForSignIn', email);
return {
email, publicKey: publicKeyWebAuthn, accountId, privateKey: keyPair.toString()
email, publicKey: publicKeyWebAuthn, accountId, privateKey: keyPair && keyPair.toString()
};
};

Expand Down Expand Up @@ -146,16 +149,16 @@ function SignInPage() {
methodNames,
});
const newSearchParams = new URLSearchParams({
publicKeyFak,
email,
isRecovery: 'true',
...(publicKeyFak ? { publicKeyFak } : {}),
...(success_url ? { success_url } : {}),
...(failure_url ? { failure_url } : {}),
...(public_key ? { public_key_lak: public_key } : {}),
...(contract_id ? { contract_id } : {}),
...(methodNames ? { methodNames } : {})
});
const hashParams = new URLSearchParams({ privateKey });
const hashParams = new URLSearchParams({ ...(privateKey ? { privateKey } : {}) });
navigate(`/verify-email?${newSearchParams.toString()}#${hashParams.toString()}`);
} catch (error: any) {
console.log(error);
Expand Down
31 changes: 19 additions & 12 deletions src/components/AuthCallback/AuthCallback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const StyledStatusMessage = styled.div`
`;

const onCreateAccount = async ({
keypair,
oidcKeypair,
accessToken,
accountId,
publicKeyFak,
Expand All @@ -43,12 +43,12 @@ const onCreateAccount = async ({
salt: CLAIM_SALT,
oidcToken: accessToken,
shouldHashToken: false,
keypair,
keypair: oidcKeypair,
});
const data = {
near_account_id: accountId,
create_account_options: {
full_access_keys: [publicKeyFak],
full_access_keys: publicKeyFak ? [publicKeyFak] : [],
limited_access_keys: public_key_lak ? [
{
public_key: public_key_lak,
Expand All @@ -60,7 +60,7 @@ const onCreateAccount = async ({
},
oidc_token: accessToken,
user_credentials_frp_signature: signature,
frp_public_key: keypair.getPublicKey().toString(),
frp_public_key: oidcKeypair.getPublicKey().toString(),
};

const headers = new Headers();
Expand Down Expand Up @@ -97,15 +97,17 @@ const onCreateAccount = async ({
throw new Error('Could not find account creation data');
}

window.localStorage.setItem('webauthn_username', email);
if (publicKeyFak) {
window.localStorage.setItem('webauthn_username', email);
}
window.localStorage.removeItem(`temp_fastauthflow_${publicKeyFak}`);

setStatusMessage('Redirecting to app...');

const parsedUrl = new URL(success_url || window.location.origin);
parsedUrl.searchParams.set('account_id', accId);
parsedUrl.searchParams.set('public_key', public_key_lak);
parsedUrl.searchParams.set('all_keys', [public_key_lak, publicKeyFak].join(','));
parsedUrl.searchParams.set('all_keys', (publicKeyFak ? [public_key_lak, publicKeyFak] : [public_key_lak]).join(','));

window.location.replace(parsedUrl.href);
},
Expand All @@ -123,7 +125,6 @@ export const onSignIn = async ({
email,
searchParams,
navigate,
onlyAddLak = false,
gateway,
}) => {
const recoveryPK = await window.fastAuthController.getUserCredential(accessToken);
Expand All @@ -141,6 +142,7 @@ export const onSignIn = async ({
// ? getDeleteKeysAction(existingDevice.publicKeys.filter((key) => key !== publicKeyFak)) : [];

// onlyAddLak will be true if current browser already has a FAK with passkey
const onlyAddLak = !publicKeyFak || publicKeyFak === 'null';
const addKeyActions = onlyAddLak
? getAddLAKAction({
publicKeyLak: public_key_lak,
Expand Down Expand Up @@ -180,15 +182,17 @@ export const onSignIn = async ({

setStatusMessage('Account recovered successfully!');

window.localStorage.setItem('webauthn_username', email);
if (publicKeyFak) {
window.localStorage.setItem('webauthn_username', email);
}
window.localStorage.removeItem(`temp_fastauthflow_${publicKeyFak}`);

setStatusMessage('Redirecting to app...');

const parsedUrl = new URL(success_url || window.location.origin);
parsedUrl.searchParams.set('account_id', accountIds[0]);
parsedUrl.searchParams.set('public_key', public_key_lak);
parsedUrl.searchParams.set('all_keys', [public_key_lak, publicKeyFak].join(','));
parsedUrl.searchParams.set('all_keys', (publicKeyFak ? [public_key_lak, publicKeyFak] : [public_key_lak]).join(','));

if (inIframe()) {
window.open(parsedUrl.href, '_parent');
Expand Down Expand Up @@ -241,16 +245,19 @@ function AuthCallbackPage() {
const { user } = result;
if (user.emailVerified) {
setStatusMessage(isRecovery ? 'Recovering account...' : 'Creating account...');
const keypair = new KeyPairEd25519(privateKey.split(':')[1]);
const keypair = privateKey && new KeyPairEd25519(privateKey.split(':')[1]);

// claim the oidc token
(window as any).fastAuthController = new FastAuthController({
accountId,
networkId
});

await window.fastAuthController.setKey(keypair);
if (keypair) {
await window.fastAuthController.setKey(keypair);
}
await window.fastAuthController.claimOidcToken(user.accessToken);
const oidcKeypair = await window.fastAuthController.getKey(`oidc_keypair_${user.accessToken}`);
(window as any).firestoreController = new FirestoreController();
window.firestoreController.updateUser({
userUid: user.uid,
Expand All @@ -259,7 +266,7 @@ function AuthCallbackPage() {

const callback = isRecovery ? onSignIn : onCreateAccount;
await callback({
keypair,
oidcKeypair,
accessToken: user.accessToken,
accountId,
publicKeyFak,
Expand Down
19 changes: 2 additions & 17 deletions src/components/CreateAccount/CreateAccount.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { ErrorMessage } from '@hookform/error-message';
import { isPassKeyAvailable } from '@near-js/biometric-ed25519';
import * as React from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
Expand Down Expand Up @@ -75,16 +74,16 @@ function CreateAccount() {
});
const newSearchParams = new URLSearchParams({
accountId,
publicKeyFak,
email,
isRecovery: 'false',
...(publicKeyFak ? { publicKeyFak } : {}),
...(success_url ? { success_url } : {}),
...(failure_url ? { failure_url } : {}),
...(public_key ? { public_key_lak: public_key } : {}),
...(contract_id ? { contract_id } : {}),
...(methodNames ? { methodNames } : {})
});
const hashParams = new URLSearchParams({ privateKey });
const hashParams = new URLSearchParams({ ...(privateKey ? { privateKey } : {}) });
navigate(`/verify-email?${newSearchParams.toString()}#${hashParams.toString()}`);
} catch (error: any) {
console.log('error', error);
Expand All @@ -105,20 +104,6 @@ function CreateAccount() {
};

useEffect(() => {
const checkPassKey = async (): Promise<void> => {
const isPasskeyReady = await isPassKeyAvailable();
if (!isPasskeyReady) {
openToast({
title: '',
type: 'INFO',
description:
'Passkey support is required for account creation. Try using an updated version of Chrome or Safari to create an account.',
duration: 5000,
});
}
};
checkPassKey();

const email = searchParams.get('email');
const username = searchParams.get('accountId');

Expand Down
Loading