auth: very rough api auth, password reset, and account creation
Pedro Lucas Porcellis porcellis@eletrotupi.com · 6 months ago
90f9ece8c22148d981fe8468069be72a39c17c1a Browse filesParents
e5b1e91Changed files
4
Additions
+106
Deletions
-4
/dev/null → api/prisma/migrations/20260405235156_add_password_reset_fields/migration.sql
1 +
-- AlterTable
2 +
ALTER TABLE "users" ADD COLUMN "password_reset_expires" DATETIME;
3 +
ALTER TABLE "users" ADD COLUMN "password_reset_token" TEXT;
/dev/null → api/prisma/migrations/migration_lock.toml
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
1 1
generator client {
2 2
provider = "prisma-client-js"
3 3
//output = "../src/generated/prisma"
4 4
}
5 5
8 8
}
9 9
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
19 21
20 22
@@map("users")
21 23
}
api/src/models/user.ts
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,
6 12
isValidPassword,
7 13
sanitizeString,
38 44
console.log(err)
39 45
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
}