eletrotupi / tcc/ api/src/services/auth.service.tse1b36f7e39e375953e4e199680dd3f36adca4939
3.7 KB | 152 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 { success: true };
74
};
75

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

84
  if (!user || !user.passwordResetExpires || isTokenExpired(user.passwordResetExpires)) {
85
    throw new InvalidOrExpiredTokenError();
86
  }
87

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

97
  return {
98
    success: true
99
  };
100
};
101

102
export const generateToken = (userId: number, email: string) => {
103
  return createJWT(userId, email);
104
};
105

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

119
const ACTIVATION_CODE_LENGTH = 6;
120
const ACTIVATION_CODE_TTL_MINUTES = 5;
121
const ACTIVATION_CODE_MAX_ATTEMPTS = 5;
122

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

126
const isUniqueConstraintViolation = (err: unknown): boolean =>
127
  err instanceof Prisma.PrismaClientKnownRequestError && err.code === "P2002";
128

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

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

145
      return code;
146
    } catch (err) {
147
      if (!isUniqueConstraintViolation(err)) throw err;
148
    }
149
  }
150

151
  throw new Error("Could not issue a unique activation code");
152
}