auth: very rough api auth, password reset, and account creation

Pedro Lucas Porcellis porcellis@eletrotupi.com 2 months ago 90f9ece8c22148d981fe8468069be72a39c17c1a
Parents: e5b1e91
4 file(s) changed
  • api/prisma/migrations/20260405235156_add_password_reset_fields/migration.sql +3 -0
  • api/prisma/migrations/migration_lock.toml +3 -0
  • api/prisma/schema.prisma +3 -1
  • api/src/models/user.ts +97 -3
api/prisma/migrations/20260405235156_add_password_reset_fields/migration.sql
@@ -0,0 +1,3 @@
1 + -- AlterTable
2 + ALTER TABLE "users" ADD COLUMN "password_reset_expires" DATETIME;
3 + ALTER TABLE "users" ADD COLUMN "password_reset_token" TEXT;
api/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
1 + # Please do not edit this file manually
2 + # It should be added in your version-control system (e.g., Git)
3 + provider = "sqlite"
api/prisma/schema.prisma
@@ -10,9 +10,11 @@
10 10 model User {
11 11 id Int @id @default(autoincrement())
12 12 firstName String @map("first_name")
13 - lastName String? @map("last_name")
13 + lastName String? @map("last_name")
14 14 email String @unique
15 15 encryptedPassword String @map("encrypted_password")
16 + passwordResetToken String? @map("password_reset_token")
17 + passwordResetExpires DateTime? @map("password_reset_expires")
16 18 createdAt DateTime @default(now()) @map("created_at")
17 19 updatedAt DateTime @updatedAt @map("updated_at")
18 20
api/src/models/user.ts
@@ -1,5 +1,11 @@
1 1 import bcrypt from "bcryptjs";
2 2 import { prisma } from '../lib/prisma'
3 + import {
4 + generateToken as createJWT,
5 + generatePasswordResetToken,
6 + getPasswordResetExpiration,
7 + isTokenExpired
8 + } from '@app/lib/jwt';
3 9
4 10 import {
5 11 isValidEmail,
@@ -40,11 +46,99 @@ throw new Error("Something was off when creating user")
40 46 }
41 47 }
42 48
43 - const generateToken = (userId: number) => {
44 - return 'xxx' + userId
49 + const generateToken = (userId: number, email: string) => {
50 + return createJWT(userId, email);
45 51 }
46 52
53 + const findUserByEmail = async (email: string) => {
54 + return await prisma.user.findUnique({
55 + where: { email }
56 + });
57 + };
58 +
59 + const authenticateUser = async (email: string, password: string) => {
60 + const user = await findUserByEmail(email);
61 +
62 + if (!user) {
63 + throw new Error('Invalid credentials');
64 + }
65 +
66 + const isPasswordValid = bcrypt.compareSync(password, user.encryptedPassword);
67 +
68 + if (!isPasswordValid) {
69 + throw new Error('Invalid credentials');
70 + }
71 +
72 + return user;
73 + };
74 +
75 + const initiatePasswordReset = async (email: string) => {
76 + const user = await findUserByEmail(email);
77 +
78 + if (!user) {
79 + // Don't reveal whether email exists
80 + return { success: true };
81 + }
82 +
83 + const resetToken = generatePasswordResetToken();
84 + const resetExpires = getPasswordResetExpiration();
85 +
86 + await prisma.user.update({
87 + where: { id: user.id },
88 + data: {
89 + passwordResetToken: resetToken,
90 + passwordResetExpires: resetExpires
91 + }
92 + });
93 +
94 + // In production, send email with reset link
95 + // For now, return token for testing
96 + return {
97 + success: true,
98 + token: resetToken,
99 + email: user.email
100 + };
101 + };
102 +
103 + const resetPassword = async (token: string, newPassword: string) => {
104 + const user = await prisma.user.findFirst({
105 + where: {
106 + passwordResetToken: token,
107 + passwordResetExpires: { not: null }
108 + }
109 + });
110 +
111 + if (!user || !user.passwordResetExpires) {
112 + throw new Error('Invalid or expired reset token');
113 + }
114 +
115 + if (isTokenExpired(user.passwordResetExpires)) {
116 + throw new Error('Reset token has expired');
117 + }
118 +
119 + if (!isValidPassword(newPassword)) {
120 + throw new ShortPasswordError();
121 + }
122 +
123 + const encryptedPassword = bcrypt.hashSync(newPassword, 10);
124 +
125 + await prisma.user.update({
126 + where: { id: user.id },
127 + data: {
128 + encryptedPassword,
129 + passwordResetToken: null,
130 + passwordResetExpires: null
131 + }
132 + });
133 +
134 + return { success: true };
135 + };
136 +
47 137 export {
48 138 createUser,
49 - generateToken
139 + generateToken,
140 + findUserByEmail,
141 + authenticateUser,
142 + initiatePasswordReset,
143 + resetPassword
50 144 }