@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.
Files changed (81) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/.turbo/turbo-dev.log +45 -46
  3. package/dist/components/Auth/index.d.ts +4 -32
  4. package/dist/components/Auth/index.js +19 -40
  5. package/dist/components/AuthComponent/index.d.ts +6 -0
  6. package/dist/components/AuthComponent/index.js +21 -0
  7. package/dist/components/LoginForm/index.d.ts +2 -1
  8. package/dist/components/LoginForm/index.js +22 -3
  9. package/dist/components/LogoutButton/index.d.ts +1 -2
  10. package/dist/components/LogoutButton/index.js +5 -11
  11. package/dist/components/LogoutButton/server.d.ts +2 -0
  12. package/dist/components/LogoutButton/server.js +8 -0
  13. package/dist/components/MFAForm/index.js +1 -1
  14. package/dist/components/Password/index.d.ts +5 -0
  15. package/dist/components/Password/index.js +18 -0
  16. package/dist/components/PasswordForm/index.js +1 -1
  17. package/dist/components/PasswordResetForm/index.js +1 -1
  18. package/dist/forms/LoginForm/client.d.ts +2 -0
  19. package/dist/forms/LoginForm/client.js +13 -0
  20. package/dist/forms/LoginForm/index.d.ts +2 -0
  21. package/dist/forms/LoginForm/index.js +13 -0
  22. package/dist/forms/LoginForm/interfaces.d.ts +5 -0
  23. package/dist/forms/LoginForm/interfaces.js +1 -0
  24. package/dist/forms/LoginForm/server.d.ts +4 -0
  25. package/dist/forms/LoginForm/server.js +25 -0
  26. package/dist/forms/PasswordForm/client.d.ts +2 -0
  27. package/dist/forms/PasswordForm/client.js +13 -0
  28. package/dist/forms/PasswordForm/index.d.ts +2 -0
  29. package/dist/forms/PasswordForm/index.js +12 -0
  30. package/dist/forms/PasswordForm/interfaces.d.ts +3 -0
  31. package/dist/forms/PasswordForm/interfaces.js +1 -0
  32. package/dist/forms/PasswordForm/server.d.ts +4 -0
  33. package/dist/forms/PasswordForm/server.js +21 -0
  34. package/dist/forms/PasswordResetForm/client.d.ts +5 -0
  35. package/dist/forms/PasswordResetForm/client.js +14 -0
  36. package/dist/forms/PasswordResetForm/index.d.ts +5 -0
  37. package/dist/forms/PasswordResetForm/index.js +13 -0
  38. package/dist/forms/PasswordResetForm/interfaces.d.ts +4 -0
  39. package/dist/forms/PasswordResetForm/interfaces.js +1 -0
  40. package/dist/forms/PasswordResetForm/server.d.ts +4 -0
  41. package/dist/forms/PasswordResetForm/server.js +23 -0
  42. package/dist/handle-proxy.d.ts +4 -0
  43. package/dist/handle-proxy.js +17 -0
  44. package/dist/hooks/useLoginForm.js +3 -0
  45. package/dist/index.d.ts +1 -5
  46. package/dist/index.js +1 -2
  47. package/dist/interfaces.d.ts +10 -0
  48. package/dist/interfaces.js +1 -0
  49. package/dist/proxy.d.ts +0 -0
  50. package/dist/proxy.js +1 -0
  51. package/dist/server.d.ts +6 -1
  52. package/dist/server.js +12 -3
  53. package/package.json +14 -11
  54. package/src/components/Auth/index.tsx +31 -75
  55. package/src/components/LogoutButton/index.tsx +7 -19
  56. package/src/components/LogoutButton/server.ts +11 -0
  57. package/src/components/Password/index.tsx +26 -0
  58. package/src/forms/LoginForm/index.tsx +33 -0
  59. package/src/forms/LoginForm/interfaces.ts +5 -0
  60. package/src/forms/LoginForm/server.ts +33 -0
  61. package/src/forms/PasswordForm/index.tsx +24 -0
  62. package/src/forms/PasswordForm/interfaces.ts +3 -0
  63. package/src/forms/PasswordForm/server.ts +28 -0
  64. package/src/forms/PasswordResetForm/index.tsx +29 -0
  65. package/src/forms/PasswordResetForm/interfaces.ts +4 -0
  66. package/src/forms/PasswordResetForm/server.ts +30 -0
  67. package/src/handle-proxy.ts +23 -0
  68. package/src/index.ts +1 -7
  69. package/src/interfaces.ts +10 -0
  70. package/tsconfig.json +6 -5
  71. package/src/components/LoginForm/index.tsx +0 -44
  72. package/src/components/MFAForm/index.tsx +0 -47
  73. package/src/components/MFAPage/index.tsx +0 -54
  74. package/src/components/MFASetup/index.tsx +0 -43
  75. package/src/components/MFASetupForm/index.tsx +0 -78
  76. package/src/components/PasswordForm/index.tsx +0 -113
  77. package/src/components/PasswordPage/index.tsx +0 -20
  78. package/src/components/PasswordResetForm/index.tsx +0 -45
  79. package/src/hooks/useLoginForm.ts +0 -33
  80. package/src/hooks/useMFAForm.ts +0 -56
  81. 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,4 @@
