@arch-cadre/auth 1.0.7 → 1.0.9
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/dist/actions/basic.d.ts +1 -1
- package/dist/actions/email.d.ts +1 -1
- package/dist/actions/index.cjs +2 -2
- package/dist/actions/index.d.ts +2 -2
- package/dist/actions/index.mjs +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/routes.cjs +7 -7
- package/dist/routes.mjs +7 -7
- package/dist/ui/forgot-password/components.cjs +2 -2
- package/dist/ui/forgot-password/components.mjs +2 -2
- package/dist/ui/forgot-password/page.cjs +1 -1
- package/dist/ui/forgot-password/page.mjs +1 -1
- package/dist/ui/reset-password/components.cjs +2 -2
- package/dist/ui/reset-password/components.mjs +2 -2
- package/dist/ui/reset-password/page.cjs +1 -1
- package/dist/ui/reset-password/page.mjs +1 -1
- package/dist/ui/reset-password/verify-email/components.cjs +2 -2
- package/dist/ui/reset-password/verify-email/components.mjs +2 -2
- package/dist/ui/reset-password/verify-email/page.cjs +1 -1
- package/dist/ui/reset-password/verify-email/page.mjs +1 -1
- package/dist/ui/signin/components.cjs +2 -2
- package/dist/ui/signin/components.d.ts +1 -1
- package/dist/ui/signin/components.mjs +2 -2
- package/dist/ui/signin/page.cjs +1 -1
- package/dist/ui/signin/page.mjs +1 -1
- package/dist/ui/signup/components.cjs +2 -2
- package/dist/ui/signup/components.d.ts +1 -1
- package/dist/ui/signup/components.mjs +2 -2
- package/dist/ui/signup/page.cjs +1 -1
- package/dist/ui/signup/page.mjs +1 -1
- package/dist/ui/verify-email/components.cjs +2 -2
- package/dist/ui/verify-email/components.mjs +2 -2
- package/dist/ui/verify-email/page.cjs +1 -1
- package/dist/ui/verify-email/page.mjs +1 -1
- package/package.json +8 -7
- package/src/actions/basic.ts +53 -0
- package/src/actions/email.ts +241 -0
- package/src/actions/index.ts +2 -0
- package/src/index.ts +13 -0
- package/src/intl.d.ts +13 -0
- package/src/routes.ts +55 -0
- package/src/types.ts +12 -0
- package/src/ui/forgot-password/components.tsx +101 -0
- package/src/ui/forgot-password/page.tsx +6 -0
- package/src/ui/layout.tsx +42 -0
- package/src/ui/reset-password/components.tsx +128 -0
- package/src/ui/reset-password/page.tsx +25 -0
- package/src/ui/reset-password/verify-email/components.tsx +110 -0
- package/src/ui/reset-password/verify-email/page.tsx +25 -0
- package/src/ui/signin/components.tsx +215 -0
- package/src/ui/signin/page.tsx +43 -0
- package/src/ui/signup/components.tsx +230 -0
- package/src/ui/signup/page.tsx +35 -0
- package/src/ui/verify-email/components.tsx +135 -0
- package/src/ui/verify-email/page.tsx +36 -0
- package/src/validation.ts +61 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useTranslation } from "@arch-cadre/intl";
|
|
3
|
+
import { Button } from "@arch-cadre/ui/components/button";
|
|
4
|
+
import { Field, FieldError, FieldGroup } from "@arch-cadre/ui/components/field";
|
|
5
|
+
import {
|
|
6
|
+
InputOTP,
|
|
7
|
+
InputOTPGroup,
|
|
8
|
+
InputOTPSlot,
|
|
9
|
+
} from "@arch-cadre/ui/components/input-otp";
|
|
10
|
+
import { cn } from "@arch-cadre/ui/lib/utils";
|
|
11
|
+
import { Loader } from "@arch-cadre/ui/shared/loader";
|
|
12
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
13
|
+
import { AlertCircle } from "lucide-react";
|
|
14
|
+
import React, { useActionState, useEffect, useState } from "react";
|
|
15
|
+
import { Controller, useForm } from "react-hook-form";
|
|
16
|
+
import { toast } from "sonner";
|
|
17
|
+
import {
|
|
18
|
+
resendEmailVerificationCodeAction,
|
|
19
|
+
verifyEmailAction,
|
|
20
|
+
} from "../../actions/index.js";
|
|
21
|
+
import { type VerifyEmailInput, verifyEmailSchema } from "../../validation.js";
|
|
22
|
+
|
|
23
|
+
export function EmailVerificationForm({ email = "" }) {
|
|
24
|
+
const [generalError, setGeneralError] = useState("");
|
|
25
|
+
const { t } = useTranslation();
|
|
26
|
+
|
|
27
|
+
const form = useForm<VerifyEmailInput>({
|
|
28
|
+
resolver: zodResolver(verifyEmailSchema),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
async function onSubmit(data: VerifyEmailInput) {
|
|
32
|
+
setGeneralError("");
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const response = await verifyEmailAction(data);
|
|
36
|
+
|
|
37
|
+
if (response.error) {
|
|
38
|
+
setGeneralError(response.message || t("error_occurred"));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
toast(response.message);
|
|
43
|
+
} catch (_error) {
|
|
44
|
+
// setGeneralError(t("error_occurred"));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className={cn("flex flex-col space-y-6")}>
|
|
50
|
+
<div className="space-y-2 text-center">
|
|
51
|
+
<h1 className="text-3xl font-semibold">{t("Verify email")}</h1>
|
|
52
|
+
<p className="text-muted-foreground">
|
|
53
|
+
{t("We've sent a verification code to your email address: {email}", {
|
|
54
|
+
email,
|
|
55
|
+
})}
|
|
56
|
+
</p>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div className="grid gap-6">
|
|
60
|
+
{generalError && (
|
|
61
|
+
<div className="flex gap-2 p-3 bg-red-50 border border-red-200 rounded-lg">
|
|
62
|
+
<AlertCircle className="w-4 h-4 text-red-600 flex-shrink-0 mt-0.5" />
|
|
63
|
+
<p className="text-sm text-red-600">{generalError}</p>
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
66
|
+
|
|
67
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="grid gap-5">
|
|
68
|
+
<FieldGroup className="w-full mx-auto">
|
|
69
|
+
<Controller
|
|
70
|
+
name="code"
|
|
71
|
+
control={form.control}
|
|
72
|
+
render={({ field, fieldState }) => (
|
|
73
|
+
<Field className="w-full" data-invalid={fieldState.invalid}>
|
|
74
|
+
<InputOTP
|
|
75
|
+
{...field}
|
|
76
|
+
className="mx-auto w-full"
|
|
77
|
+
maxLength={6}
|
|
78
|
+
autoFocus={true}
|
|
79
|
+
inputMode="text"
|
|
80
|
+
>
|
|
81
|
+
<InputOTPGroup className="mx-auto">
|
|
82
|
+
<InputOTPSlot index={0} />
|
|
83
|
+
<InputOTPSlot index={1} />
|
|
84
|
+
<InputOTPSlot index={2} />
|
|
85
|
+
<InputOTPSlot index={3} />
|
|
86
|
+
<InputOTPSlot index={4} />
|
|
87
|
+
<InputOTPSlot index={5} />
|
|
88
|
+
</InputOTPGroup>
|
|
89
|
+
</InputOTP>
|
|
90
|
+
{fieldState.invalid && (
|
|
91
|
+
<FieldError errors={[fieldState.error]} />
|
|
92
|
+
)}
|
|
93
|
+
</Field>
|
|
94
|
+
)}
|
|
95
|
+
/>
|
|
96
|
+
</FieldGroup>
|
|
97
|
+
|
|
98
|
+
<Button disabled={form.formState.isSubmitting} className="w-full">
|
|
99
|
+
{form.formState.isSubmitting
|
|
100
|
+
? t("Please wait...")
|
|
101
|
+
: t("Verify Email")}
|
|
102
|
+
{form.formState.isSubmitting && <Loader variant="dark" />}
|
|
103
|
+
</Button>
|
|
104
|
+
</form>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const resendEmailInitialState = {
|
|
111
|
+
message: "",
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export function ResendEmailVerificationCodeForm() {
|
|
115
|
+
const { t } = useTranslation();
|
|
116
|
+
const [state, action, isPending] = useActionState(
|
|
117
|
+
resendEmailVerificationCodeAction,
|
|
118
|
+
resendEmailInitialState,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
if (state.message) {
|
|
123
|
+
toast.success(state.message);
|
|
124
|
+
}
|
|
125
|
+
}, [state]);
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<form action={action}>
|
|
129
|
+
<Button disabled={isPending} variant="ghost" className="w-full">
|
|
130
|
+
{isPending ? t("Please wait...") : t("Resend verification email")}
|
|
131
|
+
{isPending && <Loader variant="default" />}
|
|
132
|
+
</Button>
|
|
133
|
+
</form>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCurrentSession,
|
|
3
|
+
getUserEmailVerificationRequestFromRequest,
|
|
4
|
+
} from "@arch-cadre/core/server";
|
|
5
|
+
import { redirect } from "next/navigation";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
EmailVerificationForm,
|
|
10
|
+
ResendEmailVerificationCodeForm,
|
|
11
|
+
} from "./components.js";
|
|
12
|
+
|
|
13
|
+
export default async function Page() {
|
|
14
|
+
const { user } = await getCurrentSession();
|
|
15
|
+
|
|
16
|
+
if (user === null) {
|
|
17
|
+
return redirect("/signin");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// TODO: Ideally we'd sent a new verification email automatically if the previous one is expired,
|
|
21
|
+
// but we can't set cookies inside server components.
|
|
22
|
+
const verificationRequest =
|
|
23
|
+
await getUserEmailVerificationRequestFromRequest();
|
|
24
|
+
|
|
25
|
+
if (verificationRequest === null && user.emailVerifiedAt) {
|
|
26
|
+
return redirect("/?verified");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-3">
|
|
31
|
+
<EmailVerificationForm email={verificationRequest?.email ?? user.email} />
|
|
32
|
+
|
|
33
|
+
<ResendEmailVerificationCodeForm />
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
// Auth validation schemas - CLEAN (No DB dependencies for client-side)
|
|
4
|
+
export const loginSchema = z.object({
|
|
5
|
+
email: z.string().email("Invalid email address"),
|
|
6
|
+
password: z.string().min(8),
|
|
7
|
+
remember: z.boolean().optional(),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const registerSchema = z.object({
|
|
11
|
+
username: z.string().min(2, "Name must be at least 2 characters"),
|
|
12
|
+
email: z.string().email("Invalid email address"),
|
|
13
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
14
|
+
terms: z.boolean().refine((val) => val === true, "You must accept the terms"),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const forgotPasswordSchema = z.object({
|
|
18
|
+
email: z.string().email("Invalid email address"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const resetPasswordSchema = z
|
|
22
|
+
.object({
|
|
23
|
+
password: z.string().min(8, "Password must be at least 8 characters"),
|
|
24
|
+
confirm: z.string(),
|
|
25
|
+
})
|
|
26
|
+
.refine((data) => data.password === data.confirm, {
|
|
27
|
+
message: "Passwords do not match",
|
|
28
|
+
path: ["confirm"],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const verifyEmailSchema = z.object({
|
|
32
|
+
code: z.string().min(6).max(6),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// mfa validation schemas
|
|
36
|
+
export const totpSetupSchema = z.object({
|
|
37
|
+
code: z.string().regex(/^\d{6}$/, "Code must be 6 digits"),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
export const totpVerifySchema = z.object({
|
|
41
|
+
code: z.string().regex(/^\d{6}$/, "Code must be 6 digits"),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
export const passkeysSetupSchema = z.object({
|
|
45
|
+
name: z.string().min(1, "Passkey name is required"),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const recoveryCodeVerifySchema = z.object({
|
|
49
|
+
code: z.string().min(16, "Recovery code is required").max(16),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Type exports for use in components
|
|
53
|
+
export type LoginInput = z.infer<typeof loginSchema>;
|
|
54
|
+
export type RegisterInput = z.infer<typeof registerSchema>;
|
|
55
|
+
export type ForgotPasswordInput = z.infer<typeof forgotPasswordSchema>;
|
|
56
|
+
export type ResetPasswordInput = z.infer<typeof resetPasswordSchema>;
|
|
57
|
+
export type TOTPSetupInput = z.infer<typeof totpSetupSchema>;
|
|
58
|
+
export type TOTPVerifyInput = z.infer<typeof totpVerifySchema>;
|
|
59
|
+
export type PasskeysSetupInput = z.infer<typeof passkeysSetupSchema>;
|
|
60
|
+
export type VerifyEmailInput = z.infer<typeof verifyEmailSchema>;
|
|
61
|
+
export type RecoveryVerifyInput = z.infer<typeof recoveryCodeVerifySchema>;
|