import { OAuth2CodeAdapter, OAuth2Code } from '@icynet/oauth2-provider'; import { OAuth2TokenType } from 'src/modules/objects/oauth2-token/oauth2-token.entity'; import { TokenService } from 'src/modules/utility/services/token.service'; import { OAuth2ClientService } from 'src/modules/objects/oauth2-client/oauth2-client.service'; import { UserService } from 'src/modules/objects/user/user.service'; import { OAuth2TokenService } from 'src/modules/objects/oauth2-token/oauth2-token.service'; import { FormUtilityService } from 'src/modules/utility/services/form-utility.service'; import { Injectable } from '@nestjs/common'; @Injectable() export class CodeAdapter implements OAuth2CodeAdapter { constructor( private readonly token: TokenService, private readonly tokenService: OAuth2TokenService, private readonly clientService: OAuth2ClientService, private readonly userService: UserService, private readonly form: FormUtilityService, ) {} ttl = 3600; challengeMethods = ['plain', 'S256']; async create( userId: number, clientId: string, scope: string | string[], ttl: number, nonce?: string, codeChallenge?: string, codeChallengeMethod?: 'plain' | 'S256', ): Promise { const client = await this.clientService.getById(clientId); const user = await this.userService.getById(userId); const accessToken = this.token.generateString(64); // Standardize scope value const scopes = ( !Array.isArray(scope) ? this.form.splitScope(scope) : scope ).join(' '); const expiresAt = new Date(Date.now() + ttl * 1000); const pcke = codeChallenge && codeChallengeMethod ? `${this.challengeMethods.indexOf( codeChallengeMethod, )}:${codeChallenge}` : null; this.tokenService.insertToken( accessToken, OAuth2TokenType.CODE, client, scopes, expiresAt, user, nonce, pcke, ); return accessToken; } async fetchByCode(code: string | OAuth2Code): Promise { const findBy = typeof code === 'string' ? code : code.code; const find = await this.tokenService.fetchByToken( findBy, OAuth2TokenType.CODE, ); if (!find) { return null; } let codeChallenge: string; let codeChallengeMethod: 'plain' | 'S256'; if (find.pcke) { codeChallengeMethod = this.challengeMethods[ Number(find.pcke.substring(0, 1)) ] as 'plain' | 'S256'; codeChallenge = find.pcke.substring(2); } return { ...find, code: find.token, client_id: find.client.client_id, user_id: find.user.id, code_challenge: codeChallenge, code_challenge_method: codeChallengeMethod, }; } async removeByCode(code: string | OAuth2Code): Promise { const findBy = typeof code === 'string' ? code : code.code; const find = await this.tokenService.fetchByToken( findBy, OAuth2TokenType.CODE, ); this.tokenService.remove(find); return true; } getUserId(code: OAuth2Code): string { return code.user_id as string; } getClientId(code: OAuth2Code): string { return code.client_id as string; } getScope(code: OAuth2Code): string { return code.scope; } checkTTL(code: OAuth2Code): boolean { return code.expires_at.getTime() > Date.now(); } getCodeChallenge(code: OAuth2Code) { return { method: code.code_challenge_method, challenge: code.code_challenge, }; } }