Skip to content

Commit f6e043e

Browse files
authored
refactor(auth): type jwt user payload and drop any casts (#541) (#560)
* add AuthenticatedUser type for the jwt user payload * refactor(types): declare authenticate decorator on FastifyInstance * refactor(app): type the authenticate decorator instead of any * refactor(auth): drop any cast on request.user in /me route * refactor(auth): drop remaining any cast in secure logout route
1 parent ca466c8 commit f6e043e

3 files changed

Lines changed: 24 additions & 6 deletions

File tree

apps/backend/src/app.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import helmet from '@fastify/helmet';
77
import jwt from '@fastify/jwt';
88
import multipart from '@fastify/multipart';
99
import rateLimit from '@fastify/rate-limit';
10-
import Fastify, {type FastifyInstance} from 'fastify';
10+
import Fastify, {type FastifyInstance, type FastifyReply, type FastifyRequest} from 'fastify';
1111

1212
import { prismaPlugin } from './plugins/prisma.js';
1313
import { redisPlugin } from './plugins/redis.js';
@@ -24,6 +24,8 @@ import { teamRoutes } from './routes/team.js';
2424
import { extractRawJwt, blocklistKey } from './utils/jwt.js';
2525
import { validateEnv } from './utils/validateEnv.js';
2626

27+
import type { AuthenticatedUser } from './types/fastify.js';
28+
2729
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2830

2931
export async function buildApp():Promise<FastifyInstance> {
@@ -104,7 +106,7 @@ export async function buildApp():Promise<FastifyInstance> {
104106
// Checks the Redis blocklist before calling jwtVerify so that a logged-out
105107
// token is rejected immediately even if it has not yet expired.
106108
// The blocklist check is skipped when Redis is not registered (test env).
107-
app.decorate('authenticate', async function (request: any, reply: any) {
109+
app.decorate('authenticate', async function (request: FastifyRequest, reply: FastifyReply) {
108110
try {
109111
if (app.hasDecorator('redis')) {
110112
const raw = extractRawJwt(request);
@@ -122,7 +124,7 @@ export async function buildApp():Promise<FastifyInstance> {
122124
}
123125
}
124126
// Assign verified payload to request.user (upstream addition).
125-
const payload = await request.jwtVerify();
127+
const payload = await request.jwtVerify<AuthenticatedUser>();
126128
if (payload) { request.user = payload; }
127129
} catch (_err) {
128130
return reply.status(401).send({ error: 'Unauthorized' });

apps/backend/src/routes/auth.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
613613
// eslint-disable-next-line @typescript-eslint/unbound-method
614614
preHandler: [app.authenticate],
615615
}, async (request: FastifyRequest, reply: FastifyReply) => {
616-
const userId = (request.user as any).id;
616+
const userId = request.user.id;
617617
const user = await app.prisma.user.findUnique({
618618
where: { id: userId },
619619
select: {
@@ -676,7 +676,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
676676
await app.redis.set(blocklistKey(raw), '1', 'EX', ttl);
677677
} catch (err) {
678678
// Non-fatal: log and continue. The token will expire on its own.
679-
app.log.warn({ err, userId: (request.user as any)?.id }, 'Redis blocklist write failed during logout — token will expire naturally');
679+
app.log.warn({ err, userId: request.user?.id }, 'Redis blocklist write failed during logout — token will expire naturally');
680680
}
681681
}
682682
} else {
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,24 @@
11
import '@fastify/cookie';
2-
import { FastifyRequest } from 'fastify';
2+
import '@fastify/jwt';
3+
import { FastifyReply, FastifyRequest } from 'fastify';
4+
5+
export interface AuthenticatedUser {
6+
id: string;
7+
username: string;
8+
}
9+
10+
declare module '@fastify/jwt' {
11+
interface FastifyJWT {
12+
user: AuthenticatedUser;
13+
}
14+
}
315

416
declare module 'fastify' {
517
interface FastifyRequest {
618
cookies: Record<string, string | undefined>;
719
}
20+
21+
interface FastifyInstance {
22+
authenticate: (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
23+
}
824
}

0 commit comments

Comments
 (0)