Skip to content

Commit 379bf0a

Browse files
committed
Add totp scanner
1 parent 10bd901 commit 379bf0a

8 files changed

Lines changed: 1269 additions & 12169 deletions

File tree

example/package-lock.json

Lines changed: 166 additions & 152 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package-lock.json

Lines changed: 893 additions & 11968 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
}
4949
],
5050
"devDependencies": {
51-
"@babel/core": "^7.20.2",
51+
"@babel/core": "^7.23.2",
5252
"@size-limit/preset-small-lib": "^8.1.0",
5353
"@types/react": "^18.0.25",
5454
"@types/react-dom": "^18.0.9",
@@ -61,10 +61,10 @@
6161
"rollup-plugin-postcss": "^4.0.2",
6262
"size-limit": "^8.1.0",
6363
"tsdx": "^0.14.1",
64-
"tslib": "^2.4.1",
65-
"typescript": "^4.9.3"
64+
"tslib": "^2.6.2",
65+
"typescript": "^5.2.2"
6666
},
6767
"dependencies": {
68-
"@authorizerdev/authorizer-js": "^1.2.6"
68+
"@authorizerdev/authorizer-js": "^1.2.14"
6969
}
7070
}

src/components/AuthorizerBasicAuthLogin.tsx

Lines changed: 107 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,26 @@ import { StyledButton, StyledFooter, StyledLink } from '../styledComponents';
88
import { isValidEmail } from '../utils/validations';
99
import { Message } from './Message';
1010
import { AuthorizerVerifyOtp } from './AuthorizerVerifyOtp';
11-
import { OtpDataType } from '../types';
11+
import { OtpDataType, TotpDataType } from '../types';
12+
import { AuthorizerTOTPScanner } from './AuthorizerTOTPScanner';
1213

1314
const initOtpData: OtpDataType = {
1415
isScreenVisible: false,
1516
email: '',
17+
phone_number: '',
18+
};
19+
20+
const initTotpData: TotpDataType = {
21+
isScreenVisible: false,
22+
email: '',
23+
phone_number: '',
24+
authenticator_scanner_image: '',
25+
authenticator_secret: '',
26+
authenticator_recovery_codes: [],
1627
};
1728

1829
interface InputDataType {
19-
email: string | null;
30+
email_or_phone_number: string | null;
2031
password: string | null;
2132
}
2233

