diff --git a/backend/src/auth/models/authenticated-user.models.ts b/backend/src/auth/models/authenticated-user.models.ts index bc3e29d54e..2c4c64ace4 100644 --- a/backend/src/auth/models/authenticated-user.models.ts +++ b/backend/src/auth/models/authenticated-user.models.ts @@ -1,4 +1,4 @@ -import { User } from '@supabase/supabase-js'; +import { User as SupabaseUser } from '@supabase/supabase-js'; export enum UserRole { AUTHENTICATED = 'authenticated', @@ -6,6 +6,24 @@ export enum UserRole { ANON = 'anon', // Anonymous } +export type User = SupabaseUser; + +export interface AnonymousUser extends User { + role: UserRole.ANON; + is_anonymous: true; +} + export interface AuthenticatedUser extends User { - role: UserRole; + role: UserRole.AUTHENTICATED; + is_anonymous: false; +} + +export function isAnonymousUser(user: User | null): user is AnonymousUser { + return user?.role === UserRole.ANON && user.is_anonymous === true; +} + +export function isAuthenticatedUser( + user: User | null +): user is AuthenticatedUser { + return user?.role === UserRole.AUTHENTICATED && user.is_anonymous === false; } diff --git a/backend/src/auth/services/auth.service.ts b/backend/src/auth/services/auth.service.ts index 6d3050545c..ff14baa62b 100644 --- a/backend/src/auth/services/auth.service.ts +++ b/backend/src/auth/services/auth.service.ts @@ -4,6 +4,8 @@ import { and, eq, inArray, sql, SQL, SQLWrapper } from 'drizzle-orm'; import DatabaseService from '../../common/services/database.service'; import { AuthenticatedUser, + isAuthenticatedUser, + User, UserRole, } from '../models/authenticated-user.models'; import { @@ -163,7 +165,7 @@ export class AuthService { * @param doNotThrow vrai pour ne pas générer une exception */ async verifieAccesRestreintCollectivite( - user: AuthenticatedUser, + user: User, collectiviteId: number, doNotThrow?: boolean ): Promise { @@ -172,7 +174,7 @@ export class AuthService { `Rôle de service détecté, accès autorisé à toutes les collectivités` ); return true; - } else if (user.role === UserRole.AUTHENTICATED) { + } else if (isAuthenticatedUser(user)) { let authorise = false; const collectivite = await this.collectiviteService.getCollectivite( collectiviteId diff --git a/backend/src/trpc/trpc.service.ts b/backend/src/trpc/trpc.service.ts index afb2d2e1c6..6ef8b4eba1 100644 --- a/backend/src/trpc/trpc.service.ts +++ b/backend/src/trpc/trpc.service.ts @@ -1,7 +1,10 @@ import { Injectable } from '@nestjs/common'; import { initTRPC, TRPCError } from '@trpc/server'; +import { + isAnonymousUser, + isAuthenticatedUser, +} from '../auth/models/authenticated-user.models'; import { Context } from './trpc.router'; -import { AuthenticatedUser } from '../auth/models/authenticated-user.models'; @Injectable() export class TrpcService { @@ -14,9 +17,26 @@ export class TrpcService { publicProcedure = this.trpc.procedure; + anonProcedure = this.trpc.procedure.use( + this.trpc.middleware(({ next, ctx }) => { + const anonUser = ctx.user; + + if (!isAnonymousUser(anonUser)) { + throw new TRPCError({ + code: 'UNAUTHORIZED', + message: 'Not anonymous', + }); + } + + return next({ ctx: { user: anonUser } }); + }) + ); + authedProcedure = this.trpc.procedure.use( this.trpc.middleware(({ next, ctx }) => { - if (ctx.user === null) { + const authUser = ctx.user; + + if (!isAuthenticatedUser(authUser)) { throw new TRPCError({ code: 'UNAUTHORIZED', message: 'Not authenticated', @@ -25,7 +45,7 @@ export class TrpcService { return next({ ctx: { - user: ctx.user as AuthenticatedUser, + user: authUser, }, }); }) diff --git a/doc/adr/0004-trpc.md b/doc/adr/0004-trpc.md index e071439fcc..9ecbca55ef 100644 --- a/doc/adr/0004-trpc.md +++ b/doc/adr/0004-trpc.md @@ -134,7 +134,8 @@ countByStatut: this.trpc.publicProcedure.input(inputSchema).query(({ input }) => /* Dans le router */ countByStatut: this.trpc.authedProcedure.input(inputSchema).query(({ input, ctx }) => { const { collectiviteId, body } = input; - return this.service.countByStatut(collectiviteId, body, ctx.user); + const authenticatedUser = ctx.user; + return this.service.countByStatut(collectiviteId, body, authenticatedUser); }); /* Dans le service */ @@ -142,6 +143,17 @@ const uuid = user.id; const role = user.role; ``` +- `anonProcedure` pour les routes avec un token anonyme et récupération du user (anonyme) via le `ctx` de la query. + +```typescript +/* Dans le router */ +countByStatut: this.trpc.anonProcedure.input(inputSchema).query(({ input, ctx }) => { + const { collectiviteId, body } = input; + const anonymousUser = ctx.user; + return this.service.countByStatut(collectiviteId, body, anonymousUser); +}); +``` + ## Améliorations possibles - Mise en place d'outils facilitant la DX.