diff --git a/.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate b/.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate index 4e71f86..3aed83f 100644 Binary files a/.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate and b/.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index 75f58b2..b4117e8 100644 --- a/.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -3,4 +3,22 @@ uuid = "D3C706B5-9CBD-4F91-8A68-CF08562866B5" type = "1" version = "2.0"> + + + + + + diff --git a/Sources/App/Controllers/FuelEntryV2Controller.swift b/Sources/App/Controllers/FuelEntryV2Controller.swift index 4267d0f..b97c86f 100644 --- a/Sources/App/Controllers/FuelEntryV2Controller.swift +++ b/Sources/App/Controllers/FuelEntryV2Controller.swift @@ -11,14 +11,18 @@ import FluentKit struct FuelEntryV2Controller: RouteCollection { func boot(routes: RoutesBuilder) throws { let fuelEntries = routes.grouped("fuel_entries") - fuelEntries.get(use: index) - fuelEntries.get("test", use: filtered) + let tokenAuthMiddleware = Token.authenticator() + let guardMiddleware = User.guardMiddleware() + let tokenAuthGroup = fuelEntries.grouped(tokenAuthMiddleware, guardMiddleware) + tokenAuthGroup.get(use: index) + tokenAuthGroup.get("test", use: filtered) // fuelEntries.get("with-images", use: getAllEntriesWithImages) - fuelEntries.post(use: create) + tokenAuthGroup.post(use: create) // fuelEntries.post("upload-image", use: uploadImage) - fuelEntries.group(":fuelEntryID") { fuelEntry in + tokenAuthGroup.group(":fuelEntryID") { fuelEntry in fuelEntry.delete(use: delete) } + } func index(req: Request) async throws -> [FuelEntry] { diff --git a/Sources/App/Controllers/UserController.swift b/Sources/App/Controllers/UserController.swift index 1ede7b2..29cf9bf 100644 --- a/Sources/App/Controllers/UserController.swift +++ b/Sources/App/Controllers/UserController.swift @@ -26,57 +26,17 @@ extension UserSignup: Validatable { } struct UserController: RouteCollection { - func boot(routes: RoutesBuilder) throws { - let usersRoute = routes.grouped("users") - usersRoute.post("signup", use: create) - - let tokenProtected = usersRoute.grouped(Token.authenticator()) - tokenProtected.get("me", use: getMyOwnUser) - - let passwordProtected = usersRoute.grouped(User.authenticator()) - passwordProtected.post("login", use: login) - } - - fileprivate func create(req: Request) throws -> EventLoopFuture { - try UserSignup.validate(content: req) - let userSignup = try req.content.decode(UserSignup.self) - let user = try User.create(from: userSignup) - var token: Token! - - return checkIfUserExists(userSignup.username, req: req).flatMap { exists in - guard !exists else { - return req.eventLoop.future(error: UserError.usernameTaken) - } - - return user.save(on: req.db) - }.flatMap { - guard let newToken = try? user.createToken(source: .signup) else { - return req.eventLoop.future(error: Abort(.internalServerError)) - } - token = newToken - return token.save(on: req.db) - }.flatMapThrowing { - NewSession(token: token.value, user: try user.asPublic()) + func boot(routes: any RoutesBuilder) throws { + let users = routes.grouped("users") + users.post(use: create) } - } - - fileprivate func login(req: Request) throws -> EventLoopFuture { - let user = try req.auth.require(User.self) - let token = try user.createToken(source: .login) - - return token.save(on: req.db).flatMapThrowing { - NewSession(token: token.value, user: try user.asPublic()) + @Sendable + func create(req: Request) async throws -> Response{ + let user = try req.content.decode(User.self) + user.passwordHash = try Bcrypt.hash(user.passwordHash) + try await user.save(on: req.db) + let token = try Token.generate(for: user) + try await token.save(on: req.db) + return try await token.encodeResponse(for: req) } - } - - func getMyOwnUser(req: Request) throws -> User.Public { - try req.auth.require(User.self).asPublic() - } - - private func checkIfUserExists(_ username: String, req: Request) -> EventLoopFuture { - User.query(on: req.db) - .filter(\.$username == username) - .first() - .map { $0 != nil } - } } diff --git a/Sources/App/Migrations/CreateToken.swift b/Sources/App/Migrations/CreateToken.swift index 3f7228f..13c7a22 100644 --- a/Sources/App/Migrations/CreateToken.swift +++ b/Sources/App/Migrations/CreateToken.swift @@ -29,3 +29,23 @@ struct CreateTokens: Migration { database.schema(Token.schema).delete() } } +struct UpdateTokens1: Migration { + func prepare(on database: Database) -> EventLoopFuture { + // 2 + database.schema(Token.schema) + // 3 + .deleteField("source") + .deleteField("created_at") + .deleteField("expires_at") + .update() + } + + // 5 + func revert(on database: Database) -> EventLoopFuture { + database.schema(Token.schema) + .field("source", .int, .required) + .field("created_at", .datetime, .required) + .field("expires_at", .datetime) + .update() + } +} diff --git a/Sources/App/Migrations/CreateUser.swift b/Sources/App/Migrations/CreateUser.swift index bd3990b..b718bc2 100644 --- a/Sources/App/Migrations/CreateUser.swift +++ b/Sources/App/Migrations/CreateUser.swift @@ -25,3 +25,45 @@ struct CreateUser: AsyncMigration { try await database.schema(User.schema).delete() } } + +struct UpdateUser1: AsyncMigration{ + func prepare(on database: Database) async throws { + try await database.schema(User.schema) + .deleteField("created_at") + .deleteField("updated_at") + .update() + } + + func revert(on database: Database) async throws { + try await database.schema(User.schema) + .field("created_at", .datetime, .required) + .field("updated_at", .datetime, .required) + .update() + } +} +struct UpdateUser2_AddTokenfield: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(User.schema) + .field("token", .string) + .update() + } + + func revert(on database: Database) async throws { + try await database.schema(User.schema) + .deleteField("token") + .update() + } +} +struct UpdateUser3_RemoveTokenfield: AsyncMigration { + func prepare(on database: Database) async throws { + try await database.schema(User.schema) + .deleteField("token") + .update() + } + + func revert(on database: Database) async throws { + try await database.schema(User.schema) + .field("token", .string) + .update() + } +} diff --git a/Sources/App/Models/Token.swift b/Sources/App/Models/Token.swift index 8b7e109..6b8ce82 100644 --- a/Sources/App/Models/Token.swift +++ b/Sources/App/Models/Token.swift @@ -8,12 +8,9 @@ import Vapor import Fluent -enum SessionSource: Int, Content { - case signup - case login -} -final class Token: Model { + +final class Token: Model, Content, @unchecked Sendable { static let schema = "token" @ID() @@ -25,36 +22,29 @@ final class Token: Model { @Field(key: "value") var value: String - @Field(key: "source") - var source: SessionSource - - @Field(key: "expires_at") - var expiresAt: Date? - - @Timestamp(key: "created_at", on: .create) - var createdAt: Date? init() {} - init(id: UUID? = nil, userId: User.IDValue, token: String, - source: SessionSource, expiresAt: Date?) { + init(id: UUID? = nil, userId: User.IDValue, token: String) { self.id = id self.$user.id = userId self.value = token - self.source = source - self.expiresAt = expiresAt } } -extension Token: ModelTokenAuthenticatable { - static let valueKey = \Token.$value - static let userKey = \Token.$user - - var isValid: Bool { - guard let expiryDate = expiresAt else { - return true +extension Token { + static func generate(for user: User) throws -> Token { + let random = [UInt8].random(count: 32).base64 + return try Token( userId: user.requireID(), token: random) } +} + +extension Token: ModelTokenAuthenticatable { + typealias User = App.User + static let valueKey = \Token.$value + static let userKey = \Token.$user - return expiryDate > Date() - } + var isValid: Bool { + true + } } diff --git a/Sources/App/Models/User.swift b/Sources/App/Models/User.swift index be1cbf2..5452f8d 100644 --- a/Sources/App/Models/User.swift +++ b/Sources/App/Models/User.swift @@ -8,16 +8,17 @@ import Fluent import Vapor -final class User: Model { - struct Public: Content { - let username: String - let id: UUID - let createdAt: Date? - let updatedAt: Date? - } - +final class User: Model, Content { static let schema = "user" - + final class Public: Content { + var id: UUID? + var username: String + + init(id: UUID?, username: String) { + self.id = id + self.username = username + } + } @ID() var id: UUID? @@ -26,12 +27,13 @@ final class User: Model { @Field(key: "password_hash") var passwordHash: String + - @Timestamp(key: "created_at", on: .create) - var createdAt: Date? - - @Timestamp(key: "updated_at", on: .update) - var updatedAt: Date? +// @Timestamp(key: "created_at", on: .create) +// var createdAt: Date? +// +// @Timestamp(key: "updated_at", on: .update) +// var updatedAt: Date? init() {} @@ -41,27 +43,11 @@ final class User: Model { self.passwordHash = passwordHash } } - extension User { - static func create(from userSignup: UserSignup) throws -> User { - User(username: userSignup.username, passwordHash: try Bcrypt.hash(userSignup.password)) - } - - func createToken(source: SessionSource) throws -> Token { - let calendar = Calendar(identifier: .gregorian) - let expiryDate = calendar.date(byAdding: .year, value: 1, to: Date()) - return try Token(userId: requireID(), - token: [UInt8].random(count: 16).base64, source: source, expiresAt: expiryDate) - } - - func asPublic() throws -> Public { - Public(username: username, - id: try requireID(), - createdAt: createdAt, - updatedAt: updatedAt) - } + func convertToPublic() -> User.Public { + return User.Public(id: id, username: username) + } } - extension User: ModelAuthenticatable { static let usernameKey = \User.$username static let passwordHashKey = \User.$passwordHash @@ -70,3 +56,8 @@ extension User: ModelAuthenticatable { try Bcrypt.verify(password, created: self.passwordHash) } } + + + + + diff --git a/Sources/App/configure.swift b/Sources/App/configure.swift index 177900a..fdf81e7 100755 --- a/Sources/App/configure.swift +++ b/Sources/App/configure.swift @@ -19,6 +19,10 @@ public func configure(_ app: Application) async throws { app.migrations.add(AddDownloadedBool()) app.migrations.add(CreateUser()) app.migrations.add(CreateTokens()) + app.migrations.add(UpdateUser1()) + app.migrations.add(UpdateTokens1()) + app.migrations.add(UpdateUser2_AddTokenfield()) + app.migrations.add(UpdateUser3_RemoveTokenfield()) try await app.autoMigrate().get() // register routes