Explorar el Código

Token auth implementation

v2_uploadImages
rpm-mcarman hace 1 año
padre
commit
e1c683d582
Se han modificado 9 ficheros con 143 adiciones y 114 borrados
  1. BIN
      .swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate
  2. +18
    -0
      .swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  3. +8
    -4
      Sources/App/Controllers/FuelEntryV2Controller.swift
  4. +11
    -51
      Sources/App/Controllers/UserController.swift
  5. +20
    -0
      Sources/App/Migrations/CreateToken.swift
  6. +42
    -0
      Sources/App/Migrations/CreateUser.swift
  7. +16
    -26
      Sources/App/Models/Token.swift
  8. +24
    -33
      Sources/App/Models/User.swift
  9. +4
    -0
      Sources/App/configure.swift

BIN
.swiftpm/xcode/package.xcworkspace/xcuserdata/michielcarman.xcuserdatad/UserInterfaceState.xcuserstate Ver fichero


+ 18
- 0
.swiftpm/xcode/xcuserdata/michielcarman.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist Ver fichero

@@ -3,4 +3,22 @@
uuid = "D3C706B5-9CBD-4F91-8A68-CF08562866B5"
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "65DB5B80-779D-462A-A47F-A0A75B1C75F8"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "Sources/App/Controllers/UserController.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
landmarkName = "create(req:)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

+ 8
- 4
Sources/App/Controllers/FuelEntryV2Controller.swift Ver fichero

@@ -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] {


+ 11
- 51
Sources/App/Controllers/UserController.swift Ver fichero

@@ -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<NewSession> {
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<NewSession> {
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<Bool> {
User.query(on: req.db)
.filter(\.$username == username)
.first()
.map { $0 != nil }
}
}

+ 20
- 0
Sources/App/Migrations/CreateToken.swift Ver fichero

@@ -29,3 +29,23 @@ struct CreateTokens: Migration {
database.schema(Token.schema).delete()
}
}
struct UpdateTokens1: Migration {
func prepare(on database: Database) -> EventLoopFuture<Void> {
// 2
database.schema(Token.schema)
// 3
.deleteField("source")
.deleteField("created_at")
.deleteField("expires_at")
.update()
}

// 5
func revert(on database: Database) -> EventLoopFuture<Void> {
database.schema(Token.schema)
.field("source", .int, .required)
.field("created_at", .datetime, .required)
.field("expires_at", .datetime)
.update()
}
}

+ 42
- 0
Sources/App/Migrations/CreateUser.swift Ver fichero

@@ -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()
}
}

+ 16
- 26
Sources/App/Models/Token.swift Ver fichero

@@ -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
}
}

+ 24
- 33
Sources/App/Models/User.swift Ver fichero

@@ -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)
}
}






+ 4
- 0
Sources/App/configure.swift Ver fichero

@@ -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


Cargando…
Cancelar
Guardar