backend: add 'iat' timestamp to token metadata and update related validation logic

This commit is contained in:
juancarmore 2026-02-02 17:18:37 +01:00
parent 73e7a1ece7
commit 3df0c54004
7 changed files with 16 additions and 5 deletions

View File

@ -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');

View File

@ -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;
}

View File

@ -7,5 +7,6 @@ export const LoginReqSchema = z.object({
});
export const TokenMetadataSchema: z.ZodType<TokenMetadata> = z.object({
iat: z.number(),
tokenType: z.nativeEnum(TokenType)
});

View File

@ -126,6 +126,7 @@ export const RoomMemberTokenOptionsSchema: z.ZodType<MeetRoomMemberTokenOptions>
);
export const RoomMemberTokenMetadataSchema: z.ZodType<MeetRoomMemberTokenMetadata> = z.object({
iat: z.number(),
livekitUrl: z.string().url('LiveKit URL must be a valid URL'),
roomId: z.string(),
memberId: z.string().optional(),

View File

@ -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,

View File

@ -16,6 +16,7 @@ export class TokenService {
async generateAccessToken(user: MeetUser, isTemporary = false): Promise<string> {
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<string> {
const tokenMetadata: TokenMetadata = {
iat: Date.now(),
tokenType: TokenType.REFRESH
};
const tokenOptions: AccessTokenOptions = {

View File

@ -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;