Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
sneko committed Jan 14, 2025
2 parents 5a877f3 + 1a3dc7a commit 57f57d9
Show file tree
Hide file tree
Showing 6 changed files with 40 additions and 21 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"i18n-ally.displayLanguage": "fr",
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": ["src/i18n"],
"i18n-ally.sourceLanguage": "fr",
"jest.jestCommandLine": "npm run test:unit --",
Expand Down
18 changes: 12 additions & 6 deletions src/app/(visitor-only)/auth/sign-in/SignInForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Typography from '@mui/material/Typography';
import { Mutex } from 'locks';
import NextLink from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';
import React, { useState } from 'react';
import React, { useRef, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';

Expand All @@ -27,16 +27,18 @@ import { ErrorAlert } from '@ad/src/components/ErrorAlert';
import { SignInPrefillSchemaType, SignInSchema, SignInSchemaType } from '@ad/src/models/actions/auth';
import {
BusinessError,
UnexpectedError,
authCredentialsRequiredError,
authFatalError,
authNoCredentialsMatchError,
authRetriableError,
internalServerErrorError,
} from '@ad/src/models/entities/errors';
import { signIn } from '@ad/src/proxies/next-auth/react';
import { linkRegistry } from '@ad/src/utils/routes/registry';

function errorCodeToError(errorCode: string): BusinessError | null {
let error: BusinessError | null;
function errorCodeToError(errorCode: string): BusinessError | UnexpectedError {
let error: BusinessError | UnexpectedError;

switch (errorCode) {
case authCredentialsRequiredError.code:
Expand All @@ -46,7 +48,8 @@ function errorCodeToError(errorCode: string): BusinessError | null {
error = authNoCredentialsMatchError;
break;
case 'undefined':
error = null;
// Probably the server has thrown something that is not a basic `Error` object
error = internalServerErrorError;
break;
default:
error = authRetriableError;
Expand All @@ -70,10 +73,11 @@ export function SignInForm({ prefill }: { prefill?: SignInPrefillSchemaType }) {
const [showSessionEndBlock, setShowSessionEndBlock] = useState<boolean>(sessionEnd);
const [showRegisteredBlock, setShowRegisteredBlock] = useState<boolean>(registered);

const [error, setError] = useState<BusinessError | null>(() => {
const [error, setError] = useState<BusinessError | UnexpectedError | null>(() => {
return attemptErrorCode ? errorCodeToError(attemptErrorCode) : null;
});
const [mutex] = useState<Mutex>(new Mutex());
const formContainerRef = useRef<HTMLFormElement | null>(null); // This is used to scroll to the error messages

const {
register,
Expand Down Expand Up @@ -118,6 +122,7 @@ export function SignInForm({ prefill }: { prefill?: SignInPrefillSchemaType }) {

if (result && !result.ok && result.error) {
setError(errorCodeToError(result.error));
formContainerRef.current?.scrollIntoView({ behavior: 'smooth' });
} else if (result && result.ok && result.url) {
setError(null);

Expand All @@ -128,6 +133,7 @@ export function SignInForm({ prefill }: { prefill?: SignInPrefillSchemaType }) {
router.push(linkRegistry.get('dashboard', undefined));
} else {
setError(authFatalError);
formContainerRef.current?.scrollIntoView({ behavior: 'smooth' });
}
} finally {
// Unlock to allow a new submit
Expand All @@ -140,7 +146,7 @@ export function SignInForm({ prefill }: { prefill?: SignInPrefillSchemaType }) {
const handleMouseDownPassword = () => setShowPassword(!showPassword);

return (
<BaseForm handleSubmit={enhancedHandleSubmit} onSubmit={onSubmit} control={control} ariaLabel="se connecter">
<BaseForm handleSubmit={enhancedHandleSubmit} onSubmit={onSubmit} control={control} ariaLabel="se connecter" innerRef={formContainerRef}>
{(!!error || showSessionEndBlock || showRegisteredBlock) && (
<Grid item xs={12}>
{!!error && <ErrorAlert errors={[error]} />}
Expand Down
12 changes: 9 additions & 3 deletions src/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const connectSrcValues = ["'self'"];
const fontSrcValues = ["'self'", 'https:', 'data:'];
const imgSrcValues = ["'self'", 'data:'];
const styleSrcValues = ["'self'", 'https:'];
const workerSrcValues: string[] = [];

// Make sure no `http` url are used
// Note: we scoped this to production some browsers enable it by default, which causes issues with `http://localhost:3000` upgrading links, breaking scripts and tags
Expand All @@ -31,6 +32,9 @@ if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
const inferedSentryUrl = `${sentryDsn.protocol}//${sentryDsn.host}/`;

connectSrcValues.push(inferedSentryUrl);

// The SessionReplay integration uses a worker for compression
workerSrcValues.push("'self'", 'blob:');
}

if (process.env.NODE_ENV === 'development') {
Expand Down Expand Up @@ -65,9 +69,11 @@ function formatSecurityHeaders(nonce?: string) {
' '
)};object-src 'none';script-src ${`'nonce-${nonce}'`} ${scriptSrcValues.join(' ')};script-src-attr 'none';connect-src ${connectSrcValues.join(
' '
)};style-src ${styleSrcValues.join(' ')};style-src-elem 'unsafe-inline' ${styleSrcValues.join(' ')};style-src-attr 'self' ${
libraryCompatibilityWorkaround ? "'unsafe-inline'" : ''
}${upgradeInsecureRequests ? ';upgrade-insecure-requests' : ''}`,
)};worker-src ${workerSrcValues.join(' ')};style-src ${styleSrcValues.join(' ')};style-src-elem 'unsafe-inline' ${styleSrcValues.join(
' '
)};style-src-attr 'self' ${libraryCompatibilityWorkaround ? "'unsafe-inline'" : ''}${
upgradeInsecureRequests ? ';upgrade-insecure-requests' : ''
}`,
'Origin-Agent-Cluster': '?1',
'Referrer-Policy': 'no-referrer',
'Strict-Transport-Security': 'max-age=15552000; includeSubDomains',
Expand Down
10 changes: 6 additions & 4 deletions src/pages/api/auth/[...nextauth].ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ export const nextAuthOptions: NextAuthOptions = {
id: 'credentials',
name: 'Connexion',
async authorize(credentials: any): Promise<TokenUserSchemaType> {
// Below the thrown object must be a basic `Error` otherwise it won't be passed on the frontend

// TODO: parse with zod SignInSchema
if (!credentials.email || !credentials.password) {
throw authCredentialsRequiredError.code;
throw new Error(authCredentialsRequiredError.code);
}

const user = await prisma.user.findUnique({
Expand All @@ -51,12 +53,12 @@ export const nextAuthOptions: NextAuthOptions = {
});

if (!user || !user.Secrets) {
throw authNoCredentialsMatchError.code;
throw new Error(authNoCredentialsMatchError.code);
}

const matchPassword = await bcrypt.compare(credentials.password, user.Secrets.passwordHash);
if (!matchPassword) {
throw authNoCredentialsMatchError.code;
throw new Error(authNoCredentialsMatchError.code);
}

const tokenUserParse = TokenUserSchema.safeParse({
Expand All @@ -67,7 +69,7 @@ export const nextAuthOptions: NextAuthOptions = {
});

if (!tokenUserParse.success) {
throw authFatalError.code;
throw new Error(authFatalError.code);
}

return tokenUserParse.data;
Expand Down
2 changes: 1 addition & 1 deletion src/server/routers/declaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ export const declarationRouter = router({
// Fill with unique values
for (const previousDeclaration of previousDeclarations) {
if (!placeholder.clientId.includes(previousDeclaration.clientId)) placeholder.clientId.push(previousDeclaration.clientId);
if (!placeholder.placeName.includes(previousDeclaration.clientId)) placeholder.placeName.push(previousDeclaration.placeName);
if (!placeholder.placeName.includes(previousDeclaration.placeName)) placeholder.placeName.push(previousDeclaration.placeName);
if (!placeholder.placeCapacity.includes(previousDeclaration.placeCapacity)) placeholder.placeCapacity.push(previousDeclaration.placeCapacity);
if (!placeholder.managerName.includes(previousDeclaration.managerName)) placeholder.managerName.push(previousDeclaration.managerName);
if (!placeholder.managerTitle.includes(previousDeclaration.managerTitle)) placeholder.managerTitle.push(previousDeclaration.managerTitle);
Expand Down
18 changes: 11 additions & 7 deletions src/utils/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,20 @@ export interface RowForForm<T, E> {
}

export function recursiveCountErrors(errors: FieldError | Merge<FieldError, FieldErrorsImpl<any>> | undefined): number {
// [WORKDAROUND] We cannot rely on `control._fields` that does not contain arrays and so their children
// Also `control._names` does not list the children so we ended with a hack below just to keep a uniform logic
// Ref: https://github.com/orgs/react-hook-form/discussions/12527

let count = 0;

if (!errors) {
return count;
}
if (errors.message) {
count += 1;
}
// `null` is of type `object` so checking this
// Also, skip properties that would refer to the DOM element of an input since it has circular loops due to parent/children properties
if (typeof errors === 'object' && errors && !(errors instanceof Element)) {
// A field could be named `message` so we make sure here it's an error and not a field to parse
if (typeof errors.message === 'string') {
count += 1;
}

if (typeof errors === 'object') {
if (Array.isArray(errors)) {
errors.forEach((error) => {
count += recursiveCountErrors(error);
Expand Down

0 comments on commit 57f57d9

Please sign in to comment.