feat: Adds reset password api route, needs associated view route.
This commit is contained in:
@@ -110,6 +110,9 @@ private extension SiteRoute.Api.UserRoute {
|
|||||||
return user
|
return user
|
||||||
// case let .login(user):
|
// case let .login(user):
|
||||||
// return try await users.login(user)
|
// return try await users.login(user)
|
||||||
|
case let .resetPassword(id: id, request: request):
|
||||||
|
try await users.resetPassword(id, request)
|
||||||
|
return nil
|
||||||
case let .update(id: id, updates: updates):
|
case let .update(id: id, updates: updates):
|
||||||
return try await users.update(id, updates)
|
return try await users.update(id, updates)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ public extension DatabaseClient {
|
|||||||
public var get: @Sendable (User.ID) async throws -> User?
|
public var get: @Sendable (User.ID) async throws -> User?
|
||||||
public var login: @Sendable (User.Login) async throws -> User.Token
|
public var login: @Sendable (User.Login) async throws -> User.Token
|
||||||
public var logout: @Sendable (User.Token.ID) async throws -> Void
|
public var logout: @Sendable (User.Token.ID) async throws -> Void
|
||||||
|
public var resetPassword: @Sendable (User.ID, User.ResetPassword) async throws -> Void
|
||||||
public var token: @Sendable (User.ID) async throws -> User.Token
|
public var token: @Sendable (User.ID) async throws -> User.Token
|
||||||
public var update: @Sendable (User.ID, User.Update) async throws -> User
|
public var update: @Sendable (User.ID, User.Update) async throws -> User
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,18 @@ public extension DatabaseClient.Users {
|
|||||||
guard let token = try await UserTokenModel.find(id, on: database)
|
guard let token = try await UserTokenModel.find(id, on: database)
|
||||||
else { return }
|
else { return }
|
||||||
try await token.delete(on: database)
|
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
|
} token: { _ in
|
||||||
guard let user = try await UserModel.query(on: database)
|
guard let user = try await UserModel.query(on: database)
|
||||||
.with(\.$token)
|
.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 {
|
extension User.Create {
|
||||||
|
|
||||||
func toModel() throws -> UserModel {
|
func toModel() throws -> UserModel {
|
||||||
try validate()
|
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 {
|
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.
|
/// The user database model.
|
||||||
///
|
///
|
||||||
/// A user is someone who is able to login and generate PO's for employees. Generally a user should also
|
/// 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 {
|
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)
|
guard let user = try await UserModel.query(on: request.db)
|
||||||
.filter(\UserModel.$username == basic.username)
|
.filter(\UserModel.$username == basic.username)
|
||||||
.first(),
|
.first(),
|
||||||
try Bcrypt.verify(basic.password, created: user.passwordHash)
|
try user.verifyPassword(basic.password)
|
||||||
else {
|
else {
|
||||||
throw Abort(.unauthorized)
|
throw Abort(.unauthorized)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ public extension SiteRoute {
|
|||||||
case create(User.Create)
|
case create(User.Create)
|
||||||
case get(id: User.ID)
|
case get(id: User.ID)
|
||||||
case index
|
case index
|
||||||
|
case resetPassword(id: User.ID, request: User.ResetPassword)
|
||||||
case update(id: User.ID, updates: User.Update)
|
case update(id: User.ID, updates: User.Update)
|
||||||
|
|
||||||
static let rootPath = "users"
|
static let rootPath = "users"
|
||||||
@@ -143,6 +144,11 @@ public extension SiteRoute {
|
|||||||
Path { rootPath }
|
Path { rootPath }
|
||||||
Method.get
|
Method.get
|
||||||
}
|
}
|
||||||
|
Route(.case(Self.resetPassword(id:request:))) {
|
||||||
|
Path { rootPath; User.ID.parser(); "reset-password" }
|
||||||
|
Method.patch
|
||||||
|
Body(.json(User.ResetPassword.self))
|
||||||
|
}
|
||||||
Route(.case(Self.update(id:updates:))) {
|
Route(.case(Self.update(id:updates:))) {
|
||||||
Path { rootPath; User.ID.parser() }
|
Path { rootPath; User.ID.parser() }
|
||||||
Method.patch
|
Method.patch
|
||||||
|
|||||||
@@ -61,6 +61,19 @@ public extension User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ResetPassword: Codable, Equatable, Sendable {
|
||||||
|
public let password: String
|
||||||
|
public let confirmPassword: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
password: String,
|
||||||
|
confirmPassword: String
|
||||||
|
) {
|
||||||
|
self.password = password
|
||||||
|
self.confirmPassword = confirmPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Token: Codable, Equatable, Identifiable, Sendable {
|
struct Token: Codable, Equatable, Identifiable, Sendable {
|
||||||
public let id: UUID
|
public let id: UUID
|
||||||
public let userID: User.ID
|
public let userID: User.ID
|
||||||
|
|||||||
Reference in New Issue
Block a user