mirror of
https://github.com/documenso/documenso.git
synced 2025-11-16 01:32:06 +10:00
Merge branch 'main' into feat/disable-access-unverified-users
This commit is contained in:
@ -19,27 +19,14 @@ export const AuthenticatorApp = ({ isTwoFactorEnabled }: AuthenticatorAppProps)
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-4 flex flex-col justify-between gap-4 rounded-lg border p-4 md:flex-row md:items-center md:gap-8">
|
||||
<div className="flex-1">
|
||||
<p>Authenticator app</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 max-w-[50ch] text-sm">
|
||||
Create one-time passwords that serve as a secondary authentication method for confirming
|
||||
your identity when requested during the sign-in process.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{isTwoFactorEnabled ? (
|
||||
<Button variant="destructive" onClick={() => setModalState('disable')} size="sm">
|
||||
Disable 2FA
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => setModalState('enable')} size="sm">
|
||||
Enable 2FA
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-shrink-0">
|
||||
{isTwoFactorEnabled ? (
|
||||
<Button variant="destructive" onClick={() => setModalState('disable')}>
|
||||
Disable 2FA
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => setModalState('enable')}>Enable 2FA</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<EnableAuthenticatorAppDialog
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
@ -145,8 +146,8 @@ export const DisableAuthenticatorAppDialog = ({
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
@ -157,7 +158,7 @@ export const DisableAuthenticatorAppDialog = ({
|
||||
>
|
||||
Disable 2FA
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
|
||||
@ -15,6 +15,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
@ -190,15 +191,15 @@ export const EnableAuthenticatorAppDialog = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isSetupTwoFactorAuthenticationSubmitting}>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
@ -251,15 +252,15 @@ export const EnableAuthenticatorAppDialog = ({
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isEnableTwoFactorAuthenticationSubmitting}>
|
||||
Enable 2FA
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
))
|
||||
|
||||
@ -7,7 +7,6 @@ import { Button } from '@documenso/ui/primitives/button';
|
||||
import { ViewRecoveryCodesDialog } from './view-recovery-codes-dialog';
|
||||
|
||||
type RecoveryCodesProps = {
|
||||
// backupCodes: string[] | null;
|
||||
isTwoFactorEnabled: boolean;
|
||||
};
|
||||
|
||||
@ -16,22 +15,13 @@ export const RecoveryCodes = ({ isTwoFactorEnabled }: RecoveryCodesProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mt-4 flex flex-col justify-between gap-4 rounded-lg border p-4 md:flex-row md:items-center md:gap-8">
|
||||
<div className="flex-1">
|
||||
<p>Recovery Codes</p>
|
||||
|
||||
<p className="text-muted-foreground mt-2 max-w-[50ch] text-sm">
|
||||
Recovery codes are used to access your account in the event that you lose access to your
|
||||
authenticator app.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button onClick={() => setIsOpen(true)} disabled={!isTwoFactorEnabled} size="sm">
|
||||
View Codes
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
className="flex-shrink-0"
|
||||
onClick={() => setIsOpen(true)}
|
||||
disabled={!isTwoFactorEnabled}
|
||||
>
|
||||
View Codes
|
||||
</Button>
|
||||
|
||||
<ViewRecoveryCodesDialog
|
||||
key={isOpen ? 'open' : 'closed'}
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
@ -119,15 +120,15 @@ export const ViewRecoveryCodesDialog = ({ open, onOpenChange }: ViewRecoveryCode
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="flex w-full items-center justify-between">
|
||||
<Button type="button" variant="ghost" onClick={() => onOpenChange(false)}>
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => onOpenChange(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isViewRecoveryCodesSubmitting}>
|
||||
Continue
|
||||
</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
|
||||
@ -7,6 +7,7 @@ import { z } from 'zod';
|
||||
import type { User } from '@documenso/prisma/client';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZCurrentPasswordSchema, ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@ -22,18 +23,9 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export const ZPasswordFormSchema = z
|
||||
.object({
|
||||
currentPassword: z
|
||||
.string()
|
||||
.min(6, { message: 'Password should contain at least 6 characters' })
|
||||
.max(72, { message: 'Password should not contain more than 72 characters' }),
|
||||
password: z
|
||||
.string()
|
||||
.min(6, { message: 'Password should contain at least 6 characters' })
|
||||
.max(72, { message: 'Password should not contain more than 72 characters' }),
|
||||
repeatedPassword: z
|
||||
.string()
|
||||
.min(6, { message: 'Password should contain at least 6 characters' })
|
||||
.max(72, { message: 'Password should not contain more than 72 characters' }),
|
||||
currentPassword: ZCurrentPasswordSchema,
|
||||
password: ZPasswordSchema,
|
||||
repeatedPassword: ZPasswordSchema,
|
||||
})
|
||||
.refine((data) => data.password === data.repeatedPassword, {
|
||||
message: 'Passwords do not match',
|
||||
@ -145,7 +137,7 @@ export const PasswordForm = ({ className }: PasswordFormProps) => {
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<div className="mt-4">
|
||||
<div className="ml-auto mt-4">
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{isSubmitting ? 'Updating password...' : 'Update password'}
|
||||
</Button>
|
||||
|
||||
@ -121,10 +121,8 @@ export const ProfileForm = ({ className, user }: ProfileFormProps) => {
|
||||
<FormControl>
|
||||
<SignaturePad
|
||||
className="h-44 w-full"
|
||||
containerClassName={cn(
|
||||
'rounded-lg border bg-background',
|
||||
isSubmitting ? 'pointer-events-none opacity-50' : null,
|
||||
)}
|
||||
disabled={isSubmitting}
|
||||
containerClassName={cn('rounded-lg border bg-background')}
|
||||
defaultValue={user.signature ?? undefined}
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
|
||||
@ -8,6 +8,7 @@ import { z } from 'zod';
|
||||
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@ -23,8 +24,8 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
export const ZResetPasswordFormSchema = z
|
||||
.object({
|
||||
password: z.string().min(6).max(72),
|
||||
repeatedPassword: z.string().min(6).max(72),
|
||||
password: ZPasswordSchema,
|
||||
repeatedPassword: ZPasswordSchema,
|
||||
})
|
||||
.refine((data) => data.password === data.repeatedPassword, {
|
||||
path: ['repeatedPassword'],
|
||||
|
||||
@ -11,9 +11,16 @@ import { FcGoogle } from 'react-icons/fc';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ErrorCode, isErrorCode } from '@documenso/lib/next-auth/error-codes';
|
||||
import { ZCurrentPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@documenso/ui/primitives/dialog';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -43,7 +50,7 @@ const LOGIN_REDIRECT_PATH = '/documents';
|
||||
|
||||
export const ZSignInFormSchema = z.object({
|
||||
email: z.string().email().min(1),
|
||||
password: z.string().min(6).max(72),
|
||||
password: ZCurrentPasswordSchema,
|
||||
totpCode: z.string().trim().optional(),
|
||||
backupCode: z.string().trim().optional(),
|
||||
});
|
||||
@ -52,10 +59,11 @@ export type TSignInFormSchema = z.infer<typeof ZSignInFormSchema>;
|
||||
|
||||
export type SignInFormProps = {
|
||||
className?: string;
|
||||
initialEmail?: string;
|
||||
isGoogleSSOEnabled?: boolean;
|
||||
};
|
||||
|
||||
export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) => {
|
||||
export const SignInForm = ({ className, initialEmail, isGoogleSSOEnabled }: SignInFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const [isTwoFactorAuthenticationDialogOpen, setIsTwoFactorAuthenticationDialogOpen] =
|
||||
useState(false);
|
||||
@ -67,7 +75,7 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) =
|
||||
|
||||
const form = useForm<TSignInFormSchema>({
|
||||
values: {
|
||||
email: '',
|
||||
email: initialEmail ?? '',
|
||||
password: '',
|
||||
totpCode: '',
|
||||
backupCode: '',
|
||||
@ -115,7 +123,6 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) =
|
||||
|
||||
const result = await signIn('credentials', {
|
||||
...credentials,
|
||||
|
||||
callbackUrl: LOGIN_REDIRECT_PATH,
|
||||
redirect: false,
|
||||
});
|
||||
@ -285,21 +292,23 @@ export const SignInForm = ({ className, isGoogleSSOEnabled }: SignInFormProps) =
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<DialogFooter className="mt-4">
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
onClick={onToggleTwoFactorAuthenticationMethodClick}
|
||||
>
|
||||
{twoFactorAuthenticationMethod === 'totp'
|
||||
? 'Use Backup Code'
|
||||
: 'Use Authenticator'}
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</fieldset>
|
||||
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
onClick={onToggleTwoFactorAuthenticationMethodClick}
|
||||
>
|
||||
{twoFactorAuthenticationMethod === 'totp' ? 'Use Backup Code' : 'Use Authenticator'}
|
||||
</Button>
|
||||
|
||||
<Button type="submit" loading={isSubmitting}>
|
||||
{isSubmitting ? 'Signing in...' : 'Sign In'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -10,6 +10,7 @@ import { z } from 'zod';
|
||||
import { useAnalytics } from '@documenso/lib/client-only/hooks/use-analytics';
|
||||
import { TRPCClientError } from '@documenso/trpc/client';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { ZPasswordSchema } from '@documenso/trpc/server/auth-router/schema';
|
||||
import { cn } from '@documenso/ui/lib/utils';
|
||||
import { Button } from '@documenso/ui/primitives/button';
|
||||
import {
|
||||
@ -27,24 +28,32 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
||||
|
||||
const SIGN_UP_REDIRECT_PATH = '/documents';
|
||||
|
||||
export const ZSignUpFormSchema = z.object({
|
||||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
|
||||
email: z.string().email().min(1),
|
||||
password: z
|
||||
.string()
|
||||
.min(6, { message: 'Password should contain at least 6 characters' })
|
||||
.max(72, { message: 'Password should not contain more than 72 characters' }),
|
||||
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
|
||||
});
|
||||
export const ZSignUpFormSchema = z
|
||||
.object({
|
||||
name: z.string().trim().min(1, { message: 'Please enter a valid name.' }),
|
||||
email: z.string().email().min(1),
|
||||
password: ZPasswordSchema,
|
||||
signature: z.string().min(1, { message: 'We need your signature to sign documents' }),
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
const { name, email, password } = data;
|
||||
return !password.includes(name) && !password.includes(email.split('@')[0]);
|
||||
},
|
||||
{
|
||||
message: 'Password should not be common or based on personal information',
|
||||
},
|
||||
);
|
||||
|
||||
export type TSignUpFormSchema = z.infer<typeof ZSignUpFormSchema>;
|
||||
|
||||
export type SignUpFormProps = {
|
||||
className?: string;
|
||||
initialEmail?: string;
|
||||
isGoogleSSOEnabled?: boolean;
|
||||
};
|
||||
|
||||
export const SignUpForm = ({ className, isGoogleSSOEnabled }: SignUpFormProps) => {
|
||||
export const SignUpForm = ({ className, initialEmail, isGoogleSSOEnabled }: SignUpFormProps) => {
|
||||
const { toast } = useToast();
|
||||
const analytics = useAnalytics();
|
||||
const router = useRouter();
|
||||
@ -52,7 +61,7 @@ export const SignUpForm = ({ className, isGoogleSSOEnabled }: SignUpFormProps) =
|
||||
const form = useForm<TSignUpFormSchema>({
|
||||
values: {
|
||||
name: '',
|
||||
email: '',
|
||||
email: initialEmail ?? '',
|
||||
password: '',
|
||||
signature: '',
|
||||
},
|
||||
@ -169,6 +178,7 @@ export const SignUpForm = ({ className, isGoogleSSOEnabled }: SignUpFormProps) =
|
||||
<FormControl>
|
||||
<SignaturePad
|
||||
className="h-36 w-full"
|
||||
disabled={isSubmitting}
|
||||
containerClassName="mt-2 rounded-lg border bg-background"
|
||||
onChange={(v) => onChange(v ?? '')}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user