feat: Adds reset password api route, needs associated view route.

This commit is contained in:
2025-01-26 21:03:10 -05:00
parent 1f2bb900ca
commit d4a2048b12
5 changed files with 60 additions and 2 deletions

View File

@@ -57,6 +57,18 @@ public extension DatabaseClient.Users {
guard let token = try await UserTokenModel.find(id, on: database)
else { return }
try await token.delete(on: database)
} resetPassword: { id, request in
database.logger.debug("Reset password: \(id)")
try request.validate()
guard let user = try await UserModel.find(id, on: database) else {
throw Abort(.badRequest, reason: "User not found.")
}
user.passwordHash = try User.hashPassword(request.password)
try await user.save(on: database)
} token: { _ in
guard let user = try await UserModel.query(on: database)
.with(\.$token)
@@ -134,11 +146,19 @@ extension User.Token {
}
}
extension User {
static func hashPassword(_ password: String) throws -> String {
try Bcrypt.hash(password, cost: 12)
}
}
extension User.Create {
func toModel() throws -> UserModel {
try validate()
return try .init(username: username, email: email, passwordHash: Bcrypt.hash(password, cost: 12))
return try .init(username: username, email: email, passwordHash: User.hashPassword(password))
}
func validate() throws {
@@ -166,6 +186,18 @@ extension User.Login {
}
}
extension User.ResetPassword {
func validate() throws {
guard password.count > 8 else {
throw ValidationError(message: "Password should be more than 8 characters long.")
}
guard password == confirmPassword else {
throw ValidationError(message: "Passwords do not match.")
}
}
}
/// The user database model.
///
/// A user is someone who is able to login and generate PO's for employees. Generally a user should also
@@ -228,6 +260,9 @@ final class UserModel: Model, @unchecked Sendable {
)
}
func verifyPassword(_ password: String) throws -> Bool {
try Bcrypt.verify(password, created: passwordHash)
}
}
final class UserTokenModel: Model, Codable, @unchecked Sendable {
@@ -273,7 +308,7 @@ public struct UserPasswordAuthenticator: AsyncBasicAuthenticator {
guard let user = try await UserModel.query(on: request.db)
.filter(\UserModel.$username == basic.username)
.first(),
try Bcrypt.verify(basic.password, created: user.passwordHash)
try user.verifyPassword(basic.password)
else {
throw Abort(.unauthorized)
}