impl session tokens

This commit is contained in:
Andrew Glaze
2025-05-16 11:56:30 -04:00
parent 0fb66832e1
commit 0f6baa6f0b
6 changed files with 105 additions and 6 deletions

View File

@@ -1,5 +1,5 @@
{
"originHash" : "d53dfe2770a8b5d4063e71737e72637f59cec754bf4553178a66d10f39bfb1ee",
"originHash" : "01eb28d6d5d4bfc205f7c8b2f8a3dc3a3d278b26c75f239cbd1cf26e4482e33d",
"pins" : [
{
"identity" : "async-http-client",
@@ -55,6 +55,24 @@
"version" : "4.8.1"
}
},
{
"identity" : "jwt",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/jwt.git",
"state" : {
"revision" : "af1c59762d70d1065ddbc0d7902ea9b3dacd1a26",
"version" : "5.1.2"
}
},
{
"identity" : "jwt-kit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/vapor/jwt-kit.git",
"state" : {
"revision" : "03f5013f0b547ce43abe45e7e90711303a3e5495",
"version" : "5.1.2"
}
},
{
"identity" : "multipart-kit",
"kind" : "remoteSourceControl",
@@ -127,6 +145,15 @@
"version" : "1.2.0"
}
},
{
"identity" : "swift-certificates",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-certificates.git",
"state" : {
"revision" : "999fd70c7803da89f3904d635a6815a2a7cd7585",
"version" : "1.10.0"
}
},
{
"identity" : "swift-collections",
"kind" : "remoteSourceControl",

View File

@@ -15,6 +15,8 @@ let package = Package(
.package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.6.0"),
// 🔵 Non-blocking, event-driven networking for Swift. Used for custom executors
.package(url: "https://github.com/apple/swift-nio.git", from: "2.65.0"),
// JWTs
.package(url: "https://github.com/vapor/jwt.git", from: "5.0.0"),
],
targets: [
.executableTarget(
@@ -25,6 +27,7 @@ let package = Package(
.product(name: "Vapor", package: "vapor"),
.product(name: "NIOCore", package: "swift-nio"),
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "JWT", package: "jwt"),
],
swiftSettings: swiftSettings
),

View File

@@ -74,7 +74,36 @@ struct AuthController: RouteCollection {
guard let account = account else {
return Response(status: .badRequest, body: "{\"error\": \"Bad Request\", \"message\": \"Invalid playerId provided.\"}")
}
return Response(status: .notImplemented)
let zatExpiry = Date.now.advanced(by: 43200)
let zrtExpiry = Date.now.advanced(by: 2592000)
let zatTokenJWT = generateToken(accountId: try account.requireID(), expires: zatExpiry, type: .ZAT)
let zrtTokenJWT = generateToken(accountId: try account.requireID(), expires: zrtExpiry, type: .ZRT)
let zatToken = try await req.jwt.sign(zatTokenJWT)
let zrtToken = try await req.jwt.sign(zrtTokenJWT)
let res = LoginDeviceRes(
zatExpiryTime: Int(zatExpiry.timeIntervalSince1970) * 1000,
zrtExpiryTime: Int(zrtExpiry.timeIntervalSince1970) * 1000,
firstLogin: true,
externalToken: "",
zat: zatToken,
zrt: zrtToken,
player: Player(
idpId: account.idpId,
appId: account.appId,
playerId: String(try account.requireID()),
pushOption: PushOptionResponse(night: "n", player: "n"),
regTime: Int(account.regDate.timeIntervalSince1970),
idpAlias: idpAlias,
firstLoginTime: Int(account.firstLogin.timeIntervalSince1970),
status: account.status
)
)
return try await res.encodeResponse(for: req)
}
}
@@ -134,11 +163,8 @@ struct LoginDeviceRes: Content {
struct Player: Content {
let idpId: String
let appId: String
let lang: String
let playerId: String
let agreement: AgreementResponse
let pushOption: PushOptionResponse
let lastLoginTime: Int
let regTime: Int
let idpAlias: String
let firstLoginTime: Int

View File

@@ -1,5 +1,4 @@
import Fluent
import struct Foundation.UUID
/// Property wrappers interact poorly with `Sendable` checking, causing a warning for the `@ID` property
/// It is recommended you write your model with sendability checking on and then suppress the warning

View File

@@ -2,6 +2,7 @@ import NIOSSL
import Fluent
import FluentSQLiteDriver
import Vapor
import JWT
// configures your application
public func configure(_ app: Application) async throws {
@@ -14,6 +15,9 @@ public func configure(_ app: Application) async throws {
app.http.server.configuration.hostname = "0.0.0.0"
app.http.server.configuration.port = 8000
// JWT
await app.jwt.keys.add(hmac: "secret", digestAlgorithm: .sha256)
// register routes
try routes(app)
}

View File

@@ -1,3 +1,43 @@
import JWT
import Fluent
import Foundation
func generateIdpAlias(appId: String, deviceId: String, serialNo: String) -> String {
return "\(appId):\(deviceId):\(serialNo)"
}
func generateToken(accountId: Int, expires: Date, type: SessionType) -> SessionPayload {
return SessionPayload(
accountId: .init(value: String(accountId)),
expiration: .init(value: expires),
type: type.rawValue
)
}
struct SessionPayload: JWTPayload {
enum CodingKeys: String, CodingKey {
case accountId = "sub"
case expiration = "exp"
case type = "type"
}
// The "sub" (subject) claim identifies the principal that is the
// subject of the JWT.
var accountId: SubjectClaim
// The "exp" (expiration time) claim identifies the expiration time on
// or after which the JWT MUST NOT be accepted for processing.
var expiration: ExpirationClaim
// Custom data.
// If true, the user is an admin.
var type: Int
// Run any additional verification logic beyond
// signature verification here.
// Since we have an ExpirationClaim, we will
// call its verify method.
func verify(using algorithm: some JWTAlgorithm) async throws {
try self.expiration.verifyNotExpired()
}
}