eletrotupi / tcc/ api/src/services/auth.service.tsb77de0cf2fd487c62b9c698932d65906b0ef9398
3.7 KB | 155 lines Raw
1
import { prisma } from '@app/lib/prisma';
2
import { Prisma, User } from '@prisma/client';
3
import bcrypt from "bcryptjs";
4
import crypto from "crypto";
5
import { addMinutes } from "date-fns";
6

7
import {
8
  generateToken as createJWT,
9
  generatePasswordResetToken,
10
  getPasswordResetExpiration,
11
  isTokenExpired
12
} from '@app/lib/jwt';
13

14
import {
15
  findUserByEmail
16
} from '@app/services/user.service'; // Replace with a barrel import
17

18
import {
19
  LoginInput,
20
  PasswordResetRequestInput,
21
  PasswordResetInput,
22
  ActivateUserInput
23
} from '@app/schemas'
24

25
import { getQueue, MailJobName } from '@app/lib/queue';
26

27
class InvalidCredentialsError extends Error {
28
  constructor() { super('Invalid credentials') }
29
}
30

31
class InvalidOrExpiredTokenError extends Error {
32
  constructor() { super('Invalid or Expired reset token') }
33
}
34

35
export const login = async({ email, password }: LoginInput) => {
36
  const user = await findUserByEmail(email)!;
37

38
  if (!user || !bcrypt.compareSync(password, user.encryptedPassword)) {
39
    throw new InvalidCredentialsError();
40
  }
41

42
  const token = generateToken(user.id, user.email);
43

44
  return { user, token };
45
};
46

47
export const requestPasswordReset = async ({ email }: PasswordResetRequestInput) => {
48
  const user = await findUserByEmail(email);
49

50
  if (!user) {
51
    return { success: true };
52
  }
53

54
  const resetToken = generatePasswordResetToken();
55
  const resetExpires = getPasswordResetExpiration();
56

57
  await prisma.user.update({
58
    where: {
59
      id: user.id
60
    },
61
    data: {
62
      passwordResetToken: resetToken,
63
      passwordResetExpires: resetExpires
64
    }
65
  });
66

67
  const mailQueue = getQueue('mail');
68
  const job = await mailQueue.add(MailJobName.PasswordReset, {
69
    userId: user.id,
70
    token: resetToken
71
  });
72

73
  return {
74
    success: true,
75
    token: resetToken
76
  };
77
};
78

79
export const resetPassword = async ({ token, newPassword }: PasswordResetInput) => {
80
  const user = await prisma.user.findFirst({
81
    where: {
82
      passwordResetToken: token,
83
      passwordResetExpires: { not: null }
84
    }
85
  });
86

87
  if (!user || !user.passwordResetExpires || isTokenExpired(user.passwordResetExpires)) {
88
    throw new InvalidOrExpiredTokenError();
89
  }
90

91
  await prisma.user.update({
92
    where: { id: user.id },
93
    data: {
94
      encryptedPassword: bcrypt.hashSync(newPassword, 10),
95
      passwordResetToken: null,
96
      passwordResetExpires: null
97
    }
98
  });
99

100
  return {
101
    success: true
102
  };
103
};
104

105
export const generateToken = (userId: number, email: string) => {
106
  return createJWT(userId, email);
107
};
108

109
export const activateUser = async(user: User): Promise<User> => {
110
  return await prisma.user.update({
111
    where: {
112
      id: user.id
113
    },
114
    data: {
115
      active: true,
116
      activationCode: null,
117
      activationCodeExpiresAt: null
118
    }
119
  });
120
}
121

122
const ACTIVATION_CODE_LENGTH = 6;
123
const ACTIVATION_CODE_TTL_MINUTES = 5;
124
const ACTIVATION_CODE_MAX_ATTEMPTS = 5;
125

126
export const generateActivationCode = (): string =>
127
  Array.from({ length: ACTIVATION_CODE_LENGTH }, () => crypto.randomInt(0, 10)).join("");
128

129
const isUniqueConstraintViolation = (err: unknown): boolean =>
130
  err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002";
131

132
export const issueActivationCode = async(userId: number): Promise<string> => {
133
  for (let attempt = 0; attempt < ACTIVATION_CODE_MAX_ATTEMPTS; attempt++) {
134
    const code = generateActivationCode();
135
    const expiresAt = addMinutes(new Date(), ACTIVATION_CODE_TTL_MINUTES);
136

137
    try {
138
      await prisma.user.update({
139
        where: {
140
          id: userId
141
        },
142
        data: {
143
          activationCode: code,
144
          activationCodeExpiresAt: expiresAt
145
        }
146
      });
147

148
      return code;
149
    } catch (err) {
150
      if (!isUniqueConstraintViolation(err)) throw err;
151
    }
152
  }
153

154
  throw new Error("Could not issue a unique activation code");
155
}