eletrotupi / tcc/ commit / 90f9ece

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

Pedro Lucas Porcellis porcellis@eletrotupi.com · 6 months ago 90f9ece8c22148d981fe8468069be72a39c17c1a Browse files
Parents e5b1e91
Changed 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 modified+97 -3
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
}