소스 검색

Multiple Auth Methods

main
rpm-mcarman 2 년 전
부모
커밋
057582bdde
9개의 변경된 파일280개의 추가작업 그리고 20개의 파일을 삭제
  1. +62
    -0
      Sources/App/Controllers/cntrlAssociate.swift
  2. +1
    -0
      Sources/App/Migrations/AllMigrations.swift
  3. +68
    -15
      Sources/App/Models/mdlAssociate.swift
  4. +29
    -1
      Sources/App/Models/mdlJWT.swift
  5. +40
    -1
      Sources/App/Models/mdlToken.swift
  6. +7
    -3
      Sources/App/configure.swift
  7. +8
    -0
      Sources/App/routes.swift
  8. +51
    -0
      jwtRS256.key
  9. +14
    -0
      jwtRS256.key.pub

+ 62
- 0
Sources/App/Controllers/cntrlAssociate.swift 파일 보기

@@ -18,9 +18,69 @@ struct cntrlAssociate: RouteCollection {
rts.get(":x", use: read)
rts.post(use: create)
rts.delete(":x", use: delete)
rts.post("create", use: createUser)
let passwordProtected = rts.grouped(mdlAssociate.authenticator(), mdlAssociate.guardMiddleware())
passwordProtected.post("login", use: login)
passwordProtected.post("tlogin", use: jwtauth)
let tokenProtected = rts.grouped(mdlToken.authenticator())
tokenProtected.get("tauth", use: auth)
//Additional routs added here
}
@Sendable
func jwtauth(req: Request) async throws -> ClientTokenResponse{
let user = try req.auth.require(mdlAssociate.self)
let payload = SessionToken(user: user)
return ClientTokenResponse(token: try req.jwt.sign(payload))
}
@Sendable
func auth(req: Request) async throws -> Response{
let result = try req.auth.require(mdlAssociate.self)
let assoc = mdlAssociate.Output(id: result.id, firstname: result.firstname, lastname: result.lastname)
guard let response = try? await assoc.encodeResponse(status: .ok, for: req) else{
throw Abort(.noContent, reason: "Something went wrong decoding data. Associate could not be shown.")
}
return response
}
@Sendable
func login(req: Request) async throws -> Response{
guard let result = try? req.auth.require(mdlAssociate.self) else{
throw Abort(.unauthorized)
}
let token = try await result.generateToken()
try await token.save(on: req.db)
guard let response = try? await token.encodeResponse(status: .ok, for: req) else{
throw Abort(.noContent, reason: "Something went wrong decoding data. Token could not be shown.")
}
return response
}
@Sendable
func createUser(req: Request) async throws -> Response{
try mdlAssociate.CreateUser.validate(content: req)
let create = try req.content.decode(mdlAssociate.CreateUser.self)
guard create.password == create.confirmpassword else{
throw Abort(.badRequest, reason: "Passwords did not match")
}
let user = try mdlAssociate(
firstname: create.firstname,
lastname: create.lastname,
username: create.username,
password: Bcrypt.hash(create.password)
)
try await user.save(on: req.db)
let assoc = mdlAssociate.Output(id: user.id, firstname: user.firstname, lastname: user.lastname)
guard let response = try? await assoc.encodeResponse(status: .ok, for: req) else{
throw Abort(.noContent, reason: "Something went wrong decoding data. Associate could not be shown.")
}
return response
}
@Sendable
func index(req: Request) async throws -> Response {
let obj = try await mdlAssociate.ObjQuery(req: req).all()
@@ -66,6 +126,8 @@ struct cntrlAssociate: RouteCollection {
}
@Sendable
func delete(req: Request) async throws -> Response {
guard let obj = try? await mdlAssociate.ObjQuery(req: req).first() else {


+ 1
- 0
Sources/App/Migrations/AllMigrations.swift 파일 보기

@@ -20,6 +20,7 @@ public func doMigrations(_ app: Application) async throws{
app.migrations.add(mdlXAssocDept.Create())
app.migrations.add(mdlXDocDept.Create())
app.migrations.add(mdlDocument.tmpField())
app.migrations.add(mdlAssociate.Mod1())
try await app.autoMigrate()
}

+ 68
- 15
Sources/App/Models/mdlAssociate.swift 파일 보기

@@ -10,15 +10,16 @@ import Fluent
import Vapor
import Foundation
import FluentKit
import JWT

final class mdlAssociate: Model, Content {
static let schema = "associate"
@ID(custom: "id", generatedBy: .database) var id: Int?
@Field(key: "firstName") var firstname: String?
@Field(key: "lastName") var lastname: String?
@Field(key: "firstName") var firstname: String
@Field(key: "lastName") var lastname: String
@Field(key: "userName") var username: String
@Field(key: "password") var password: String
@Siblings(through: mdlXAssocDept.self, from: \.$associate, to: \.$department) var departments: [mdlDepartment]
@@ -30,34 +31,31 @@ final class mdlAssociate: Model, Content {
init() { }
init(id: Int? = nil,
firstname: String? = nil,
lastname: String? = nil,
firstname: String,
lastname: String,
username: String,
password: String ,
createuserid: Int? = nil,
updateuserid: Int? = nil
) {
self.id = id
self.firstname = firstname
self.lastname = lastname
self.username = username
self.password = password
self.createuserid = createuserid
self.updateuserid = updateuserid
}
}
extension mdlAssociate{
struct User: JWTPayload{
var id: Int
var email: String
var password: String
func verify(using signer: JWTSigner) throws{
}
}
struct Create: AsyncMigration {
func prepare(on database: Database) async throws {
try await database.schema("associate")
.field("id", .int, .identifier(auto: true))
.field("firstName", .string)
.field("lastName", .string)
// .field("userName", .string)
// .field("password", .string)
.field("createDate", .datetime)
.field("createUserId", .int)
.field("updateDate", .datetime)
@@ -69,6 +67,21 @@ extension mdlAssociate{
try await database.schema("associate").delete()
}
}
struct Mod1: AsyncMigration{
func prepare(on database: Database) async throws {
try await database.schema("associate")
.field("userName", .string).unique(on: "userName")
.field("password", .string)
.update()
}
func revert(on database: Database) async throws {
try await database.schema("associate")
.deleteField("userName")
.deleteField("password")
.update()
}
}
public static func ObjQuery(req: Request) async throws -> QueryBuilder<mdlAssociate>{
let query = mdlAssociate.query(on: req.db)
@@ -107,4 +120,44 @@ extension mdlAssociate{
}
}
}

extension mdlAssociate{
struct CreateUser: Content{
var username: String
var password: String
var confirmpassword: String
var firstname: String
var lastname: String
}
struct Output: Content{
var id: Int?
var firstname: String
var lastname: String
}
}
extension mdlAssociate.CreateUser: Validatable{
static func validations(_ validations: inout Validations){
validations.add("firstname", as: String.self, is: !.empty)
validations.add("lastname", as: String.self, is: !.empty)
validations.add("username", as: String.self, is: !.empty)
validations.add("password", as: String.self, is: .count(8...))
}
}
extension mdlAssociate: ModelAuthenticatable{
static let usernameKey = \mdlAssociate.$username
static var passwordHashKey = \mdlAssociate.$password
func verify(password: String) throws -> Bool {
try Bcrypt.verify(password, created: self.password)
}
}
extension mdlAssociate{
func generateToken() async throws -> mdlToken {
try .init(
value: [UInt8].random(count: 16).base64,
userid: self.requireID()
)
}
}

+ 29
- 1
Sources/App/Models/mdlJWT.swift 파일 보기

@@ -5,4 +5,32 @@
// Created by Michiel Carman on 4/16/24.
//

import Foundation
import Vapor
import Fluent
import JWT

struct SessionToken: Content, Authenticatable, JWTPayload{
var expirationtime: TimeInterval = 60 * 15
var expiration: ExpirationClaim
var userid: Int
init(userid: Int){
self.userid = userid
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationtime))
}
init(user: mdlAssociate){
self.userid = try! user.requireID()
self.expiration = ExpirationClaim(value: Date().addingTimeInterval(expirationtime))
}
func verify(using signer: JWTKit.JWTSigner) throws {
try expiration.verifyNotExpired()
}
}
struct ClientTokenResponse: Content{
var token: String
}

+ 40
- 1
Sources/App/Models/mdlToken.swift 파일 보기

@@ -5,4 +5,43 @@
// Created by Michiel Carman on 4/16/24.
//

import Foundation
import Fluent
import Vapor

final class mdlToken: Model, Content{
static let schema = "user_token"
@ID(custom: "id", generatedBy: .database) var id: Int?
@Field(key: "value") var value: String
@Parent(key: "associateId") var user: mdlAssociate
init(){}
init(id: Int? = nil, value: String, userid: mdlAssociate.IDValue){
self.id = id
self.value = value
self.$user.id = userid
}
}
extension mdlToken{
struct Create: AsyncMigration {
var name: String { "CreateUserToken"}
func prepare(on database: Database) async throws {
try await database.schema("user_token")
.field("id", .int, .identifier(auto: true))
.field("value", .string)
.field("associateId", .int, .required, .references("associate", "id"))
.unique(on: "value")
.create()
}
func revert(on database: Database) async throws {
try await database.schema("user_token").delete()
}
}
}
extension mdlToken: ModelTokenAuthenticatable{
static let valueKey = \mdlToken.$value
static let userKey = \mdlToken.$user
var isValid: Bool{true}
}

+ 7
- 3
Sources/App/configure.swift 파일 보기

@@ -2,19 +2,23 @@ import NIOSSL
import Fluent
import FluentPostgresDriver
import Vapor
//import JWT

extension String {
var bytes: [UInt8] { .init(self.utf8) }
}


//extension JWKIdentifier{
// static let `public` = JWKIdentifier(string: "public")
// static let `private` = JWKIdentifier(string: "private")
//}

// configures your application
public func configure(_ app: Application) async throws {
// let privateKey = try String(contentsOfFile: app.directory.workingDirectory + "jwtRS256.key")
// let privateKey = try String(contentsOfFile: app.directory.workingDirectory + "jwtRS256.key").replacingOccurrences(of: "\\n", with: "\n")
// let privateSigner = try JWTSigner.rs256(key: .private(pem: privateKey.bytes))
//
// let publicKey = try String(contentsOfFile: app.directory.workingDirectory + "jwtRS256.key.pub")
// let publicKey = try String(contentsOfFile: app.directory.workingDirectory + "jwtRS256.key.pub").replacingOccurrences(of: "\\n", with: "\n")
// let publicSigner = try JWTSigner.rs256(key: .public(pem: publicKey.bytes))
//
// app.jwt.signers.use(privateSigner, kid: .private)


+ 8
- 0
Sources/App/routes.swift 파일 보기

@@ -2,6 +2,13 @@ import Foundation
import Fluent
import Vapor
import Crypto
//import JWT

//struct Example: JWTPayload, Content{
//
// var test: String
// func verify(using signer: JWTSigner) throws{}
//}

func routes(_ app: Application) throws {
app.routes.caseInsensitive = true
@@ -11,6 +18,7 @@ func routes(_ app: Application) throws {
baseApp.get { req async in
"Training Services Version 1.0"
}
try baseApp.register(collection: cntrlAssociate())
try baseApp.register(collection: cntrlDepartment())
try baseApp.register(collection: cntrlDocument())


+ 51
- 0
jwtRS256.key 파일 보기

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKQIBAAKCAgEAx0IhjUkXs2RSz7bhcrISg6u+So5EdyLN/3gQK8N5CFshS9n2
Div8QYBWTsJU/vJ8qn1g8jrZ0PCpEbvfTZVHs3Lb5N4USSUHe+JpyFup1EnOzTAx
Au4OoOSnnoiBIHv0gwunFE9lH1ey/p/ro+uBz9iIBmwtn+4xGPkcZ4QPeKOUudUL
ZEQ3DK7MNF9rcCAyts/xyrQ4mHcpHM4r8SeWt4aiblWBkdQqhQdKbUQbyrfpv3AZ
GVRoUjYkIfvp5BiCkZADj/knhxxvdgKNv9D8hOhkQ69H6uFk4jT1QsRa0GuKPCWf
ZQOxdCCpk65PlrrAXryWWPMfywry/vuMCLUJJiUVjXoYIKOzz8GNf214RhmHuW1b
aNDgkLV8EQ5GfFcWd92Tb/04eH9TI3M7iSGaIShRCbRJ8szFSzGCGOh2DNGXfj1B
PixlFP1s/Vdin9UQMXMZpQnw1IgNdMksJ8hGMH2qxo6PrJvxIgjlW1F0/m9CT9mj
2GIkYA5CwPej0VidkUgqFOeY8fGXw82YP44jAHpOO3cmC2zPNxqUN09caJj/BaNQ
D9h0mxyW/t6xVuvZzN/TmUFeZ8UOgxnqCsu9QFrah4S9JN/7ZssYI+PrWaeYYdM9
+8QJdNzG89G+Krcfez4ZXD8RmarpMpu9Ibxn4tLwQKJUg3+i15IQYem2pEUCAwEA
AQKCAgEAnCCWF5VZC8glFvpOBke/OAqe7N4yiULtNCpISXI9aK23Fi7vnEUcv9om
RkoAvU8VHaVmDAbO2udnIi0aGDlK2DG8WMUmEfYAujI6/rHMDTnT33TOQmRzc+AV
8qM0wlY9zVyZCtH3Ayr9ZoO56FPFAC3Cz3199l11fQLcHcQdN0K9ayqiZWsJ01c3
TCYkEdQ4yIOBCnpSCiz8OsTVK30AnEbTCS7YeoJK2rdOuvTt7dZTQhw8nltD7n7b
qwEvT/lgfVvhXrM0JKrELyxrEwxWgHFy9pHmAQWgvZoPPsSJ/9rzkpNMJKNTLRHW
rY+cbxdfWhvclk/R7o9ivSYuoDfxtN5hDkYeQ1yFMxNgYqwyjayRmwii4XILf4dd
GJtqxf7sThR57XzKs85dixUPL154a5xWQFapeXuksItw2NGp5yAQotAYjdI4Sn1q
BXn4edZf92PAKBrkboNbJma3NCOQ9ZF2pil/wvi8gjAq5osKhptsXKY2v6/XwJ2+
duivPI9HuVheNABNKDaaktEXUwAgDl9a9xdM/CqZJjWUxZ2Ro/sepvFT1XiSIX17
PGYTfNPSqzgbStUiUuAiimyICZXiVTEaqGtv5penG659nx9/Th1E4xnm//XbwrSm
dW8yZFEaXvMtY1lCuuMHTeRkfsvMl+oesVX4+1eAmsLmT2VVPRUCggEBAO2Bgx3P
YIUbxcGqVKP5xBG3TvWx413aprqd1qr5mGhT7mnXsTXaR66fE9gsKHxauVLZKDMK
d2lG8AQitsuT1MRUhtKSEgtmmbJnnKG1TCsueFYBFyrXS6Z4RZq7hVopaJ1Wq2IT
KmazUXPYcvJoaYaAVhlV6Ax3ayua3xu2xz0p9kDlZjNSClLXd36EANtfa+iGiPRJ
rJXWtmYz0JB946RkzfyUZDCw3Iv+LdOH3g2/kR26IwtgDN3hBdeQ6uXAYMv6qhmY
vKaHRzByWlGDrnfGZnjNT6gCILHBCTfHgGgeRdgfdRPDmxwDttW4ZGgYHTtvk+UQ
Ge2hmwUs67eRqNsCggEBANbGLy+eRA+8I72irVWLhSuv2pm7bZXGoLTtHUQme0hm
J4RatOOsar/BsEONQ5zjbSygfiNrrlAYTRuq+BKwq8+BQiSHgoAStqEozw8sYMkg
JSqZLjkr4jBY3k+Caax1Q56FgybA7zlWRJ3b51bxVXOdBOudXrsrSWYNiMGRZL9R
H8gKwaVUjAV+hRzw9WHcv6Z8wEjDBJU/erk8wWgQXp1gYfTmD7+EPcqjG1kQFR7G
NgVwNMv0nSAc2b78/aE9rofPoVNFYTZb9omGzkHqSlgzfg8RIThFj/rHkeKBZgcL
I5fWYpeHtBQ8ugGpIkAPNCWuJ0J5CR4MDaVqc71qYV8CggEBAMUqzTSEIfaeta8u
vhFamcCzUqN5R+10RRGLUtihWDrT4oqPexff275FZW4CSqHhlxQGHu8wcy08rKhi
QSoD0ZAjM8G/bad1R4bpqQg9v7vhWhjb0uXH0Y+jLPVlUzEkSyrC9of2rS6REr9/
Iv3GXERCajeiL3+Z66hJa4pQVFqcaUg6qHtWUievpA2tfCChuK1ScoOKXfIOS2HR
BgXN3Jtu6+oRLpOVX6IAIv8GPftf2xK4obshw8y2r+ji735IFJOBxpB1UbdVem3n
oL8m+EGKvSrwFn69NXMbc5GQ1zKYXxcqFkGQE0ZQdwo5h3E7A5mB6V8x3LR/gnfr
cMv16h0CggEABO908iEu6zWllJwCY67bFo/p9BJsiD6SPtYcEA7aUmWWjuIOuhSi
N1naFRNP9HD9d0MQ4vt8e/ay/unE3ZkVuaT8wh4jD5JehAGOEIkyiCym7f2rJEEv
53CDW0xh5UakkSX8R42zcZvb48zD2imdjxSSol9xZ9eszh2NgPU4XXUdx9kHbict
OIsZv/qBiQgrl0XSnVlt54gX593Pi/akVfV7CsemoP4G9XseC8Pk2Foxr5pOfElY
uv4uusbRhT0Hn7NTv7gebcNbPPnOD5G0t6YnRW1dWA+xKVAguBXFJHBLFxgkF9tU
dgqXM6aRVOuEhUElQc/KN9iLD7wMgNPK/QKCAQA4ivEUfhbBiIqOfk4jwmnOzrBU
jycdEqFo5ZkM83/L16ukmrOTLnSTqdrEIsGry/D0K5/LOOBJDTScSA5LbFordcZm
Ofm5yXD1anWgOZUx+xR3ZF1UIVrtm2WC65iOVDrcYZsDGRFi6jtBFZuGV7+LtRhC
rF0jquh55KzaoG3c0LfECi/3FReZmT3SkIHJxyqxWYRlasJVkcKERZ10y+3KeGkv
ro5Rd+nRVCt9/4qi1bqsRd5YCAdbNSTkFEblbqcWVlcX577qUU0xOCm4fJuONVfd
vA6QjcLflZDYdO7omPxStq/rbEUBOBi8XXx/FajkC1nL9kq0ZSc9ODvluROD
-----END RSA PRIVATE KEY-----

+ 14
- 0
jwtRS256.key.pub 파일 보기

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAx0IhjUkXs2RSz7bhcrIS
g6u+So5EdyLN/3gQK8N5CFshS9n2Div8QYBWTsJU/vJ8qn1g8jrZ0PCpEbvfTZVH
s3Lb5N4USSUHe+JpyFup1EnOzTAxAu4OoOSnnoiBIHv0gwunFE9lH1ey/p/ro+uB
z9iIBmwtn+4xGPkcZ4QPeKOUudULZEQ3DK7MNF9rcCAyts/xyrQ4mHcpHM4r8SeW
t4aiblWBkdQqhQdKbUQbyrfpv3AZGVRoUjYkIfvp5BiCkZADj/knhxxvdgKNv9D8
hOhkQ69H6uFk4jT1QsRa0GuKPCWfZQOxdCCpk65PlrrAXryWWPMfywry/vuMCLUJ
JiUVjXoYIKOzz8GNf214RhmHuW1baNDgkLV8EQ5GfFcWd92Tb/04eH9TI3M7iSGa
IShRCbRJ8szFSzGCGOh2DNGXfj1BPixlFP1s/Vdin9UQMXMZpQnw1IgNdMksJ8hG
MH2qxo6PrJvxIgjlW1F0/m9CT9mj2GIkYA5CwPej0VidkUgqFOeY8fGXw82YP44j
AHpOO3cmC2zPNxqUN09caJj/BaNQD9h0mxyW/t6xVuvZzN/TmUFeZ8UOgxnqCsu9
QFrah4S9JN/7ZssYI+PrWaeYYdM9+8QJdNzG89G+Krcfez4ZXD8RmarpMpu9Ibxn
4tLwQKJUg3+i15IQYem2pEUCAwEAAQ==
-----END PUBLIC KEY-----

불러오는 중...
취소
저장