| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // apps/box-app-api/src/feature/auth/auth.service.ts
- import { Injectable, Logger } from '@nestjs/common';
- import { JwtService } from '@nestjs/jwt';
- import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
- import { RabbitmqPublisherService } from '../../rabbitmq/rabbitmq-publisher.service';
- import { AppJwtPayload } from './interfaces/app-jwt-payload';
- import { PrismaMongoStatsService } from '../../prisma/prisma-mongo-stats.service';
- import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
- @Injectable()
- export class AuthService {
- private readonly logger = new Logger(AuthService.name);
- constructor(
- private readonly jwtService: JwtService,
- private readonly rabbitmqPublisher: RabbitmqPublisherService,
- private readonly prismaMongoStatsService: PrismaMongoStatsService,
- private readonly prismaMongoService: PrismaMongoService,
- ) {}
- async login(params: {
- uid: string;
- ip: string;
- userAgent?: string;
- appVersion?: string;
- os?: string;
- uChannelId?: string;
- machine?: string;
- // plus whatever you need like account, password, etc.
- }): Promise<any> {
- const { uid, ip, userAgent, appVersion, os, uChannelId, machine } = params;
- // 1) Your existing auth logic (validate user, etc.)
- // const user = await this.validateUser(...);
- // 2) Generate tokenId (jti) and JWT
- const tokenId = crypto.randomUUID(); // Node 18+; or use uuid library
- const now = Date.now(); // number (ms since epoch)
- const user = await this.getUserByUid(uid);
- const firstChannel = await this.getChannelById(uChannelId || '');
- // if user does not exist, uChannelId = firstChannel.channelId
- let finalUChannelId = uChannelId;
- if (!user && firstChannel) {
- finalUChannelId = firstChannel.channelId;
- }
- // if user exists, take firstChannel.channelId
- else if (user && firstChannel) {
- finalUChannelId = firstChannel.channelId;
- }
- // Build JWT payload with required and optional tracking fields.
- // Tracking fields (uChannelId, machine, ip, userAgent, appVersion, os) are optional
- // to preserve backward compatibility with older tokens and minimize JWT size for stability.
- const payload: AppJwtPayload = {
- sub: uid,
- uid,
- jti: tokenId,
- uChannelId: finalUChannelId,
- machine,
- ip,
- userAgent,
- appVersion,
- os,
- iat: Math.floor(now / 1000), // issued at (in seconds, per JWT spec)
- };
- const accessToken = await this.jwtService.signAsync(payload);
- // 3) Build login event payload
- const loginEvent: UserLoginEventPayload = {
- uid,
- ip,
- userAgent,
- appVersion,
- os,
- uChannelId: finalUChannelId,
- machine,
- tokenId,
- loginAt: now,
- };
- // 4) Fire-and-forget publish (but wait for broker confirm)
- try {
- this.logger.log(`Publishing user login event for uid=${uid}`);
- await this.rabbitmqPublisher.publishUserLogin(loginEvent);
- } catch (error) {
- // Decide your policy:
- // - Either just log and continue (login succeeds even if stats fail),
- // - Or throw to fail login if stats are critical.
- // For now, let's log and continue.
- // If you want stricter behaviour, re-throw.
- const errorMessage =
- error instanceof Error ? error.message : String(error);
- const errorStack = error instanceof Error ? error.stack : undefined;
- this.logger.error(
- `Failed to publish user login event for uid=${uid}: ${errorMessage}`,
- errorStack,
- );
- }
- // let startupAd: AdPayload | null = null;
- // try {
- // startupAd = await this.adPoolService.getRandomAdByType(AdType.STARTUP);
- // } catch (err) {
- // this.logger.error('Failed to get startup ad for login', err);
- // }
- // let startupAdWithoutUrl: Omit<AdPayload, 'adsUrl'> | null = null;
- // if (startupAd) {
- // const { adsUrl, ...rest } = startupAd;
- // startupAdWithoutUrl = rest;
- // }
- return { accessToken };
- }
- // add a function to retrieve user record from mongo by uid
- async getUserByUid(uid: string): Promise<any> {
- // Implement your MongoDB query here to find the user by uid
- // For example, if you have a UserModel injected, you might do:
- // return this.userModel.findOne({ uid }).exec();
- // Placeholder implementation:
- const user = await this.prismaMongoStatsService.user.findUnique({
- where: { uid },
- });
- if (user) {
- return user;
- }
- return null;
- }
- // retrive the first channel from mongo collection, order by createAt ascending
- async getFirstChannel(): Promise<any> {
- const channel = await this.prismaMongoService.channel.findFirst({
- orderBy: { createAt: 'asc' },
- });
- if (channel) {
- return channel;
- }
- return null;
- }
- // find by channelId
- async getChannelById(channelId: string): Promise<any> {
- const channel = await this.prismaMongoService.channel.findUnique({
- where: { channelId },
- });
- if (channel) {
- return this.getFirstChannel();
- }
- return null;
- }
- }
|