import { AuthCookieService, AuthorizationService, type User as DomainUser, RefreshToken, RefreshTokenService } from "@cv/core"; import { JwtAuthGuard, VerifiedScopeGuard } from "@cv/auth"; import { UseGuards } from "@nestjs/common"; import { Args, Context, Mutation, Query, Resolver } from "@nestjs/graphql"; import type { Response } from "express"; import { CurrentRefreshTokenId } from "@/modules/current-user/current-refresh-token-id.decorator"; import { CurrentUser } from "@/modules/current-user/current-user.decorator"; import type { GraphQLContext } from "../graphql/graphql-context.type"; import { ActiveSession } from "./active-session.type"; @Resolver() export class TokenResolver { constructor( private readonly authorizationService: AuthorizationService, private readonly authCookieService: AuthCookieService, private readonly refreshTokenService: RefreshTokenService, ) {} @Mutation(() => Boolean) @UseGuards(JwtAuthGuard) async logout( @CurrentRefreshTokenId() currentRefreshTokenId: string | undefined, @Context() { res }: GraphQLContext, ): Promise { if (currentRefreshTokenId) { await this.revokeSession( currentRefreshTokenId, currentRefreshTokenId, res, ); } else { this.authCookieService.clearAuthCookies(res); } return true; } @Query(() => [ActiveSession]) @UseGuards(JwtAuthGuard, VerifiedScopeGuard) async activeSessions( @CurrentUser() user: DomainUser, @CurrentRefreshTokenId() currentRefreshTokenId: string | undefined, ): Promise { const sessions = await this.refreshTokenService.findActiveSessionsByUser(user); await Promise.all( sessions.map((session: RefreshToken) => this.authorizationService.canView(user, session), ), ); return sessions.map((session) => ActiveSession.fromDomain(session, currentRefreshTokenId), ); } @Mutation(() => Boolean) @UseGuards(JwtAuthGuard, VerifiedScopeGuard) async deleteSession( @CurrentUser() user: DomainUser, @Args("sessionId") sessionId: string, @CurrentRefreshTokenId() currentRefreshTokenId: string | undefined, @Context() { res }: GraphQLContext, ): Promise { const session = await this.refreshTokenService.findByIdOrFail(sessionId); await this.authorizationService.canDelete(user, session); await this.revokeSession(sessionId, currentRefreshTokenId, res); return true; } private async revokeSession( sessionId: string, currentRefreshTokenId: string | undefined, res: Response, ): Promise { await this.refreshTokenService.deleteById(sessionId); const isCurrentSession = currentRefreshTokenId !== undefined && sessionId === currentRefreshTokenId; if (isCurrentSession) { this.authCookieService.clearAuthCookies(res); } } }