Skip to content

Commit

Permalink
Added SMS login
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtisassad committed Nov 20, 2024
1 parent 84ce500 commit 2071f2c
Show file tree
Hide file tree
Showing 12 changed files with 191 additions and 7 deletions.
1 change: 1 addition & 0 deletions libs/shared/src/types/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export enum WalletSsoSource {
Apple = 'apple',
Email = 'email',
Farcaster = 'farcaster',
SMS = 'SMS',
Unknown = 'unknown', // address created after we launched SSO, before we started recording WalletSsoSource
}

Expand Down
25 changes: 21 additions & 4 deletions packages/commonwealth/client/scripts/controllers/app/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,17 +318,19 @@ async function constructMagic(isCosmos: boolean, chain?: string) {

export async function startLoginWithMagicLink({
email,
phoneNumber,
provider,
chain,
isCosmos,
}: {
email?: string;
phoneNumber?: string;
provider?: WalletSsoSource;
chain?: string;
isCosmos: boolean;
}) {
if (!email && !provider)
throw new Error('Must provide email or SSO provider');
if (!email && !phoneNumber && !provider)
throw new Error('Must provide email or SMS or SSO provider');
const magic = await constructMagic(isCosmos, chain);

if (email) {
Expand All @@ -348,6 +350,18 @@ export async function startLoginWithMagicLink({
walletSsoSource: WalletSsoSource.Farcaster,
});

return { bearer, address };
} else if (phoneNumber) {
const bearer = await magic.auth.loginWithSMS({
phoneNumber,
showUI: true,
});

const { address } = await handleSocialLoginCallback({
bearer,
walletSsoSource: WalletSsoSource.SMS,
});

return { bearer, address };
} else {
localStorage.setItem('magic_provider', provider!);
Expand Down Expand Up @@ -416,12 +430,15 @@ export async function handleSocialLoginCallback({
}
const isCosmos = desiredChain?.base === ChainBase.CosmosSDK;
const magic = await constructMagic(isCosmos, desiredChain?.id);
const isEmail = walletSsoSource === WalletSsoSource.Email;

// Code up to this line might run multiple times because of extra calls to useEffect().
// Those runs will be rejected because getRedirectResult purges the browser search param.
let profileMetadata, magicAddress;
if (isEmail || walletSsoSource === WalletSsoSource.Farcaster) {
if (
walletSsoSource === WalletSsoSource.Email ||
walletSsoSource === WalletSsoSource.Farcaster ||
walletSsoSource === WalletSsoSource.SMS
) {
const metadata = await magic.user.getMetadata();
profileMetadata = { username: null };

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,13 @@ export const AUTH_TYPES: AuthTypesList = {
},
label: 'Email',
},
SMS: {
icon: {
name: 'SMS',
isCustom: true,
},
label: 'SMS',
},
farcaster: {
icon: {
name: 'farcaster',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export type AuthSSOs =
| 'github'
| 'apple'
| 'email'
| 'farcaster';
| 'farcaster'
| 'SMS';
export type CosmosWallets = 'keplr' | 'leap';
export type SubstrateWallets = 'polkadot';
export type SolanaWallets = 'phantom';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
CaretUp,
ChatCenteredDots,
ChatDots,
ChatText,
Chats,
Check,
CheckCircle,
Expand Down Expand Up @@ -325,6 +326,7 @@ export const customIconLookup = {
x: CustomIcons.CWX, // twitter
apple: CustomIcons.CWApple,
farcaster: CustomIcons.CWFarcaster,
SMS: withPhosphorIcon(ChatText),
};

export type IconName = keyof typeof iconLookup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { EVMWalletsSubModal } from './EVMWalletsSubModal';
import { EmailForm } from './EmailForm';
import { MobileWalletConfirmationSubModal } from './MobileWalletConfirmationSubModal';
import './ModalBase.scss';
import { SMSForm } from './SMSForm';

const MODAL_COPY = {
[AuthModalType.CreateAccount]: {
Expand Down Expand Up @@ -58,6 +59,7 @@ const SSO_OPTIONS: AuthSSOs[] = [
'github',
'email',
'farcaster',
'SMS',
] as const;

/**
Expand Down Expand Up @@ -98,9 +100,11 @@ const ModalBase = ({
useState(false);
const [isAuthenticatingWithEmail, setIsAuthenticatingWithEmail] =
useState(false);
const [isAuthenticatingWithSMS, setIsAuthenticatingWithSMS] = useState(false);

const handleClose = async () => {
setIsAuthenticatingWithEmail(false);
setIsAuthenticatingWithSMS(false);
setIsEVMWalletsModalVisible(false);
isWalletConnectEnabled &&
(await onResetWalletConnect().catch(console.error));
Expand All @@ -119,6 +123,7 @@ const ModalBase = ({
isMobileWalletVerificationStep,
onResetWalletConnect,
onEmailLogin,
onSMSLogin,
onWalletSelect,
onSocialLogin,
onVerifyMobileWalletSignature,
Expand Down Expand Up @@ -239,6 +244,10 @@ const ModalBase = ({
setIsAuthenticatingWithEmail(true);
return;
}
if (option === 'SMS') {
setIsAuthenticatingWithSMS(true);
return;
}

// if any wallet option is selected
if (activeTabIndex === 0) {
Expand Down Expand Up @@ -331,11 +340,13 @@ const ModalBase = ({
)}

{/*
If email option is selected don't render SSO's list,
If email or SMS option is selected don't render SSO's list,
else render wallets/SSO's list based on activeTabIndex
*/}
{(activeTabIndex === 0 ||
(activeTabIndex === 1 && !isAuthenticatingWithEmail)) &&
(activeTabIndex === 1 &&
!isAuthenticatingWithEmail &&
!isAuthenticatingWithSMS)) &&
tabsList[activeTabIndex].options.map(renderAuthButton)}

{/* If email option is selected from the SSO's list, show email form */}
Expand All @@ -347,6 +358,15 @@ const ModalBase = ({
onSubmit={async ({ email }) => await onEmailLogin(email)}
/>
)}
{/* If SMS option is selected from the SSO's list, show SMS form */}
{activeTabIndex === 1 && isAuthenticatingWithSMS && (
<SMSForm
isLoading={isMagicLoading}
onCancel={() => setIsAuthenticatingWithSMS(false)}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
onSubmit={async ({ SMS }) => await onSMSLogin(SMS)}
/>
)}
</section>
</>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@import '../../../../../../../styles/shared';

.SMSForm {
display: flex;
flex-direction: column;
gap: 12px;
width: 100%;
padding: 0 4px;

.LoadingSpinner {
margin: auto;
}

.action-btns {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;

.btn-border {
margin: 0 !important;

@include extraSmall {
width: 100% !important;

button {
width: 100% !important;
}
}

&:focus-within {
border-width: 1px !important;
padding: 0px !important;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import CWCircleMultiplySpinner from 'views/components/component_kit/new_designs/CWCircleMultiplySpinner';
import { CWButton } from '../../../../../components/component_kit/new_designs/CWButton';
import { CWForm } from '../../../../../components/component_kit/new_designs/CWForm';
import { CWTextInput } from '../../../../../components/component_kit/new_designs/CWTextInput';
import './SMSForm.scss';
import { SMSValidationSchema } from './validation';

type SMSFormProps = {
onCancel: () => void;
onSubmit: (values: { SMS: string }) => void;
isLoading?: boolean;
};

const SMSForm = ({ onSubmit, onCancel, isLoading }: SMSFormProps) => {
return (
<CWForm
className="SMSForm"
validationSchema={SMSValidationSchema}
onSubmit={!isLoading ? onSubmit : () => {}}
>
{isLoading ? (
<CWCircleMultiplySpinner />
) : (
<>
<CWTextInput
fullWidth
hookToForm
name="SMS"
label="Phone number"
placeholder="Phone number"
/>
<div className="action-btns">
<CWButton
type="button"
onClick={onCancel}
buttonType="tertiary"
label="Back to sign in options"
disabled={isLoading}
/>
<CWButton
type="submit"
buttonWidth="wide"
buttonType="primary"
label="Sign in with Magic"
disabled={isLoading}
/>
</div>
</>
)}
</CWForm>
);
};

export { SMSForm };
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SMSForm } from './SMSForm';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from 'zod';

const SMSValidationSchema = z.object({
SMS: z.string(),
});

export { SMSValidationSchema };
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type Wallet = IWebWallet<any>;
const useAuthentication = (props: UseAuthenticationProps) => {
const [username, setUsername] = useState<string>(DEFAULT_NAME);
const [email, setEmail] = useState<string>();
const [SMS, setSMS] = useState<string>();
const [wallets, setWallets] = useState<Array<Wallet>>();
const [selectedWallet, setSelectedWallet] = useState<Wallet>();
const [primaryAccount, setPrimaryAccount] = useState<Account>();
Expand Down Expand Up @@ -148,6 +149,38 @@ const useAuthentication = (props: UseAuthenticationProps) => {
};

// Handles Magic Link Login
const onSMSLogin = async (phoneNumber = '') => {
const tempSMSToUse = phoneNumber || SMS;
setSMS(tempSMSToUse);

setIsMagicLoading(true);

if (!phoneNumber) {
notifyError('Please enter a valid phone number.');
setIsMagicLoading(false);
return;
}

try {
const isCosmos = app.chain?.base === ChainBase.CosmosSDK;
const { address: magicAddress } = await startLoginWithMagicLink({
phoneNumber: tempSMSToUse,
isCosmos,
chain: app.chain?.id,
});
setIsMagicLoading(false);

await handleSuccess(magicAddress, isNewlyCreated);
props?.onModalClose?.();

trackLoginEvent('SMS', true);
} catch (e) {
notifyError(`Error authenticating with SMS`);
console.error(`Error authenticating with SMS: ${e}`);
setIsMagicLoading(false);
}
};

const onEmailLogin = async (emailToUse = '') => {
const tempEmailToUse = emailToUse || email;
setEmail(tempEmailToUse);
Expand Down Expand Up @@ -560,8 +593,10 @@ const useAuthentication = (props: UseAuthenticationProps) => {
onWalletSelect,
onResetWalletConnect,
onEmailLogin,
onSMSLogin,
onSocialLogin,
setEmail,
setSMS,
onVerifyMobileWalletSignature,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const AuthButtonsShowcase = () => {
<AuthButton type="x" />
<AuthButton type="email" />
<AuthButton type="farcaster" />
<AuthButton type="SMS" />
</div>
<CWText type="h5">Disabled</CWText>
<div className="flex-row">
Expand Down

0 comments on commit 2071f2c

Please sign in to comment.