1
+ export interface PasswordResetFormFields {
2
+ password: string;
3
+ token: string;
4
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { FormResponse } from '@sqrzro/server/forms';
2
+ import type { PasswordResetFormFields } from './interfaces';
3
+ declare function submit(formData: PasswordResetFormFields): FormResponse<void>;
4
+ export default submit;
@@ -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,4 @@
1
+ import { NextResponse } from 'next/server';
2
+ import type { NextRequest } from 'next/server';
3
+ declare function handleProxy(request: NextRequest): Promise<NextResponse>;
4
+ export default handleProxy;
@@ -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 type { UseMFAFormArgs, UseMFAFormReturn } from './hooks/useMFAForm';
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 useMFAForm } from './hooks/useMFAForm';
3
+ export { default as handleProxy } from './handle-proxy';
@@ -0,0 +1,10 @@
1
+ export interface AuthClassNames {
2
+ actions: string;
3
+ footer: string;
4
+ form: string;
5
+ link: string;
6
+ logo: string;
7
+ root: string;
8
+ panel: string;
9
+ title: string;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
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
- login?: () => Promise<void>;
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": "2.0.0-r19.9",
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/components": "r19",
9
- "@sqrzro/hooks": "r19",
10
- "@sqrzro/interfaces": "r19",
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.0.7",
17
- "typescript": "^5.7.3"
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 -p tsconfig.json",
21
- "dev": "tsc -p tsconfig.json --watch",
23
+ "build": "tsc",
24
+ "dev": "tsc --watch",
22
25
  "start": "pnpm dev"
23
26
  }
24
27
  }
@@ -1,91 +1,47 @@
1
- import { tw } from '@sqrzro/components';
2
- import type { ClassNameProps } from '@sqrzro/components';
3
- import { notFound } from 'next/navigation';
1
+ import { Suspense } from 'react';
4
2
 
5
- import LoginForm from '../LoginForm';
6
- import MFAPage from '../MFAPage';
7
- import PasswordPage from '../PasswordPage';
3
+ import { notFound } from 'next/navigation';
8
4
 
9
- import { registerAuthEvents } from '../../server';
10
- import type { ScopeObject } from '../../server';
5
+ import LoginForm from '../../forms/LoginForm';
6
+ import Password from '../Password';
11
7
 
12
- export interface AuthClassNames {
13
- actions: string;
14
- footer: string;
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
- export interface AuthProps extends ClassNameProps<AuthClassNames> {
24
- logo?: React.ReactElement;
25
- onLogin?: () => Promise<void>;
26
- onPassword?: (email: string, token: string) => Promise<boolean>;
27
- params: { auth: string[] };
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
- interface AuthPageProps {
33
- classNames?: Partial<AuthClassNames>;
34
- searchParams: AuthProps['searchParams'];
35
- }
19
+ if (!Array.isArray(awaitedParams.auth) || awaitedParams.auth.length !== 1) {
20
+ return notFound();
21
+ }
36
22
 
37
- /**
38
- * To make it easier for consumers to use Auth in their projects, this auth component uses a
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
- await registerAuthEvents({ login: onLogin, password: onPassword });
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 className={tw(classNames?.root)}>
82
- <div className={tw(classNames?.logo)}>{logo}</div>
83
- <div className={tw(classNames?.panel)}>
84
- {getPage(auth[0], {
85
- classNames,
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 { useRouter } from 'next/navigation';
3
+ import { useTransition } from 'react';
4
4
 
5
- import { logout } from '../../server';
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
- children = 'Log out',
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
- <form action={handleLogout} className="contents">
28
- <button type="submit">{children}</button>
29
- </form>
15
+ <button disabled={isLoading} onClick={() => startTransition(handle)}>
16
+ {children}
17
+ </button>
30
18
  );
31
19
  }
32
20
 
@@ -0,0 +1,11 @@
1
+ 'use server';
2
+
3
+ import { deleteSession } from '@sqrzro/server/auth';
4
+ import { redirect } from 'next/navigation';
5
+
6
+ async function handle(): Promise<void> {
7
+ await deleteSession();
8
+ return redirect('/auth/login');
9
+ }
10
+
11
+ export default handle;
@@ -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,5 @@
1
+ export interface LoginFormFields {
2
+ email: string;
3
+ password: string;
4
+ redirect?: string;
5
+ }
@@ -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,3 @@
1
+ export interface PasswordFormFields {
2
+ email: string;
3
+ }
@@ -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,4 @@
1
+ export interface PasswordResetFormFields {
2
+ password: string;
3
+ token: string;
4
+ }
@@ -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;