@@ -29,12 +40,13 @@ export const AuthorizerBasicAuthLogin: FC<{
2940
const [error, setError] = useState(``);
3041
const [loading, setLoading] = useState(false);
3142
const [otpData, setOtpData] = useState<OtpDataType>({ ...initOtpData });
43+
const [totpData, setTotpData] = useState<TotpDataType>({ ...initTotpData });
3244
const [formData, setFormData] = useState<InputDataType>({
33-
email: null,
45+
email_or_phone_number: null,
3446
password: null,
3547
});
3648
const [errorData, setErrorData] = useState<InputDataType>({
37-
email: null,
49+
email_or_phone_number: null,
3850
password: null,
3951
});
4052
const { setAuthData, config, authorizerRef } = useAuthorizer();
@@ -48,7 +60,8 @@ export const AuthorizerBasicAuthLogin: FC<{
4860
setLoading(true);
4961
try {
5062
const data: LoginInput = {
51-
email: formData.email || '',
63+
email: formData.email_or_phone_number || '',
64+
phone_number: formData.email_or_phone_number || '',
5265
password: formData.password || '',
5366
};
5467
if (urlProps?.scope) {
@@ -63,11 +76,34 @@ export const AuthorizerBasicAuthLogin: FC<{
6376
}
6477

6578
const res = await authorizerRef.login(data);
66-
67-
if (res && res?.should_show_email_otp_screen) {
79+
// if totp is enabled for the first time show totp screen with scanner
80+
if (
81+
res &&
82+
res.should_show_totp_screen &&
83+
res.authenticator_scanner_image &&
84+
res.authenticator_secret &&
85+
res.authenticator_recovery_codes
86+
) {
87+
setTotpData({
88+
isScreenVisible: true,
89+
email: data.email || ``,
90+
phone_number: data.phone_number || ``,
91+
authenticator_scanner_image: res.authenticator_scanner_image,
92+
authenticator_secret: res.authenticator_secret,
93+
authenticator_recovery_codes: res.authenticator_recovery_codes,
94+
});
95+
return;
96+
}
97+
if (
98+
res &&
99+
(res?.should_show_email_otp_screen ||
100+
res?.should_show_mobile_otp_screen ||
101+
res?.should_show_totp_screen)
102+
) {
68103
setOtpData({
69104
isScreenVisible: true,
70-
email: data.email,
105+
email: data.email || ``,
106+
phone_number: data.phone_number || ``,
71107
});
72108
return;
73109
}
@@ -101,14 +137,23 @@ export const AuthorizerBasicAuthLogin: FC<{
101137
};
102138

103139
useEffect(() => {
104-
if (formData.email === '') {
105-
setErrorData({ ...errorData, email: 'Email is required' });
106-
} else if (formData.email && !isValidEmail(formData.email)) {
107-
setErrorData({ ...errorData, email: 'Please enter valid email' });
140+
if (formData.email_or_phone_number === '') {
141+
setErrorData({
142+
...errorData,
143+
email_or_phone_number: 'Email OR Phone Number is required',
144+
});
145+
} else if (
146+
formData.email_or_phone_number &&
147+
!isValidEmail(formData.email_or_phone_number)
148+
) {
149+
setErrorData({
150+
...errorData,
151+
email_or_phone_number: 'Please enter valid email',
152+
});
108153
} else {
109-
setErrorData({ ...errorData, email: null });
154+
setErrorData({ ...errorData, email_or_phone_number: null });
110155
}
111-
}, [formData.email]);
156+
}, [formData.email_or_phone_number]);
112157

113158
useEffect(() => {
114159
if (formData.password === '') {
@@ -118,12 +163,39 @@ export const AuthorizerBasicAuthLogin: FC<{
118163
}
119164
}, [formData.password]);
120165

121-
return otpData.isScreenVisible ? (
122-
<AuthorizerVerifyOtp
123-
{...{ setView, onLogin, email: otpData.email }}
124-
urlProps={urlProps}
125-
/>
126-
) : (
166+
if (totpData.isScreenVisible) {
167+
return (
168+
<AuthorizerTOTPScanner
169+
{...{
170+
setView,
171+
onLogin,
172+
email: totpData.email || ``,
173+
phone_number: totpData.phone_number || ``,
174+
authenticator_scanner_image: totpData.authenticator_scanner_image,
175+
authenticator_secret: totpData.authenticator_secret,
176+
authenticator_recovery_codes:
177+
totpData.authenticator_recovery_codes || [],
178+
}}
179+
urlProps={urlProps}
180+
/>
181+
);
182+
}
183+
184+
if (otpData.isScreenVisible) {
185+
return (
186+
<AuthorizerVerifyOtp
187+
{...{
188+
setView,
189+
onLogin,
190+
email: otpData.email || ``,
191+
phone_number: otpData.phone_number || ``,
192+
}}
193+
urlProps={urlProps}
194+
/>
195+
);
196+
}
197+
198+
return (
127199
<>
128200
{error && (
129201
<Message type={MessageType.Error} text={error} onClose={onErrorClose} />
@@ -138,19 +210,23 @@ export const AuthorizerBasicAuthLogin: FC<{
138210
<span>* </span>Email
139211
</label>
140212
<input
141-
name="email"
142-
id="authorizer-login-email"
213+
name="email_or_phone_number"
214+
id="authorizer-login-email-or-phone-number"
143215
className={`${styles['form-input-field']} ${
144-
errorData.email ? styles['input-error-content'] : null
216+
errorData.email_or_phone_number
217+
? styles['input-error-content']
218+
: null
145219
}`}
146-
placeholder="eg. foo@bar.com"
147-
type="email"
148-
value={formData.email || ''}
149-
onChange={(e) => onInputChange('email', e.target.value)}
220+
placeholder="eg. foo@bar.com / +919999999999"
221+
type="text"
222+
value={formData.email_or_phone_number || ''}
223+
onChange={(e) =>
224+
onInputChange('email_or_phone_number', e.target.value)
225+
}
150226
/>
151-
{errorData.email && (
227+
{errorData.email_or_phone_number && (
152228
<div className={styles['form-input-error']}>
153-
{errorData.email}
229+
{errorData.email_or_phone_number}
154230
</div>
155231
)}
156232
</div>
@@ -182,9 +258,9 @@ export const AuthorizerBasicAuthLogin: FC<{
182258
<StyledButton
183259
type="submit"
184260
disabled={
185-
!!errorData.email ||
261+
!!errorData.email_or_phone_number ||
186262
!!errorData.password ||
187-
!formData.email ||
263+
!formData.email_or_phone_number ||
188264
!formData.password ||
189265
loading
190266
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import React, { FC, useState } from 'react';
2+
import { StyledButton } from '../styledComponents';
3+
import { ButtonAppearance, Views } from '../constants';
4+
import { AuthorizerVerifyOtp } from './AuthorizerVerifyOtp';
5+
6+
export const AuthorizerTOTPScanner: FC<{
7+
setView?: (v: Views) => void;
8+
onLogin?: (data: any) => void;
9+
email?: string;
10+
phone_number?: string;
11+
urlProps?: Record<string, any>;
12+
authenticator_scanner_image: string;
13+
authenticator_secret: string;
14+
authenticator_recovery_codes: string[];
15+
}> = ({
16+
setView,
17+
onLogin,
18+
email,
19+
phone_number,
20+
authenticator_scanner_image,
21+
authenticator_secret,
22+
authenticator_recovery_codes,
23+
urlProps,
24+
}) => {
25+
const [isOTPScreenVisisble, setIsOTPScreenVisisble] =
26+
useState<boolean>(false);
27+
28+
const handleContinue = () => {
29+
setIsOTPScreenVisisble(true);
30+
};
31+
32+
if (isOTPScreenVisisble) {
33+
<AuthorizerVerifyOtp
34+
{...{
35+
setView,
36+
onLogin,
37+
email,
38+
phone_number,
39+
urlProps,
40+
}}
41+
/>;
42+
}
43+
44+
return (
45+
<>
46+
<p style={{ margin: '10px 0px', fontWeight: 'bold' }}>
47+
Scan the QR code or enter the secret key into your authenticator app.
48+
</p>
49+
<img
50+
src={`data:image/jpeg;base64,${authenticator_scanner_image}`}
51+
alt="scanner"
52+
/>
53+
<p style={{ margin: '10px 0px' }}>
54+
If you are unable to scan the QR code, please enter the secret key
55+
manually.
56+
</p>
57+
<p style={{ margin: '10px 0px', fontWeight: 'bold' }}>
58+
{authenticator_secret}
59+
</p>
60+
<p style={{ margin: '10px 0px' }}>
61+
If you lose access to your authenticator app, you can use the recovery
62+
codes below to regain access to your account. Please save these codes
63+
safely and do not share them with anyone.
64+
</p>
65+
<ul>
66+
{authenticator_recovery_codes.map((code, index) => {
67+
return <li key={index}>{code}</li>;
68+
})}
69+
</ul>
70+
<StyledButton
71+
type="button"
72+
appearance={ButtonAppearance.Primary}
73+
onClick={handleContinue}
74+
>
75+
Continue
76+
</StyledButton>
77+
</>
78+
);
79+
};

src/components/AuthorizerVerifyOtp.tsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ interface InputDataType {
1515
export const AuthorizerVerifyOtp: FC<{
1616
setView?: (v: Views) => void;
1717
onLogin?: (data: any) => void;
18-
email: string;
18+
email?: string;
19+
phone_number?: string;
1920
urlProps?: Record<string, any>;
20-
}> = ({ setView, onLogin, email, urlProps }) => {
21+
}> = ({ setView, onLogin, email, phone_number, urlProps }) => {
2122
const [error, setError] = useState(``);
2223
const [successMessage, setSuccessMessage] = useState(``);
2324
const [loading, setLoading] = useState(false);
@@ -29,6 +30,11 @@ export const AuthorizerVerifyOtp: FC<{
2930
otp: null,
3031
});
3132
const { authorizerRef, config, setAuthData } = useAuthorizer();
33+
useEffect(() => {
34+
if (!email && !phone_number) {
35+
setError(`Email or Phone Number is required`);
36+
}
37+
}, []);
3238

3339
const onInputChange = async (field: string, value: string) => {
3440
setFormData({ ...formData, [field]: value });
@@ -129,7 +135,7 @@ export const AuthorizerVerifyOtp: FC<{
129135
<Message type={MessageType.Error} text={error} onClose={onErrorClose} />
130136
)}
131137
<p style={{ textAlign: 'center', margin: '10px 0px' }}>
132-
Please enter the OTP you received on your email address.
138+
Please enter the OTP sent to your email or phone number or authenticator
133139
</p>
134140
<br />
135141
<form onSubmit={onSubmit} name="authorizer-mfa-otp-form">

src/types/index.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,15 @@ export type AuthorizerContextPropsType = {
6060

6161
export type OtpDataType = {
6262
isScreenVisible: boolean;
63-
email: string;
63+
email?: string;
64+
phone_number?: string;
65+
};
66+
67+
export type TotpDataType = {
68+
isScreenVisible: boolean;
69+
email?: string;
70+
phone_number?: string;
71+
authenticator_scanner_image: string;
72+
authenticator_secret: string;
73+
authenticator_recovery_codes: string[];
6474
};

yalc.lock

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)