From 3df0c54004b331e4c3eed393a44f569c394a1426 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 2 Feb 2026 17:18:37 +0100 Subject: [PATCH] backend: add 'iat' timestamp to token metadata and update related validation logic --- meet-ce/backend/src/middlewares/auth.middleware.ts | 10 +++++----- meet-ce/backend/src/models/token-metadata.model.ts | 3 +++ meet-ce/backend/src/models/zod-schemas/auth.schema.ts | 1 + .../src/models/zod-schemas/room-member.schema.ts | 1 + meet-ce/backend/src/services/room-member.service.ts | 2 ++ meet-ce/backend/src/services/token.service.ts | 2 ++ meet-ce/typings/src/room-member.ts | 2 ++ 7 files changed, 16 insertions(+), 5 deletions(-) diff --git a/meet-ce/backend/src/middlewares/auth.middleware.ts b/meet-ce/backend/src/middlewares/auth.middleware.ts index d7c3804a..0f50b1b5 100644 --- a/meet-ce/backend/src/middlewares/auth.middleware.ts +++ b/meet-ce/backend/src/middlewares/auth.middleware.ts @@ -17,13 +17,13 @@ import { } from '../models/error.model.js'; import { TokenType } from '../models/token-metadata.model.js'; import { RoomMemberRepository } from '../repositories/room-member.repository.js'; +import { RoomRepository } from '../repositories/room.repository.js'; import { ApiKeyService } from '../services/api-key.service.js'; import { LoggerService } from '../services/logger.service.js'; import { RequestSessionService } from '../services/request-session.service.js'; import { TokenService } from '../services/token.service.js'; import { UserService } from '../services/user.service.js'; import { getAccessToken, getRoomMemberToken } from '../utils/token.utils.js'; -import { RoomRepository } from '../repositories/room.repository.js'; /** * Interface for authentication validators. @@ -192,7 +192,7 @@ export const roomMemberTokenValidator: AuthValidator = { try { // Verify the token and extract the room member token metadata const tokenService = container.get(TokenService); - const { iat, metadata: tokenMetadata } = await tokenService.verifyToken(token); + const { metadata: tokenMetadata } = await tokenService.verifyToken(token); if (!tokenMetadata) { throw new Error('Missing required token claims'); @@ -200,10 +200,10 @@ export const roomMemberTokenValidator: AuthValidator = { // Validate the room member token metadata const parsedMetadata = tokenService.parseRoomMemberTokenMetadata(tokenMetadata); - const { roomId, memberId } = parsedMetadata; + const { iat, roomId, memberId } = parsedMetadata; // If the token has a memberId, validate that permissions haven't been updated after token issuance - if (memberId && iat) { + if (memberId) { const roomMemberRepository = container.get(RoomMemberRepository); const roomMember = await roomMemberRepository.findByRoomAndMemberId(roomId, memberId); @@ -211,7 +211,7 @@ export const roomMemberTokenValidator: AuthValidator = { if (!roomMember || iat < roomMember.permissionsUpdatedAt) { throw new Error('Token has outdated permissions'); } - } else if (!memberId && iat) { + } else { // If the token has no memberId (anonymous access), validate that room roles/anonymous haven't been updated const roomRepository = container.get(RoomRepository); const room = await roomRepository.findByRoomId(roomId, 'rolesUpdatedAt'); diff --git a/meet-ce/backend/src/models/token-metadata.model.ts b/meet-ce/backend/src/models/token-metadata.model.ts index 0e80218b..d06a2607 100644 --- a/meet-ce/backend/src/models/token-metadata.model.ts +++ b/meet-ce/backend/src/models/token-metadata.model.ts @@ -2,6 +2,9 @@ * Metadata associated with access, refresh, and temporary tokens. */ export interface TokenMetadata { + /** Token issued at timestamp (milliseconds since epoch) */ + iat: number; + /** Type of the token */ tokenType: TokenType; } diff --git a/meet-ce/backend/src/models/zod-schemas/auth.schema.ts b/meet-ce/backend/src/models/zod-schemas/auth.schema.ts index 499c9ff9..b4939af4 100644 --- a/meet-ce/backend/src/models/zod-schemas/auth.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/auth.schema.ts @@ -7,5 +7,6 @@ export const LoginReqSchema = z.object({ }); export const TokenMetadataSchema: z.ZodType = z.object({ + iat: z.number(), tokenType: z.nativeEnum(TokenType) }); diff --git a/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts b/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts index 62f21876..16c101ff 100644 --- a/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts +++ b/meet-ce/backend/src/models/zod-schemas/room-member.schema.ts @@ -126,6 +126,7 @@ export const RoomMemberTokenOptionsSchema: z.ZodType ); export const RoomMemberTokenMetadataSchema: z.ZodType = z.object({ + iat: z.number(), livekitUrl: z.string().url('LiveKit URL must be a valid URL'), roomId: z.string(), memberId: z.string().optional(), diff --git a/meet-ce/backend/src/services/room-member.service.ts b/meet-ce/backend/src/services/room-member.service.ts index ca42d439..be99f4d0 100644 --- a/meet-ce/backend/src/services/room-member.service.ts +++ b/meet-ce/backend/src/services/room-member.service.ts @@ -612,6 +612,7 @@ export class RoomMemberService { const livekitPermissions = this.getLiveKitPermissions(roomId, effectivePermissions); const tokenMetadata: MeetRoomMemberTokenMetadata = { + iat: Date.now(), livekitUrl: MEET_ENV.LIVEKIT_URL, roomId, memberId, @@ -644,6 +645,7 @@ export class RoomMemberService { ); const tokenMetadata: MeetRoomMemberTokenMetadata = { + iat: Date.now(), livekitUrl: MEET_ENV.LIVEKIT_URL, roomId, memberId, diff --git a/meet-ce/backend/src/services/token.service.ts b/meet-ce/backend/src/services/token.service.ts index 68d86495..64d16bcd 100644 --- a/meet-ce/backend/src/services/token.service.ts +++ b/meet-ce/backend/src/services/token.service.ts @@ -16,6 +16,7 @@ export class TokenService { async generateAccessToken(user: MeetUser, isTemporary = false): Promise { const tokenMetadata: TokenMetadata = { + iat: Date.now(), tokenType: isTemporary ? TokenType.TEMPORARY : TokenType.ACCESS }; const tokenOptions: AccessTokenOptions = { @@ -41,6 +42,7 @@ export class TokenService { async generateRefreshToken(user: MeetUser): Promise { const tokenMetadata: TokenMetadata = { + iat: Date.now(), tokenType: TokenType.REFRESH }; const tokenOptions: AccessTokenOptions = { diff --git a/meet-ce/typings/src/room-member.ts b/meet-ce/typings/src/room-member.ts index 96c3acfc..510531f6 100644 --- a/meet-ce/typings/src/room-member.ts +++ b/meet-ce/typings/src/room-member.ts @@ -72,6 +72,8 @@ export interface MeetRoomMemberTokenOptions { * Contains information about the room and member permissions. */ export interface MeetRoomMemberTokenMetadata { + /** Token issued at timestamp (milliseconds since epoch) */ + iat: number; livekitUrl: string; roomId: string; memberId?: string;