auth.service.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. // apps/box-app-api/src/feature/auth/auth.service.ts
  2. import { Injectable, Logger } from '@nestjs/common';
  3. import { JwtService } from '@nestjs/jwt';
  4. import { UserLoginEventPayload } from '@box/common/events/user-login-event.dto';
  5. import { RabbitmqPublisherService } from '../../rabbitmq/rabbitmq-publisher.service';
  6. import { AppJwtPayload } from './interfaces/app-jwt-payload';
  7. import { PrismaMongoStatsService } from '../../prisma/prisma-mongo-stats.service';
  8. import { PrismaMongoService } from '../../prisma/prisma-mongo.service';
  9. @Injectable()
  10. export class AuthService {
  11. private readonly logger = new Logger(AuthService.name);
  12. constructor(
  13. private readonly jwtService: JwtService,
  14. private readonly rabbitmqPublisher: RabbitmqPublisherService,
  15. private readonly prismaMongoStatsService: PrismaMongoStatsService,
  16. private readonly prismaMongoService: PrismaMongoService,
  17. ) {}
  18. async login(params: {
  19. uid: string;
  20. ip: string;
  21. userAgent?: string;
  22. appVersion?: string;
  23. os?: string;
  24. uChannelId?: string;
  25. machine?: string;
  26. // plus whatever you need like account, password, etc.
  27. }): Promise<any> {
  28. const { uid, ip, userAgent, appVersion, os, uChannelId, machine } = params;
  29. // 1) Your existing auth logic (validate user, etc.)
  30. // const user = await this.validateUser(...);
  31. // 2) Generate tokenId (jti) and JWT
  32. const tokenId = crypto.randomUUID(); // Node 18+; or use uuid library
  33. const now = Date.now(); // number (ms since epoch)
  34. const user = await this.getUserByUid(uid);
  35. const firstChannel = await this.getChannelById(uChannelId || '');
  36. // if user does not exist, uChannelId = firstChannel.channelId
  37. let finalUChannelId = uChannelId;
  38. if (!user && firstChannel) {
  39. finalUChannelId = firstChannel.channelId;
  40. }
  41. // if user exists, take firstChannel.channelId
  42. else if (user && firstChannel) {
  43. finalUChannelId = firstChannel.channelId;
  44. }
  45. // Build JWT payload with required and optional tracking fields.
  46. // Tracking fields (uChannelId, machine, ip, userAgent, appVersion, os) are optional
  47. // to preserve backward compatibility with older tokens and minimize JWT size for stability.
  48. const payload: AppJwtPayload = {
  49. sub: uid,
  50. uid,
  51. jti: tokenId,
  52. uChannelId: finalUChannelId,
  53. machine,
  54. ip,
  55. userAgent,
  56. appVersion,
  57. os,
  58. iat: Math.floor(now / 1000), // issued at (in seconds, per JWT spec)
  59. };
  60. const accessToken = await this.jwtService.signAsync(payload);
  61. // 3) Build login event payload
  62. const loginEvent: UserLoginEventPayload = {
  63. uid,
  64. ip,
  65. userAgent,
  66. appVersion,
  67. os,
  68. uChannelId: finalUChannelId,
  69. machine,
  70. tokenId,
  71. loginAt: now,
  72. };
  73. // 4) Fire-and-forget publish (but wait for broker confirm)
  74. try {
  75. this.logger.log(`Publishing user login event for uid=${uid}`);
  76. await this.rabbitmqPublisher.publishUserLogin(loginEvent);
  77. } catch (error) {
  78. // Decide your policy:
  79. // - Either just log and continue (login succeeds even if stats fail),
  80. // - Or throw to fail login if stats are critical.
  81. // For now, let's log and continue.
  82. // If you want stricter behaviour, re-throw.
  83. const errorMessage =
  84. error instanceof Error ? error.message : String(error);
  85. const errorStack = error instanceof Error ? error.stack : undefined;
  86. this.logger.error(
  87. `Failed to publish user login event for uid=${uid}: ${errorMessage}`,
  88. errorStack,
  89. );
  90. }
  91. // let startupAd: AdPayload | null = null;
  92. // try {
  93. // startupAd = await this.adPoolService.getRandomAdByType(AdType.STARTUP);
  94. // } catch (err) {
  95. // this.logger.error('Failed to get startup ad for login', err);
  96. // }
  97. // let startupAdWithoutUrl: Omit<AdPayload, 'adsUrl'> | null = null;
  98. // if (startupAd) {
  99. // const { adsUrl, ...rest } = startupAd;
  100. // startupAdWithoutUrl = rest;
  101. // }
  102. return { accessToken };
  103. }
  104. // add a function to retrieve user record from mongo by uid
  105. async getUserByUid(uid: string): Promise<any> {
  106. // Implement your MongoDB query here to find the user by uid
  107. // For example, if you have a UserModel injected, you might do:
  108. // return this.userModel.findOne({ uid }).exec();
  109. // Placeholder implementation:
  110. const user = await this.prismaMongoStatsService.user.findUnique({
  111. where: { uid },
  112. });
  113. if (user) {
  114. return user;
  115. }
  116. return null;
  117. }
  118. // retrive the first channel from mongo collection, order by createAt ascending
  119. async getFirstChannel(): Promise<any> {
  120. const channel = await this.prismaMongoService.channel.findFirst({
  121. orderBy: { createAt: 'asc' },
  122. });
  123. if (channel) {
  124. return channel;
  125. }
  126. return null;
  127. }
  128. // find by channelId
  129. async getChannelById(channelId: string): Promise<any> {
  130. const channel = await this.prismaMongoService.channel.findUnique({
  131. where: { channelId },
  132. });
  133. if (channel) {
  134. return this.getFirstChannel();
  135. }
  136. return null;
  137. }
  138. }