@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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
import { Form, FormSubmit, PasswordFormField, useForm } from '@sqrzro/ui/forms';
|
|
5
|
+
import submit from './server';
|
|
6
|
+
function PasswordResetForm({ token }) {
|
|
7
|
+
const { fieldProps, formProps } = useForm({
|
|
8
|
+
defaults: { token },
|
|
9
|
+
onSubmit: submit,
|
|
10
|
+
});
|
|
11
|
+
return (_jsx(Fragment, { children: _jsxs(Form, { ...formProps, children: [_jsx(PasswordFormField, { ...fieldProps('password') }), _jsx(FormSubmit, { isFullWidth: true, children: "Reset Password" })] }) }));
|
|
12
|
+
}
|
|
13
|
+
export default PasswordResetForm;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
import { updatePasswordWithToken } from '@sqrzro/server/auth';
|
|
3
|
+
import { submitForm } from '@sqrzro/server/forms';
|
|
4
|
+
import { redirect } from 'next/navigation';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
const schema = z
|
|
7
|
+
.object({
|
|
8
|
+
password: z.string(),
|
|
9
|
+
token: z.string().regex(/[A-Za-z0-9]{48}/u),
|
|
10
|
+
})
|
|
11
|
+
.required({ password: true, token: true });
|
|
12
|
+
async function fn(data) {
|
|
13
|
+
await updatePasswordWithToken(data.token, data.password);
|
|
14
|
+
return redirect('/auth/login');
|
|
15
|
+
}
|
|
16
|
+
async function submit(formData) {
|
|
17
|
+
return submitForm({
|
|
18
|
+
fn,
|
|
19
|
+
formData,
|
|
20
|
+
schema,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export default submit;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { validateSession } from '@sqrzro/server/auth';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
async function handleProxy(request) {
|
|
4
|
+
const pathname = request.nextUrl.pathname;
|
|
5
|
+
const validated = await validateSession(request.cookies);
|
|
6
|
+
if (validated) {
|
|
7
|
+
if (pathname.startsWith('/auth')) {
|
|
8
|
+
return NextResponse.redirect(new URL('/', request.url));
|
|
9
|
+
}
|
|
10
|
+
return NextResponse.next();
|
|
11
|
+
}
|
|
12
|
+
if (pathname.startsWith('/auth')) {
|
|
13
|
+
return NextResponse.next();
|
|
14
|
+
}
|
|
15
|
+
return NextResponse.redirect(new URL('/auth/login', request.url));
|
|
16
|
+
}
|
|
17
|
+
export default handleProxy;
|
|
@@ -3,6 +3,9 @@ import { useForm } from '@sqrzro/hooks';
|
|
|
3
3
|
function useLoginForm(onSubmit, { redirect } = {}) {
|
|
4
4
|
return useForm({
|
|
5
5
|
defaults: { redirect },
|
|
6
|
+
onError: (error) => {
|
|
7
|
+
console.error('Login form error:', error);
|
|
8
|
+
},
|
|
6
9
|
onSubmit,
|
|
7
10
|
toasts: { validation: 'Incorrect email or password', success: false },
|
|
8
11
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
1
|
export { default as Auth } from './components/Auth';
|
|
2
|
-
export type { AuthClassNames, AuthProps } from './components/Auth';
|
|
3
|
-
export type { MFASetupProps } from './components/MFASetup';
|
|
4
|
-
export { default as MFASetup } from './components/MFASetup';
|
|
5
2
|
export { default as LogoutButton } from './components/LogoutButton';
|
|
6
|
-
export
|
|
7
|
-
export { default as useMFAForm } from './hooks/useMFAForm';
|
|
3
|
+
export { default as handleProxy } from './handle-proxy';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
1
|
export { default as Auth } from './components/Auth';
|
|
2
|
-
export { default as MFASetup } from './components/MFASetup';
|
|
3
2
|
export { default as LogoutButton } from './components/LogoutButton';
|
|
4
|
-
export { default as
|
|
3
|
+
export { default as handleProxy } from './handle-proxy';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/proxy.d.ts
ADDED
|
File without changes
|
package/dist/proxy.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
package/dist/server.d.ts
CHANGED
|
@@ -3,13 +3,18 @@ import type { NextRedirect } from '@sqrzro/server/forms';
|
|
|
3
3
|
import type { Errorable } from '@sqrzro/interfaces';
|
|
4
4
|
export type { ScopeObject } from '@sqrzro/server/auth';
|
|
5
5
|
interface AuthEventObject {
|
|
6
|
-
|
|
6
|
+
auth?: (user: UserObject) => Promise<boolean>;
|
|
7
|
+
login?: (user: UserObject) => Promise<{
|
|
8
|
+
redirect: string;
|
|
9
|
+
} | void>;
|
|
10
|
+
mfa?: (user: UserObject) => Promise<void>;
|
|
7
11
|
password?: (email: string, token: string) => Promise<boolean>;
|
|
8
12
|
}
|
|
9
13
|
export declare function registerAuthEvents(events: AuthEventObject): Promise<void>;
|
|
10
14
|
export declare function checkUserHasMFA(user: UserObject): Promise<boolean>;
|
|
11
15
|
export declare function generateMFA(name: string, email?: string): Promise<string | null>;
|
|
12
16
|
export declare function getSessionUser(): Promise<UserObject | null>;
|
|
17
|
+
export declare function setUserSession(id: string): Promise<void>;
|
|
13
18
|
export declare function checkMFAEnabled(): Promise<boolean>;
|
|
14
19
|
interface MFAFormFields {
|
|
15
20
|
token: string;
|
package/dist/server.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
'use server';
|
|
2
|
-
import { handleLoginForm, handleLogout, handleMFAForm, handlePasswordForm, handlePasswordResetWithTokenForm, checkUserHasMFA as serverCheckUserHasMFA, generateMFA as serverGenerateMFA, getSessionUser as serverGetSessionUser, checkMFAEnabled as syncCheckMFAEnabled, } from '@sqrzro/server/auth';
|
|
2
|
+
import { handleLoginForm, handleLogout, handleMFAForm, handlePasswordForm, handlePasswordResetWithTokenForm, checkUserHasMFA as serverCheckUserHasMFA, generateMFA as serverGenerateMFA, getSessionUser as serverGetSessionUser, setUserSession as serverSetUserSession, checkMFAEnabled as syncCheckMFAEnabled, } from '@sqrzro/server/auth';
|
|
3
3
|
const authEvents = {};
|
|
4
4
|
export async function registerAuthEvents(events) {
|
|
5
|
+
if (events.auth) {
|
|
6
|
+
authEvents.auth = events.auth;
|
|
7
|
+
}
|
|
5
8
|
if (events.login) {
|
|
6
9
|
authEvents.login = events.login;
|
|
7
10
|
}
|
|
11
|
+
if (events.mfa) {
|
|
12
|
+
authEvents.mfa = events.mfa;
|
|
13
|
+
}
|
|
8
14
|
if (events.password) {
|
|
9
15
|
authEvents.password = events.password;
|
|
10
16
|
}
|
|
@@ -25,15 +31,18 @@ export async function generateMFA(name, email) {
|
|
|
25
31
|
export async function getSessionUser() {
|
|
26
32
|
return serverGetSessionUser();
|
|
27
33
|
}
|
|
34
|
+
export async function setUserSession(id) {
|
|
35
|
+
return serverSetUserSession(id);
|
|
36
|
+
}
|
|
28
37
|
// This function is synchronous, so we need to wrap it in a promise so it can be exported properly.
|
|
29
38
|
export async function checkMFAEnabled() {
|
|
30
39
|
return Promise.resolve(syncCheckMFAEnabled());
|
|
31
40
|
}
|
|
32
41
|
export async function submitLoginForm(formData) {
|
|
33
|
-
return handleLoginForm(formData, authEvents.login);
|
|
42
|
+
return handleLoginForm(formData, authEvents.auth, authEvents.login);
|
|
34
43
|
}
|
|
35
44
|
export async function submitMFAForm(formData) {
|
|
36
|
-
return handleMFAForm(formData);
|
|
45
|
+
return handleMFAForm(formData, authEvents.mfa);
|
|
37
46
|
}
|
|
38
47
|
async function sendPasswordResetMail(email, token) {
|
|
39
48
|
return authEvents.password?.(email, token) || false;
|
package/package.json
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sqrzro/auth",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0-alpha.0",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@sqrzro/
|
|
9
|
-
"
|
|
10
|
-
"@sqrzro/
|
|
11
|
-
"@sqrzro/server": "r19",
|
|
12
|
-
"next": "^15.1.5",
|
|
13
|
-
"react": "^19.0.0"
|
|
8
|
+
"@sqrzro/server": "n16",
|
|
9
|
+
"zod": "^4.1.13",
|
|
10
|
+
"@sqrzro/ui": "^4.0.0-alpha.0"
|
|
14
11
|
},
|
|
15
12
|
"devDependencies": {
|
|
16
|
-
"@types/react": "^19.
|
|
17
|
-
"
|
|
13
|
+
"@types/react": "^19.2.7",
|
|
14
|
+
"prettier": "^3.7.4",
|
|
15
|
+
"typescript": "^5.9.3",
|
|
16
|
+
"@sqrzro/prettier-config": "^4.0.0-alpha.0"
|
|
17
|
+
},
|
|
18
|
+
"peerDependencies": {
|
|
19
|
+
"next": "^16.0.1",
|
|
20
|
+
"react": "^19.2.0"
|
|
18
21
|
},
|
|
19
22
|
"scripts": {
|
|
20
|
-
"build": "tsc
|
|
21
|
-
"dev": "tsc
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"dev": "tsc --watch",
|
|
22
25
|
"start": "pnpm dev"
|
|
23
26
|
}
|
|
24
27
|
}
|
|
@@ -1,91 +1,47 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ClassNameProps } from '@sqrzro/components';
|
|
3
|
-
import { notFound } from 'next/navigation';
|
|
1
|
+
import { Suspense } from 'react';
|
|
4
2
|
|
|
5
|
-
import
|
|
6
|
-
import MFAPage from '../MFAPage';
|
|
7
|
-
import PasswordPage from '../PasswordPage';
|
|
3
|
+
import { notFound } from 'next/navigation';
|
|
8
4
|
|
|
9
|
-
import
|
|
10
|
-
import
|
|
5
|
+
import LoginForm from '../../forms/LoginForm';
|
|
6
|
+
import Password from '../Password';
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
form: string;
|
|
16
|
-
link: string;
|
|
17
|
-
logo: string;
|
|
18
|
-
root: string;
|
|
19
|
-
panel: string;
|
|
20
|
-
title: string;
|
|
8
|
+
interface AuthProps {
|
|
9
|
+
params: Promise<Record<string, string>>;
|
|
10
|
+
searchParams: Promise<Record<string, string>>;
|
|
21
11
|
}
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
scopes?: Partial<ScopeObject>;
|
|
29
|
-
searchParams: { email?: string; r?: string; token?: string };
|
|
30
|
-
}
|
|
13
|
+
async function AuthComponent({
|
|
14
|
+
params,
|
|
15
|
+
searchParams,
|
|
16
|
+
}: Readonly<AuthProps>): Promise<React.ReactElement> {
|
|
17
|
+
const awaitedParams = await params;
|
|
31
18
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
19
|
+
if (!Array.isArray(awaitedParams.auth) || awaitedParams.auth.length !== 1) {
|
|
20
|
+
return notFound();
|
|
21
|
+
}
|
|
36
22
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
* Catch-All Segment to handle the rendering of all Auth pages. This function is used to determine
|
|
40
|
-
* which page to render based on the route.
|
|
41
|
-
*
|
|
42
|
-
* More information on Catch-All Segments can be found here:
|
|
43
|
-
* https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes#catch-all-segments
|
|
44
|
-
*
|
|
45
|
-
* @param route
|
|
46
|
-
* @param props
|
|
47
|
-
* @returns
|
|
48
|
-
*/
|
|
49
|
-
function getPage(route: string, props: AuthPageProps): React.ReactElement {
|
|
50
|
-
switch (route) {
|
|
51
|
-
case 'mfa':
|
|
52
|
-
return <MFAPage {...props} />;
|
|
53
|
-
case 'password':
|
|
54
|
-
return <PasswordPage {...props} />;
|
|
55
|
-
default:
|
|
56
|
-
return <LoginForm {...props} />;
|
|
23
|
+
if (awaitedParams.auth[0] === 'login') {
|
|
24
|
+
return <LoginForm />;
|
|
57
25
|
}
|
|
58
|
-
}
|
|
59
26
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
* A note on `classNames`: As `Auth` needs to run on the server (as some of its children require
|
|
63
|
-
* sessions etc), using the `getClassNames` pattern won't work, as it currently only works for
|
|
64
|
-
* client components. So we have to pass the classNames directly to the component.
|
|
65
|
-
*/
|
|
66
|
-
async function Auth({
|
|
67
|
-
classNames,
|
|
68
|
-
logo,
|
|
69
|
-
onLogin,
|
|
70
|
-
onPassword,
|
|
71
|
-
params: { auth },
|
|
72
|
-
searchParams,
|
|
73
|
-
}: Readonly<AuthProps>): Promise<React.ReactElement> {
|
|
74
|
-
if (auth.length > 1) {
|
|
75
|
-
return notFound();
|
|
27
|
+
if (awaitedParams.auth[0] === 'mfa') {
|
|
28
|
+
return <div>[MFA NOT DONE]</div>;
|
|
76
29
|
}
|
|
77
30
|
|
|
78
|
-
|
|
31
|
+
if (awaitedParams.auth[0] === 'password') {
|
|
32
|
+
return <Password searchParams={searchParams} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return notFound();
|
|
36
|
+
}
|
|
79
37
|
|
|
38
|
+
function Auth({ params, searchParams }: Readonly<AuthProps>): React.ReactElement {
|
|
80
39
|
return (
|
|
81
|
-
<div
|
|
82
|
-
|
|
83
|
-
<
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
searchParams,
|
|
87
|
-
})}
|
|
88
|
-
</div>
|
|
40
|
+
<div>
|
|
41
|
+
AUTH PAGE HEADER
|
|
42
|
+
<Suspense>
|
|
43
|
+
<AuthComponent params={params} searchParams={searchParams} />
|
|
44
|
+
</Suspense>
|
|
89
45
|
</div>
|
|
90
46
|
);
|
|
91
47
|
}
|
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useTransition } from 'react';
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import handle from './server';
|
|
6
6
|
|
|
7
7
|
export interface LogoutButtonProps {
|
|
8
8
|
children?: React.ReactElement | string;
|
|
9
|
-
redirectTo?: string;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
function LogoutButton({
|
|
13
|
-
|
|
14
|
-
redirectTo,
|
|
15
|
-
}: Readonly<LogoutButtonProps>): React.ReactElement {
|
|
16
|
-
const router = useRouter();
|
|
17
|
-
|
|
18
|
-
async function handleLogout(): Promise<void> {
|
|
19
|
-
await logout();
|
|
20
|
-
router.push(redirectTo || '/');
|
|
21
|
-
|
|
22
|
-
// It seems necessary to refresh to ensure the user is redirected to the login page
|
|
23
|
-
router.refresh();
|
|
24
|
-
}
|
|
11
|
+
function LogoutButton({ children = 'Log out' }: Readonly<LogoutButtonProps>): React.ReactElement {
|
|
12
|
+
const [isLoading, startTransition] = useTransition();
|
|
25
13
|
|
|
26
14
|
return (
|
|
27
|
-
<
|
|
28
|
-
|
|
29
|
-
</
|
|
15
|
+
<button disabled={isLoading} onClick={() => startTransition(handle)}>
|
|
16
|
+
{children}
|
|
17
|
+
</button>
|
|
30
18
|
);
|
|
31
19
|
}
|
|
32
20
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { validateReset } from '@sqrzro/server/auth';
|
|
2
|
+
|
|
3
|
+
import PasswordForm from '../../forms/PasswordForm';
|
|
4
|
+
import PasswordResetForm from '../../forms/PasswordResetForm';
|
|
5
|
+
|
|
6
|
+
interface PasswordProps {
|
|
7
|
+
searchParams: Promise<Record<string, string>>;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function Password({ searchParams }: Readonly<PasswordProps>): Promise<React.ReactElement> {
|
|
11
|
+
const awaitedSearchParams = await searchParams;
|
|
12
|
+
|
|
13
|
+
if (awaitedSearchParams.token) {
|
|
14
|
+
const validated = await validateReset('PASSWORD', awaitedSearchParams.token);
|
|
15
|
+
|
|
16
|
+
if (validated) {
|
|
17
|
+
return <PasswordResetForm token={awaitedSearchParams.token} />;
|
|
18
|
+
} else {
|
|
19
|
+
return <div>Invalid or Expired Token</div>;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return <PasswordForm />;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export default Password;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Link } from '@sqrzro/ui/components';
|
|
6
|
+
import { Form, FormSubmit, PasswordFormField, TextFormField, useForm } from '@sqrzro/ui/forms';
|
|
7
|
+
|
|
8
|
+
import submit from './server';
|
|
9
|
+
|
|
10
|
+
function LoginForm(): React.ReactElement | null {
|
|
11
|
+
const { fieldProps, formData, formProps } = useForm({
|
|
12
|
+
onSubmit: submit,
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<Fragment>
|
|
17
|
+
<Form {...formProps}>
|
|
18
|
+
<TextFormField {...fieldProps('email')} hasAssistiveError />
|
|
19
|
+
<PasswordFormField {...fieldProps('password')} hasAssistiveError />
|
|
20
|
+
<FormSubmit>Sign In</FormSubmit>
|
|
21
|
+
<footer>
|
|
22
|
+
<Link
|
|
23
|
+
href={`/auth/password${formData.email ? `?email=${formData.email}` : ''}`}
|
|
24
|
+
>
|
|
25
|
+
Forgot Password?
|
|
26
|
+
</Link>
|
|
27
|
+
</footer>
|
|
28
|
+
</Form>
|
|
29
|
+
</Fragment>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default LoginForm;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { createSession, validateUser } from '@sqrzro/server/auth';
|
|
4
|
+
import { FormResponse, submitForm } from '@sqrzro/server/forms';
|
|
5
|
+
import { redirect } from 'next/navigation';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
import type { LoginFormFields } from './interfaces';
|
|
9
|
+
|
|
10
|
+
const schema = z
|
|
11
|
+
.object({
|
|
12
|
+
email: z.email(),
|
|
13
|
+
password: z.string(),
|
|
14
|
+
redirect: z.string().optional(),
|
|
15
|
+
})
|
|
16
|
+
.required({ email: true, password: true });
|
|
17
|
+
|
|
18
|
+
async function fn(data: LoginFormFields): Promise<void> {
|
|
19
|
+
const userID = await validateUser(data.email, data.password);
|
|
20
|
+
await createSession(userID);
|
|
21
|
+
|
|
22
|
+
redirect('/');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function submit(formData: LoginFormFields): FormResponse<void> {
|
|
26
|
+
return submitForm({
|
|
27
|
+
fn,
|
|
28
|
+
formData,
|
|
29
|
+
schema,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default submit;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Form, FormSubmit, TextFormField, useForm } from '@sqrzro/ui/forms';
|
|
6
|
+
|
|
7
|
+
import submit from './server';
|
|
8
|
+
|
|
9
|
+
function PasswordForm(): React.ReactElement | null {
|
|
10
|
+
const { fieldProps, formProps } = useForm({
|
|
11
|
+
onSubmit: submit,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<Fragment>
|
|
16
|
+
<Form {...formProps}>
|
|
17
|
+
<TextFormField {...fieldProps('email')} hasAssistiveError />
|
|
18
|
+
<FormSubmit isFullWidth>Send Email</FormSubmit>
|
|
19
|
+
</Form>
|
|
20
|
+
</Fragment>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export default PasswordForm;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { createReset } from '@sqrzro/server/auth';
|
|
4
|
+
import { FormResponse, submitForm } from '@sqrzro/server/forms';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
import type { PasswordFormFields } from './interfaces';
|
|
8
|
+
|
|
9
|
+
const schema = z
|
|
10
|
+
.object({
|
|
11
|
+
email: z.email(),
|
|
12
|
+
})
|
|
13
|
+
.required({ email: true });
|
|
14
|
+
|
|
15
|
+
async function fn(data: PasswordFormFields): Promise<void> {
|
|
16
|
+
const token = await createReset('PASSWORD', data.email);
|
|
17
|
+
console.log(token);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function submit(formData: PasswordFormFields): FormResponse<void> {
|
|
21
|
+
return submitForm({
|
|
22
|
+
fn,
|
|
23
|
+
formData,
|
|
24
|
+
schema,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default submit;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { Fragment } from 'react';
|
|
4
|
+
|
|
5
|
+
import { Form, FormSubmit, PasswordFormField, useForm } from '@sqrzro/ui/forms';
|
|
6
|
+
|
|
7
|
+
import submit from './server';
|
|
8
|
+
|
|
9
|
+
interface PasswordResetFormProps {
|
|
10
|
+
token: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function PasswordResetForm({ token }: Readonly<PasswordResetFormProps>): React.ReactElement | null {
|
|
14
|
+
const { fieldProps, formProps } = useForm({
|
|
15
|
+
defaults: { token },
|
|
16
|
+
onSubmit: submit,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<Fragment>
|
|
21
|
+
<Form {...formProps}>
|
|
22
|
+
<PasswordFormField {...fieldProps('password')} />
|
|
23
|
+
<FormSubmit isFullWidth>Reset Password</FormSubmit>
|
|
24
|
+
</Form>
|
|
25
|
+
</Fragment>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default PasswordResetForm;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use server';
|
|
2
|
+
|
|
3
|
+
import { updatePasswordWithToken } from '@sqrzro/server/auth';
|
|
4
|
+
import { FormResponse, submitForm } from '@sqrzro/server/forms';
|
|
5
|
+
import { redirect } from 'next/navigation';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
|
|
8
|
+
import type { PasswordResetFormFields } from './interfaces';
|
|
9
|
+
|
|
10
|
+
const schema = z
|
|
11
|
+
.object({
|
|
12
|
+
password: z.string(),
|
|
13
|
+
token: z.string().regex(/[A-Za-z0-9]{48}/u),
|
|
14
|
+
})
|
|
15
|
+
.required({ password: true, token: true });
|
|
16
|
+
|
|
17
|
+
async function fn(data: PasswordResetFormFields): Promise<void> {
|
|
18
|
+
await updatePasswordWithToken(data.token, data.password);
|
|
19
|
+
return redirect('/auth/login');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function submit(formData: PasswordResetFormFields): FormResponse<void> {
|
|
23
|
+
return submitForm({
|
|
24
|
+
fn,
|
|
25
|
+
formData,
|
|
26
|
+
schema,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default submit;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { validateSession } from '@sqrzro/server/auth';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
|
+
import type { NextRequest } from 'next/server';
|
|
4
|
+
|
|
5
|
+
async function handleProxy(request: NextRequest): Promise<NextResponse> {
|
|
6
|
+
const pathname = request.nextUrl.pathname;
|
|
7
|
+
const validated = await validateSession(request.cookies);
|
|
8
|
+
|
|
9
|
+
if (validated) {
|
|
10
|
+
if (pathname.startsWith('/auth')) {
|
|
11
|
+
return NextResponse.redirect(new URL('/', request.url));
|
|
12
|
+
}
|
|
13
|
+
return NextResponse.next();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (pathname.startsWith('/auth')) {
|
|
17
|
+
return NextResponse.next();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return NextResponse.redirect(new URL('/auth/login', request.url));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default handleProxy;
|