diff --git a/src/modules/auth/auth.interface.ts b/src/modules/auth/auth.interface.ts index cee476e7b..5ac3c8090 100644 --- a/src/modules/auth/auth.interface.ts +++ b/src/modules/auth/auth.interface.ts @@ -4,7 +4,7 @@ import type { Request as ExpressRequest } from 'express'; export type MfaMethod = 'NONE' | 'SMS' | 'TOTP' | 'EMAIL'; export interface AccessTokenClaims { - id: number; + sub: string; scopes: string[]; } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 19996ca83..b7ba8467e 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -496,7 +496,7 @@ export class AuthService { private async getAccessToken(user: User): Promise { const scopes = await this.getScopes(user); const payload: AccessTokenClaims = { - id: user.id, + sub: `acct:${user.id}@${this.configService.get('security.issuerDomain')}`, scopes, }; return this.tokensService.signJwt( diff --git a/src/modules/auth/staart.strategy.ts b/src/modules/auth/staart.strategy.ts index 2391db1a2..d09c5cada 100644 --- a/src/modules/auth/staart.strategy.ts +++ b/src/modules/auth/staart.strategy.ts @@ -1,15 +1,16 @@ import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { PassportStrategy } from '@nestjs/passport'; import { Request } from 'express'; import ipRangeCheck from 'ip-range-check'; import minimatch from 'minimatch'; import { Strategy } from 'passport-strategy'; import { getClientIp } from 'request-ip'; -import { ApiKeysService } from '../api-keys/api-keys.service'; +import { validate } from 'uuid'; import { LOGIN_ACCESS_TOKEN } from '../../providers/tokens/tokens.constants'; import { TokensService } from '../../providers/tokens/tokens.service'; +import { ApiKeysService } from '../api-keys/api-keys.service'; import { AccessTokenClaims, AccessTokenParsed } from './auth.interface'; -import { validate } from 'uuid'; class StaartStrategyName extends Strategy { name = 'staart'; @@ -20,6 +21,7 @@ export class StaartStrategy extends PassportStrategy(StaartStrategyName) { constructor( private apiKeyService: ApiKeysService, private tokensService: TokensService, + private configService: ConfigService, ) { super(); } @@ -79,7 +81,12 @@ export class StaartStrategy extends PassportStrategy(StaartStrategyName) { LOGIN_ACCESS_TOKEN, bearerToken, ) as AccessTokenClaims; - const { id, scopes } = payload; + const { sub, scopes } = payload; + const [userPart, hostPart] = sub.split('@'); + if (hostPart !== this.configService.get('security.issuerDomain')) + throw new Error('Invalid issuer domain'); + const id = parseInt(userPart.replace('acct:', '')); + if (isNaN(id)) throw new Error('Invalid user ID'); return this.safeSuccess({ type: 'user', id, scopes }); } catch (error) {} diff --git a/src/providers/tokens/tokens.service.ts b/src/providers/tokens/tokens.service.ts index 6a0842392..e9a9acfeb 100644 --- a/src/providers/tokens/tokens.service.ts +++ b/src/providers/tokens/tokens.service.ts @@ -16,30 +16,32 @@ export class TokensService { constructor(private configService: ConfigService) {} signJwt( - subject: string, - payload: number | string | object | Buffer, + jwtType: string, + payload: object, expiresIn?: string, options?: SignOptions, ) { - if (typeof payload === 'number') payload = payload.toString(); return sign( - payload, + { ...payload, typ: jwtType }, this.configService.get('security.jwtSecret') ?? '', { ...options, - subject, expiresIn, }, ); } - verify(subject: string, token: string, options?: VerifyOptions) { + verify(jwtType: string, token: string, options?: VerifyOptions) { try { - return (verify( + const result = (verify( token, this.configService.get('security.jwtSecret') ?? '', - { ...options, subject }, + options, ) as any) as T; + if ('typ' in result) { + if ((result as { typ?: string }).typ !== jwtType) throw new Error(); + } else throw new Error(); + return result; } catch (error) { throw new UnauthorizedException(INVALID_TOKEN); }