@sqrzro/auth 2.0.0-r19.9 → 4.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +2 -2
- package/.turbo/turbo-dev.log +45 -46
- package/dist/components/Auth/index.d.ts +4 -32
- package/dist/components/Auth/index.js +19 -40
- package/dist/components/AuthComponent/index.d.ts +6 -0
- package/dist/components/AuthComponent/index.js +21 -0
- package/dist/components/LoginForm/index.d.ts +2 -1
- package/dist/components/LoginForm/index.js +22 -3
- package/dist/components/LogoutButton/index.d.ts +1 -2
- package/dist/components/LogoutButton/index.js +5 -11
- package/dist/components/LogoutButton/server.d.ts +2 -0
- package/dist/components/LogoutButton/server.js +8 -0
- package/dist/components/MFAForm/index.js +1 -1
- package/dist/components/Password/index.d.ts +5 -0
- package/dist/components/Password/index.js +18 -0
- package/dist/components/PasswordForm/index.js +1 -1
- package/dist/components/PasswordResetForm/index.js +1 -1
- package/dist/forms/LoginForm/client.d.ts +2 -0
- package/dist/forms/LoginForm/client.js +13 -0
- package/dist/forms/LoginForm/index.d.ts +2 -0
- package/dist/forms/LoginForm/index.js +13 -0
- package/dist/forms/LoginForm/interfaces.d.ts +5 -0
- package/dist/forms/LoginForm/interfaces.js +1 -0
- package/dist/forms/LoginForm/server.d.ts +4 -0
- package/dist/forms/LoginForm/server.js +25 -0
- package/dist/forms/PasswordForm/client.d.ts +2 -0
- package/dist/forms/PasswordForm/client.js +13 -0
- package/dist/forms/PasswordForm/index.d.ts +2 -0
- package/dist/forms/PasswordForm/index.js +12 -0
- package/dist/forms/PasswordForm/interfaces.d.ts +3 -0
- package/dist/forms/PasswordForm/interfaces.js +1 -0
- package/dist/forms/PasswordForm/server.d.ts +4 -0
- package/dist/forms/PasswordForm/server.js +21 -0
- package/dist/forms/PasswordResetForm/client.d.ts +5 -0
- package/dist/forms/PasswordResetForm/client.js +14 -0
- package/dist/forms/PasswordResetForm/index.d.ts +5 -0
- package/dist/forms/PasswordResetForm/index.js +13 -0
- package/dist/forms/PasswordResetForm/interfaces.d.ts +4 -0
- package/dist/forms/PasswordResetForm/interfaces.js +1 -0
- package/dist/forms/PasswordResetForm/server.d.ts +4 -0
- package/dist/forms/PasswordResetForm/server.js +23 -0
- package/dist/handle-proxy.d.ts +4 -0
- package/dist/handle-proxy.js +17 -0
- package/dist/hooks/useLoginForm.js +3 -0
- package/dist/index.d.ts +1 -5
- package/dist/index.js +1 -2
- package/dist/interfaces.d.ts +10 -0
- package/dist/interfaces.js +1 -0
- package/dist/proxy.d.ts +0 -0
- package/dist/proxy.js +1 -0
- package/dist/server.d.ts +6 -1
- package/dist/server.js +12 -3
- package/package.json +14 -11
- package/src/components/Auth/index.tsx +31 -75
- package/src/components/LogoutButton/index.tsx +7 -19
- package/src/components/LogoutButton/server.ts +11 -0
- package/src/components/Password/index.tsx +26 -0
- package/src/forms/LoginForm/index.tsx +33 -0
- package/src/forms/LoginForm/interfaces.ts +5 -0
- package/src/forms/LoginForm/server.ts +33 -0
- package/src/forms/PasswordForm/index.tsx +24 -0
- package/src/forms/PasswordForm/interfaces.ts +3 -0
- package/src/forms/PasswordForm/server.ts +28 -0
- package/src/forms/PasswordResetForm/index.tsx +29 -0
- package/src/forms/PasswordResetForm/interfaces.ts +4 -0
- package/src/forms/PasswordResetForm/server.ts +30 -0
- package/src/handle-proxy.ts +23 -0
- package/src/index.ts +1 -7
- package/src/interfaces.ts +10 -0
- package/tsconfig.json +6 -5
- package/src/components/LoginForm/index.tsx +0 -44
- package/src/components/MFAForm/index.tsx +0 -47
- package/src/components/MFAPage/index.tsx +0 -54
- package/src/components/MFASetup/index.tsx +0 -43
- package/src/components/MFASetupForm/index.tsx +0 -78
- package/src/components/PasswordForm/index.tsx +0 -113
- package/src/components/PasswordPage/index.tsx +0 -20
- package/src/components/PasswordResetForm/index.tsx +0 -45
- package/src/hooks/useLoginForm.ts +0 -33
- package/src/hooks/useMFAForm.ts +0 -56
- package/src/server.ts +0 -100
package/src/index.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
export { default as Auth } from './components/Auth';
|
|
2
|
-
export type { AuthClassNames, AuthProps } from './components/Auth';
|
|
3
|
-
|
|
4
|
-
export type { MFASetupProps } from './components/MFASetup';
|
|
5
|
-
export { default as MFASetup } from './components/MFASetup';
|
|
6
|
-
|
|
7
2
|
export { default as LogoutButton } from './components/LogoutButton';
|
|
8
3
|
|
|
9
|
-
export
|
|
10
|
-
export { default as useMFAForm } from './hooks/useMFAForm';
|
|
4
|
+
export { default as handleProxy } from './handle-proxy';
|
package/tsconfig.json
CHANGED
|
@@ -4,15 +4,16 @@
|
|
|
4
4
|
"declaration": true,
|
|
5
5
|
"esModuleInterop": true,
|
|
6
6
|
"importHelpers": true,
|
|
7
|
+
"lib": ["ESNext"],
|
|
7
8
|
"jsx": "react-jsx",
|
|
8
|
-
"lib": ["DOM", "ESNext"],
|
|
9
9
|
"module": "ESNext",
|
|
10
|
-
"moduleResolution": "
|
|
10
|
+
"moduleResolution": "bundler",
|
|
11
11
|
"outDir": "dist",
|
|
12
12
|
"resolveJsonModule": true,
|
|
13
13
|
"skipLibCheck": true,
|
|
14
|
-
"
|
|
14
|
+
"strict": true,
|
|
15
|
+
"target": "esnext"
|
|
15
16
|
},
|
|
16
|
-
"include": ["
|
|
17
|
-
"exclude": ["node_modules"]
|
|
17
|
+
"include": ["**/*.ts"],
|
|
18
|
+
"exclude": ["dist", "./*.d.ts", "node_modules"]
|
|
18
19
|
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Fragment } from 'react';
|
|
4
|
-
|
|
5
|
-
import { Form, FormSubmit, Link, PasswordFormField, TextFormField, tw } from '@sqrzro/components';
|
|
6
|
-
|
|
7
|
-
import useLoginForm from '../../hooks/useLoginForm';
|
|
8
|
-
import { submitLoginForm } from '../../server';
|
|
9
|
-
|
|
10
|
-
import type { AuthClassNames } from '../Auth';
|
|
11
|
-
|
|
12
|
-
export interface LoginFormProps {
|
|
13
|
-
classNames?: Partial<AuthClassNames>;
|
|
14
|
-
searchParams: { r?: string };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function LoginForm({ classNames, searchParams }: Readonly<LoginFormProps>): React.ReactElement {
|
|
18
|
-
const { fieldProps, formData, formProps, isLoading } = useLoginForm(submitLoginForm, {
|
|
19
|
-
redirect: searchParams.r,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<Fragment>
|
|
24
|
-
<h1 className={tw('text-center', classNames?.title)}>Sign in to continue</h1>
|
|
25
|
-
<Form {...formProps} classNames={{ root: classNames?.form }}>
|
|
26
|
-
<TextFormField {...fieldProps('email')} hasAssistiveError />
|
|
27
|
-
<PasswordFormField {...fieldProps('password')} hasAssistiveError />
|
|
28
|
-
<div className={tw(classNames?.actions)}>
|
|
29
|
-
<FormSubmit isLoading={isLoading}>Sign In</FormSubmit>
|
|
30
|
-
</div>
|
|
31
|
-
<footer className={tw('text-center', classNames?.footer)}>
|
|
32
|
-
<Link
|
|
33
|
-
className={classNames?.link}
|
|
34
|
-
href={`/auth/password${formData.email ? `?email=${formData.email}` : ''}`}
|
|
35
|
-
>
|
|
36
|
-
Forgot Password?
|
|
37
|
-
</Link>
|
|
38
|
-
</footer>
|
|
39
|
-
</Form>
|
|
40
|
-
</Fragment>
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export default LoginForm;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { CodeFormField, Form, FormSubmit } from '@sqrzro/components';
|
|
4
|
-
|
|
5
|
-
import useMFAForm from '../../hooks/useMFAForm';
|
|
6
|
-
import { submitMFAForm } from '../../server';
|
|
7
|
-
|
|
8
|
-
import type { AuthClassNames } from '../Auth';
|
|
9
|
-
import { useEffect, useState } from 'react';
|
|
10
|
-
|
|
11
|
-
export interface MFAFormProps {
|
|
12
|
-
classNames?: Partial<AuthClassNames>;
|
|
13
|
-
searchParams: { r?: string };
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function MFAForm({ classNames, searchParams }: Readonly<MFAFormProps>): React.ReactElement {
|
|
17
|
-
const [delayedKey, setDelayedKey] = useState(0);
|
|
18
|
-
|
|
19
|
-
const { formProps, fieldProps, isLoading, key, submitForm } = useMFAForm(submitMFAForm, {
|
|
20
|
-
redirect: searchParams.r,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
if (!isLoading) {
|
|
25
|
-
setDelayedKey(key);
|
|
26
|
-
}
|
|
27
|
-
}, [isLoading, key]);
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<Form {...formProps} classNames={{ root: classNames?.form }}>
|
|
31
|
-
<CodeFormField
|
|
32
|
-
key={delayedKey}
|
|
33
|
-
{...fieldProps('token')}
|
|
34
|
-
isDisabled={isLoading}
|
|
35
|
-
onFinalChange={submitForm}
|
|
36
|
-
hasAssistiveError
|
|
37
|
-
hasAssistiveLabel
|
|
38
|
-
isAutoFocus
|
|
39
|
-
/>
|
|
40
|
-
<div className="sr-only">
|
|
41
|
-
<FormSubmit>Verify</FormSubmit>
|
|
42
|
-
</div>
|
|
43
|
-
</Form>
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export default MFAForm;
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { Fragment } from 'react';
|
|
2
|
-
|
|
3
|
-
import { tw } from '@sqrzro/components';
|
|
4
|
-
import { notFound } from 'next/navigation';
|
|
5
|
-
|
|
6
|
-
import type { AuthClassNames } from '../Auth';
|
|
7
|
-
import MFAForm from '../MFAForm';
|
|
8
|
-
import MFASetup from '../MFASetup';
|
|
9
|
-
|
|
10
|
-
import { checkMFAEnabled, checkUserHasMFA, getSessionUser } from '../../server';
|
|
11
|
-
import LogoutButton from '../LogoutButton';
|
|
12
|
-
|
|
13
|
-
export interface MFAPageProps {
|
|
14
|
-
classNames?: Partial<AuthClassNames>;
|
|
15
|
-
searchParams: { r?: string };
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async function MFAPage({
|
|
19
|
-
classNames,
|
|
20
|
-
searchParams,
|
|
21
|
-
}: Readonly<MFAPageProps>): Promise<React.ReactElement> {
|
|
22
|
-
if (!(await checkMFAEnabled())) {
|
|
23
|
-
return notFound();
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const user = await getSessionUser();
|
|
27
|
-
|
|
28
|
-
if (!user) {
|
|
29
|
-
return <div>Error</div>;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const userHasMFA = await checkUserHasMFA(user);
|
|
33
|
-
|
|
34
|
-
if (!userHasMFA) {
|
|
35
|
-
return <MFASetup classNames={classNames} searchParams={searchParams} />;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return (
|
|
39
|
-
<Fragment>
|
|
40
|
-
<h1 className={tw('text-center', classNames?.title)}>Multi-Factor Authentication</h1>
|
|
41
|
-
<p className="text-center">
|
|
42
|
-
To confirm your identity, enter the 6-digit code listed in your authentication app:
|
|
43
|
-
</p>
|
|
44
|
-
<MFAForm classNames={classNames} searchParams={searchParams} />
|
|
45
|
-
<footer className={tw('text-center', classNames?.footer)}>
|
|
46
|
-
<LogoutButton>
|
|
47
|
-
<span className={classNames?.link}>Back</span>
|
|
48
|
-
</LogoutButton>
|
|
49
|
-
</footer>
|
|
50
|
-
</Fragment>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export default MFAPage;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Fragment } from 'react';
|
|
2
|
-
|
|
3
|
-
import { tw } from '@sqrzro/components';
|
|
4
|
-
|
|
5
|
-
import { generateMFA, getSessionUser } from '../../server';
|
|
6
|
-
|
|
7
|
-
import type { AuthClassNames } from '../Auth';
|
|
8
|
-
import MFASetupForm from '../MFASetupForm';
|
|
9
|
-
|
|
10
|
-
export interface MFASetupProps {
|
|
11
|
-
classNames?: Partial<AuthClassNames>;
|
|
12
|
-
searchParams: { r?: string };
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async function MFASetup({
|
|
16
|
-
classNames,
|
|
17
|
-
searchParams,
|
|
18
|
-
}: Readonly<MFASetupProps>): Promise<React.ReactElement> {
|
|
19
|
-
if (!process.env.APP_NAME) {
|
|
20
|
-
throw new Error('MFA secret could not be generated because APP_NAME is not defined');
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const user = await getSessionUser();
|
|
24
|
-
|
|
25
|
-
if (!user) {
|
|
26
|
-
return <div>Error</div>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const url = await generateMFA(process.env.APP_NAME, user.email);
|
|
30
|
-
|
|
31
|
-
if (!url) {
|
|
32
|
-
return <div>Error</div>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Fragment>
|
|
37
|
-
<h1 className={tw('', classNames?.title)}>Multi-Factor Authentication</h1>
|
|
38
|
-
<MFASetupForm classNames={classNames} searchParams={searchParams} url={url} />
|
|
39
|
-
</Fragment>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export default MFASetup;
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Fragment, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { Button, Link, tw } from '@sqrzro/components';
|
|
6
|
-
|
|
7
|
-
import type { AuthClassNames } from '../Auth';
|
|
8
|
-
import LogoutButton from '../LogoutButton';
|
|
9
|
-
import MFAForm from '../MFAForm';
|
|
10
|
-
|
|
11
|
-
export interface MFASetupFormProps {
|
|
12
|
-
classNames?: Partial<AuthClassNames>;
|
|
13
|
-
searchParams: { r?: string };
|
|
14
|
-
url: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function MFASetupForm({
|
|
18
|
-
classNames,
|
|
19
|
-
searchParams,
|
|
20
|
-
url,
|
|
21
|
-
}: Readonly<MFASetupFormProps>): React.ReactElement | null {
|
|
22
|
-
const [step, setStep] = useState(1);
|
|
23
|
-
|
|
24
|
-
function nextStep(): void {
|
|
25
|
-
setStep(2);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function previousStep(event: React.MouseEvent): void {
|
|
29
|
-
setStep(1);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (step === 1) {
|
|
33
|
-
return (
|
|
34
|
-
<div className={tw(classNames?.form)}>
|
|
35
|
-
<p>
|
|
36
|
-
You'll need to setup an additional authentication step before you can
|
|
37
|
-
continue.
|
|
38
|
-
</p>
|
|
39
|
-
<figure className="flex justify-center rounded border p-4">
|
|
40
|
-
<img alt="qrcode" src={url} width="147" />
|
|
41
|
-
</figure>
|
|
42
|
-
<p>
|
|
43
|
-
<strong>Step 1:</strong> Scan the QR code above in an authentication app like
|
|
44
|
-
Google Authenticator or Authy.
|
|
45
|
-
</p>
|
|
46
|
-
<Button onClick={nextStep} variant="primary" isFullWidth>
|
|
47
|
-
Continue
|
|
48
|
-
</Button>
|
|
49
|
-
<div className="text-center">
|
|
50
|
-
<LogoutButton>
|
|
51
|
-
<span className={classNames?.link}>Back</span>
|
|
52
|
-
</LogoutButton>
|
|
53
|
-
</div>
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
if (step === 2) {
|
|
59
|
-
return (
|
|
60
|
-
<div className={tw(classNames?.form)}>
|
|
61
|
-
<p>
|
|
62
|
-
<strong>Step 2:</strong> To confirm your identity, enter the 6-digit code listed
|
|
63
|
-
in your authentication app:
|
|
64
|
-
</p>
|
|
65
|
-
<MFAForm classNames={classNames} searchParams={searchParams} />
|
|
66
|
-
<div className="text-center">
|
|
67
|
-
<button className={classNames?.link} onClick={previousStep} type="button">
|
|
68
|
-
Scan QR code again
|
|
69
|
-
</button>
|
|
70
|
-
</div>
|
|
71
|
-
</div>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export default MFASetupForm;
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Fragment, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { Form, FormSubmit, Link, TextFormField, tw } from '@sqrzro/components';
|
|
6
|
-
import { useForm } from '@sqrzro/hooks';
|
|
7
|
-
|
|
8
|
-
import { submitPasswordForm } from '../../server';
|
|
9
|
-
|
|
10
|
-
import type { AuthClassNames } from '../Auth';
|
|
11
|
-
|
|
12
|
-
interface PasswordFormFields {
|
|
13
|
-
email: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface PasswordFormProps {
|
|
17
|
-
classNames?: Partial<AuthClassNames>;
|
|
18
|
-
email?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function PasswordForm({ classNames, email }: Readonly<PasswordFormProps>): React.ReactElement {
|
|
22
|
-
const [sentCount, setSentCount] = useState(0);
|
|
23
|
-
|
|
24
|
-
const { fieldProps, formData, formProps, isLoading, setFormData, submitForm } = useForm<
|
|
25
|
-
PasswordFormFields,
|
|
26
|
-
boolean
|
|
27
|
-
>({
|
|
28
|
-
defaults: { email },
|
|
29
|
-
onSubmit: submitPasswordForm,
|
|
30
|
-
onSuccess: () => {
|
|
31
|
-
setSentCount(sentCount + 1);
|
|
32
|
-
},
|
|
33
|
-
toasts: { success: false },
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
function handleResend(event: React.MouseEvent<HTMLAnchorElement>): void {
|
|
37
|
-
event.preventDefault();
|
|
38
|
-
submitForm();
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function handleReset(event: React.MouseEvent<HTMLAnchorElement>): void {
|
|
42
|
-
event.preventDefault();
|
|
43
|
-
|
|
44
|
-
setFormData('email', '');
|
|
45
|
-
setSentCount(0);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<Fragment>
|
|
50
|
-
<div className={tw(sentCount === 0 ? 'block' : 'hidden')}>
|
|
51
|
-
<Form {...formProps} classNames={{ root: classNames?.form }}>
|
|
52
|
-
<h1 className={classNames?.title}>Reset Your Password</h1>
|
|
53
|
-
<p className="text-sm">
|
|
54
|
-
Enter the email address associated with your account and we'll send you
|
|
55
|
-
a link to reset your password.
|
|
56
|
-
</p>
|
|
57
|
-
<TextFormField {...fieldProps('email')} type="email" hasAssistiveLabel />
|
|
58
|
-
<div className={tw(classNames?.actions)}>
|
|
59
|
-
<FormSubmit isLoading={isLoading}>Send Email</FormSubmit>
|
|
60
|
-
</div>
|
|
61
|
-
<footer className={tw('text-center', classNames?.footer)}>
|
|
62
|
-
<Link className={classNames?.link} href="/auth/login">
|
|
63
|
-
Back
|
|
64
|
-
</Link>
|
|
65
|
-
</footer>
|
|
66
|
-
</Form>
|
|
67
|
-
</div>
|
|
68
|
-
|
|
69
|
-
<div className={tw(sentCount > 0 ? 'flex' : 'hidden')}>
|
|
70
|
-
<div className={tw(classNames?.form)}>
|
|
71
|
-
<h1 className={classNames?.title}>Check Your Email</h1>
|
|
72
|
-
{sentCount === 1 ? (
|
|
73
|
-
<Fragment>
|
|
74
|
-
<p className="text-sm">
|
|
75
|
-
If <strong>{formData.email}</strong> matches an email we have on
|
|
76
|
-
file, then we've sent you an email containing further
|
|
77
|
-
instructions for resetting your password.
|
|
78
|
-
</p>
|
|
79
|
-
<p className="text-sm">
|
|
80
|
-
If you haven't received an email in 5 minutes, check your spam,{' '}
|
|
81
|
-
<Link className={classNames?.link} onClick={handleResend}>
|
|
82
|
-
resend
|
|
83
|
-
</Link>
|
|
84
|
-
, or{' '}
|
|
85
|
-
<Link className={classNames?.link} onClick={handleReset}>
|
|
86
|
-
try a different email address
|
|
87
|
-
</Link>
|
|
88
|
-
.
|
|
89
|
-
</p>
|
|
90
|
-
</Fragment>
|
|
91
|
-
) : (
|
|
92
|
-
<Fragment>
|
|
93
|
-
<p className="text-sm">
|
|
94
|
-
We've resent password reset instructions to{' '}
|
|
95
|
-
<strong>{formData.email}</strong>, if it is an email we have on
|
|
96
|
-
file.
|
|
97
|
-
</p>
|
|
98
|
-
<p className="text-sm">
|
|
99
|
-
Please check again. If you still haven't received an email,{' '}
|
|
100
|
-
<Link className={classNames?.link} onClick={handleReset}>
|
|
101
|
-
try a different email address
|
|
102
|
-
</Link>
|
|
103
|
-
.
|
|
104
|
-
</p>
|
|
105
|
-
</Fragment>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
</div>
|
|
109
|
-
</Fragment>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export default PasswordForm;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { AuthClassNames } from '../Auth';
|
|
2
|
-
import PasswordForm from '../PasswordForm';
|
|
3
|
-
import PasswordResetForm from '../PasswordResetForm';
|
|
4
|
-
|
|
5
|
-
interface PasswordPageProps {
|
|
6
|
-
classNames?: Partial<AuthClassNames>;
|
|
7
|
-
searchParams: { email?: string; token?: string };
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function PasswordPage({
|
|
11
|
-
classNames,
|
|
12
|
-
searchParams,
|
|
13
|
-
}: Readonly<PasswordPageProps>): React.ReactElement {
|
|
14
|
-
if (searchParams.token) {
|
|
15
|
-
return <PasswordResetForm classNames={classNames} token={searchParams.token} />;
|
|
16
|
-
}
|
|
17
|
-
return <PasswordForm classNames={classNames} email={searchParams.email} />;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default PasswordPage;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { Form, FormSubmit, PasswordFormField } from '@sqrzro/components';
|
|
4
|
-
import { useForm } from '@sqrzro/hooks';
|
|
5
|
-
import { useRouter } from 'next/navigation';
|
|
6
|
-
|
|
7
|
-
import { submitPasswordResetForm } from '../../server';
|
|
8
|
-
|
|
9
|
-
import type { AuthClassNames } from '../Auth';
|
|
10
|
-
|
|
11
|
-
interface PasswordResetFormProps {
|
|
12
|
-
classNames?: Partial<AuthClassNames>;
|
|
13
|
-
token: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function PasswordResetForm({
|
|
17
|
-
classNames,
|
|
18
|
-
token,
|
|
19
|
-
}: Readonly<PasswordResetFormProps>): React.ReactElement {
|
|
20
|
-
const router = useRouter();
|
|
21
|
-
|
|
22
|
-
const { fieldProps, formProps, isLoading } = useForm({
|
|
23
|
-
defaults: { token },
|
|
24
|
-
hiddenFields: ['token'],
|
|
25
|
-
onSubmit: submitPasswordResetForm,
|
|
26
|
-
onSuccess: (response) => {
|
|
27
|
-
router.push(response || '/');
|
|
28
|
-
},
|
|
29
|
-
toasts: { success: false },
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<Form {...formProps} classNames={{ root: classNames?.form }}>
|
|
34
|
-
<h1 className={classNames?.title}>Reset Your Password</h1>
|
|
35
|
-
<div className="relative">
|
|
36
|
-
<PasswordFormField {...fieldProps('password', 'New Password')} />
|
|
37
|
-
</div>
|
|
38
|
-
<div className="mt-8">
|
|
39
|
-
<FormSubmit isLoading={isLoading}>Reset Password</FormSubmit>
|
|
40
|
-
</div>
|
|
41
|
-
</Form>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export default PasswordResetForm;
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useForm } from '@sqrzro/hooks';
|
|
4
|
-
import type { UseFormReturn } from '@sqrzro/hooks';
|
|
5
|
-
|
|
6
|
-
import type { Errorable } from '@sqrzro/interfaces';
|
|
7
|
-
|
|
8
|
-
interface NextRedirect {
|
|
9
|
-
NEXT_REDIRECT: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export interface LoginFormFields {
|
|
13
|
-
email: string;
|
|
14
|
-
password: string;
|
|
15
|
-
redirect: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface UseLoginFormOptions {
|
|
19
|
-
redirect?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function useLoginForm(
|
|
23
|
-
onSubmit: (formData: LoginFormFields) => Promise<Errorable<NextRedirect>>,
|
|
24
|
-
{ redirect }: UseLoginFormOptions = {}
|
|
25
|
-
): UseFormReturn<LoginFormFields> {
|
|
26
|
-
return useForm({
|
|
27
|
-
defaults: { redirect },
|
|
28
|
-
onSubmit,
|
|
29
|
-
toasts: { validation: 'Incorrect email or password', success: false },
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default useLoginForm;
|
package/src/hooks/useMFAForm.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
import { useForm } from '@sqrzro/hooks';
|
|
6
|
-
import type { UseFormArgs, UseFormReturn } from '@sqrzro/hooks';
|
|
7
|
-
import { Errorable } from '@sqrzro/interfaces';
|
|
8
|
-
|
|
9
|
-
interface NextRedirect {
|
|
10
|
-
NEXT_REDIRECT: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface MFAFormFields {
|
|
14
|
-
redirect: string;
|
|
15
|
-
token: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface UseMFAFormArgs {
|
|
19
|
-
onSubmit: UseFormArgs<MFAFormFields, void>['onSubmit'];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface UseMFAFormReturn extends UseFormReturn<MFAFormFields> {
|
|
23
|
-
key: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface UseMFAFormOptions {
|
|
27
|
-
redirect?: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function useLoginForm(
|
|
31
|
-
onSubmit: (formData: MFAFormFields) => Promise<Errorable<NextRedirect>>,
|
|
32
|
-
{ redirect }: UseMFAFormOptions = {}
|
|
33
|
-
): UseMFAFormReturn {
|
|
34
|
-
const [errorCount, setErrorCount] = useState(0);
|
|
35
|
-
|
|
36
|
-
const form = useForm({
|
|
37
|
-
defaults: { redirect },
|
|
38
|
-
onSubmit,
|
|
39
|
-
onValidationError: () => {
|
|
40
|
-
setErrorCount((count) => count + 1);
|
|
41
|
-
},
|
|
42
|
-
toasts: {
|
|
43
|
-
success: false,
|
|
44
|
-
validation:
|
|
45
|
-
"There seems to be an issue with the code you've entered. Please try again.",
|
|
46
|
-
},
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
form.resetForm();
|
|
51
|
-
}, [errorCount]);
|
|
52
|
-
|
|
53
|
-
return { ...form, key: errorCount };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export default useLoginForm;
|
package/src/server.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
'use server';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
handleLoginForm,
|
|
5
|
-
handleLogout,
|
|
6
|
-
handleMFAForm,
|
|
7
|
-
handlePasswordForm,
|
|
8
|
-
handlePasswordResetWithTokenForm,
|
|
9
|
-
checkUserHasMFA as serverCheckUserHasMFA,
|
|
10
|
-
generateMFA as serverGenerateMFA,
|
|
11
|
-
getSessionUser as serverGetSessionUser,
|
|
12
|
-
checkMFAEnabled as syncCheckMFAEnabled,
|
|
13
|
-
} from '@sqrzro/server/auth';
|
|
14
|
-
import type { LoginFormFields, UserObject } from '@sqrzro/server/auth';
|
|
15
|
-
import type { NextRedirect } from '@sqrzro/server/forms';
|
|
16
|
-
import type { Errorable } from '@sqrzro/interfaces';
|
|
17
|
-
|
|
18
|
-
export type { ScopeObject } from '@sqrzro/server/auth';
|
|
19
|
-
|
|
20
|
-
interface AuthEventObject {
|
|
21
|
-
login?: () => Promise<void>;
|
|
22
|
-
password?: (email: string, token: string) => Promise<boolean>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const authEvents: AuthEventObject = {};
|
|
26
|
-
|
|
27
|
-
export async function registerAuthEvents(events: AuthEventObject): Promise<void> {
|
|
28
|
-
if (events.login) {
|
|
29
|
-
authEvents.login = events.login;
|
|
30
|
-
}
|
|
31
|
-
if (events.password) {
|
|
32
|
-
authEvents.password = events.password;
|
|
33
|
-
}
|
|
34
|
-
return Promise.resolve();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/*
|
|
38
|
-
* Functions re-exported from @sqrzro/server/auth
|
|
39
|
-
*
|
|
40
|
-
* NextJS may complain about these functions not being async if they are exported directly from the
|
|
41
|
-
* @sqrzro/server/auth module, so we re-export them here to be on the safe side.
|
|
42
|
-
*/
|
|
43
|
-
|
|
44
|
-
export async function checkUserHasMFA(user: UserObject): Promise<boolean> {
|
|
45
|
-
return serverCheckUserHasMFA(user);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function generateMFA(name: string, email?: string): Promise<string | null> {
|
|
49
|
-
return serverGenerateMFA(name, email);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export async function getSessionUser(): Promise<UserObject | null> {
|
|
53
|
-
return serverGetSessionUser();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// This function is synchronous, so we need to wrap it in a promise so it can be exported properly.
|
|
57
|
-
export async function checkMFAEnabled(): Promise<boolean> {
|
|
58
|
-
return Promise.resolve(syncCheckMFAEnabled());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
interface MFAFormFields {
|
|
62
|
-
token: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface PasswordFormFields {
|
|
66
|
-
email: string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
interface PasswordResetFormFields {
|
|
70
|
-
password: string;
|
|
71
|
-
token: string;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function submitLoginForm(formData: LoginFormFields): Promise<Errorable<NextRedirect>> {
|
|
75
|
-
return handleLoginForm(formData, authEvents.login);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export async function submitMFAForm(formData: MFAFormFields): Promise<Errorable<NextRedirect>> {
|
|
79
|
-
return handleMFAForm(formData);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function sendPasswordResetMail(email: string, token: string): Promise<boolean> {
|
|
83
|
-
return authEvents.password?.(email, token) || false;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export async function submitPasswordForm(
|
|
87
|
-
formData: PasswordFormFields
|
|
88
|
-
): Promise<Errorable<boolean>> {
|
|
89
|
-
return handlePasswordForm(formData, sendPasswordResetMail);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export async function submitPasswordResetForm(
|
|
93
|
-
formData: PasswordResetFormFields
|
|
94
|
-
): Promise<Errorable<string>> {
|
|
95
|
-
return handlePasswordResetWithTokenForm(formData);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function logout(): Promise<void> {
|
|
99
|
-
return handleLogout();
|
|
100
|
-
}
|