311 lines
11 KiB
Swift
311 lines
11 KiB
Swift
import Vapor
|
|
import Fluent
|
|
|
|
struct AuthController: RouteCollection {
|
|
func boot(routes: any RoutesBuilder) throws {
|
|
routes.post("v4", "device", "accessToken", "create", use: self.createAccessToken)
|
|
routes.post("v3", "agreement", "getForLogin", use: self.loginAgreement)
|
|
routes.post("v4", "auth", "loginDevice", use: self.loginDevice)
|
|
routes.post("v3", "zat", "login", use: self.zatLogin)
|
|
}
|
|
|
|
@Sendable
|
|
func createAccessToken(req: Request) async throws -> AccessTokenRes {
|
|
AccessTokenRes(
|
|
accessToken: "fwPla7fQ8ty9+DZT/lD//uWZD4uD6C4lD6gGIIZTLKRTQ52/SLCRmk/370jcWGs+e+1iSoZtL7lj8ov9B0/jHmijH4nsHPQT6pchaQM1M9mtwYNQq0BWhVr9hF0jjCK/a5LIVd1kBac/Gemv29WKEDKSrUS9HxxUigoPRwtOy8m+oDj9FmDJZ+rzqWCc0QjES4Ky0fTpXZ7ESoguDzNmRtW3FYr+OFexw8wBPlwiC4w=",
|
|
expiryTime: Int(Date.init(timeIntervalSinceNow: .init(0)).timeIntervalSince1970)
|
|
)
|
|
}
|
|
|
|
@Sendable
|
|
func loginAgreement(req: Request) async throws -> String {
|
|
let body = try req.content.decode(LoginAgreementReq.self, as: .json)
|
|
|
|
return #"""
|
|
{
|
|
"adAgreementStatus": "n",
|
|
"agreement": {
|
|
"E001": "n",
|
|
"E002": "n",
|
|
"E006": "n",
|
|
"N002": "n",
|
|
"N003": "n",
|
|
"timestamp": "\#(Int(Date().timeIntervalSince1970) * 1000)"
|
|
},
|
|
"agreementPopup": "n",
|
|
"appId": "\#(body.appId)",
|
|
"appName": "World Flipper (NA)",
|
|
"context": "login",
|
|
"country": "\#(body.country)",
|
|
"firstAgreement": "n",
|
|
"idpCode": "\#(body.idpCode)",
|
|
"idpId": "6076008646",
|
|
"informationSecurityCountry": "kr",
|
|
"kakaoSyncAgreementGetSet": "n",
|
|
"kakaoSyncStatus": "off",
|
|
"kakaogameSdkVer": "3.0",
|
|
"lang": "\#(body.lang)",
|
|
"partnerId": 825,
|
|
"partnerName": "주식회사 카카오게임즈",
|
|
"plusFriendStatusInfo": null,
|
|
"policyApplyTime": 1630854000000
|
|
}
|
|
"""#
|
|
}
|
|
|
|
@Sendable
|
|
func loginDevice(req: Request) async throws -> LoginDeviceRes {
|
|
let body = try req.content.decode(LoginDeviceReq.self, as: .json)
|
|
|
|
let idpAlias = generateIdpAlias(appId: body.appId, deviceId: body.deviceId, serialNo: body.serialNo)
|
|
let idpId = body.whiteKey
|
|
var account: Account? = nil
|
|
|
|
if let rawAccountId = req.headers["playerId"].first, let accountId = Int(rawAccountId) {
|
|
if let existingAccount = try await Account.query(on: req.db).filter(\.$id == accountId).first() {
|
|
account = existingAccount
|
|
}
|
|
} else if let existingAccount = try await Account.query(on: req.db).filter(\.$idpId == idpId).first() {
|
|
account = existingAccount
|
|
} else {
|
|
account = Account(appId: body.appId, idpAlias: idpAlias, idpCode: "zd3", idpId: idpId, status: "normal")
|
|
try await account!.create(on: req.db)
|
|
}
|
|
|
|
guard let account = account else {
|
|
throw Abort(.badRequest)
|
|
}
|
|
|
|
if account.idpAlias != idpAlias {
|
|
account.idpAlias = idpAlias
|
|
try await account.save(on: req.db)
|
|
}
|
|
|
|
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: LoginPlayer(
|
|
idpId: account.idpId,
|
|
appId: account.appId,
|
|
playerId: String(try account.requireID()),
|
|
agreemenet: nil,
|
|
pushOption: PushOptionResponse(night: "n", player: "n"),
|
|
regTime: Int(account.regDate.timeIntervalSince1970),
|
|
idpAlias: idpAlias,
|
|
idpCode: nil,
|
|
lang: nil,
|
|
lastLoginTime: nil,
|
|
firstLoginTime: Int(account.firstLogin.timeIntervalSince1970),
|
|
status: account.status
|
|
)
|
|
)
|
|
|
|
return res
|
|
}
|
|
|
|
@Sendable
|
|
func zatLogin(req: Request) async throws -> LoginDeviceRes {
|
|
let body = try req.content.decode(ZatLoginReq.self, as: .json)
|
|
if let session = try? await req.jwt.verify(body.zat, as: SessionPayload.self) {
|
|
guard session.type == SessionType.ZAT.rawValue && session.accountId.value == body.playerId else {
|
|
throw Abort(.badRequest, reason: "Invalid zat provided.")
|
|
}
|
|
}
|
|
guard
|
|
let accountId = Int(body.playerId),
|
|
let account = try await Account.query(on: req.db)
|
|
.filter(\.$id == accountId)
|
|
.first()
|
|
else {
|
|
throw Abort(.badRequest, reason: "Invalid playerId")
|
|
}
|
|
|
|
account.lastLogin = Date.now
|
|
try await account.save(on: req.db)
|
|
|
|
let zatExpiry = Date.now.advanced(by: 43200)
|
|
|
|
let session = generateToken(accountId: try account.requireID(), expires: zatExpiry, type: SessionType.ZAT)
|
|
let zatToken = try await req.jwt.sign(session)
|
|
return LoginDeviceRes(
|
|
zatExpiryTime: Int(zatExpiry.timeIntervalSince1970) * 1000,
|
|
zrtExpiryTime: nil,
|
|
firstLogin: false,
|
|
externalToken: "",
|
|
zat: zatToken,
|
|
zrt: nil,
|
|
player: LoginPlayer(
|
|
idpId: account.idpId,
|
|
appId: account.appId,
|
|
playerId: String(accountId),
|
|
agreemenet: AgreementResponse(E001: "y", E002: "y", E006: "y", N002: "n", N003: "n", timestamp: "1717623430484"),
|
|
pushOption: PushOptionResponse(night: "n", player: "n"),
|
|
regTime: Int(account.regDate.timeIntervalSince1970) * 1000,
|
|
idpAlias: account.idpAlias,
|
|
idpCode: account.idpCode,
|
|
lang: body.lang,
|
|
lastLoginTime: Int(account.lastLogin.timeIntervalSince1970) * 1000,
|
|
firstLoginTime: Int(account.firstLogin.timeIntervalSince1970) * 1000,
|
|
status: account.status
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
|
|
struct ZatLoginReq: Content {
|
|
let adid: String
|
|
let appId: String
|
|
let appSecret: String
|
|
let appVer: String
|
|
let clientTime: Int
|
|
let country: String
|
|
let deviceId: String
|
|
let deviceModel: String
|
|
let fields: [String]
|
|
let gsiToken: Bool?
|
|
let lang: String
|
|
let loginType: String
|
|
let market: String
|
|
let network: String
|
|
let os: String
|
|
let playerId: String
|
|
let resume: Bool
|
|
let retryNo: Int?
|
|
let sdkVer: String
|
|
let telecom: String
|
|
let timezoneOffset: Int
|
|
let usimCountry: String?
|
|
let whiteKey: String
|
|
let zat: String
|
|
|
|
init(from decoder: any Decoder) throws {
|
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
|
adid = try container.decode(String.self, forKey: .adid)
|
|
appId = try container.decode(String.self, forKey: .appId)
|
|
appSecret = try container.decode(String.self, forKey: .appSecret)
|
|
appVer = try container.decode(String.self, forKey: .appVer)
|
|
clientTime = try container.decode(Int.self, forKey: .clientTime)
|
|
country = try container.decode(String.self, forKey: .country)
|
|
deviceId = try container.decode(String.self, forKey: .deviceId)
|
|
deviceModel = try container.decode(String.self, forKey: .deviceModel)
|
|
fields = try container.decode([String].self, forKey: .fields)
|
|
gsiToken = try container.decodeIfPresent(Bool.self, forKey: .gsiToken)
|
|
lang = try container.decode(String.self, forKey: .lang)
|
|
loginType = try container.decode(String.self, forKey: .loginType)
|
|
market = try container.decode(String.self, forKey: .market)
|
|
network = try container.decode(String.self, forKey: .network)
|
|
os = try container.decode(String.self, forKey: .os)
|
|
playerId = try container.decode(String.self, forKey: .playerId)
|
|
resume = try container.decode(Bool.self, forKey: .resume)
|
|
retryNo = try container.decodeIfPresent(Int.self, forKey: .retryNo)
|
|
sdkVer = try container.decode(String.self, forKey: .sdkVer)
|
|
telecom = try container.decode(String.self, forKey: .telecom)
|
|
timezoneOffset = try container.decode(Int.self, forKey: .timezoneOffset)
|
|
usimCountry = try container.decodeIfPresent(String.self, forKey: .usimCountry)
|
|
whiteKey = try container.decode(String.self, forKey: .whiteKey)
|
|
zat = try container.decode(String.self, forKey: .zat)
|
|
}
|
|
}
|
|
|
|
struct AccessTokenRes: Content {
|
|
let accessToken: String
|
|
let expiryTime: Int
|
|
}
|
|
|
|
struct LoginAgreementReq: Content {
|
|
let deviceId: String
|
|
let os: String
|
|
let country: String
|
|
let lang: String
|
|
let appId: String
|
|
let idpCode: String
|
|
let serialNo: String
|
|
let idpId: String
|
|
}
|
|
|
|
struct LoginDeviceReq: Content {
|
|
let lang: String
|
|
let clientTime: Int
|
|
let deviceId: String
|
|
let serialNo: String
|
|
let country: String
|
|
let whiteKey: String
|
|
let market: String
|
|
let appSecret: String
|
|
let deviceAppKey: String
|
|
let sdkVer: String
|
|
let appVer: String
|
|
let os: String
|
|
let loginType: String
|
|
let accessToken: String
|
|
let resume: Bool
|
|
let osVer: String
|
|
let appId: String
|
|
let deviceModel: String
|
|
let network: String
|
|
let isIosAppOnMac: Bool
|
|
let adid: String
|
|
let timezoneOffset: Int
|
|
let fields: [String]
|
|
let telecom: String
|
|
}
|
|
|
|
struct LoginDeviceRes: Content {
|
|
let zatExpiryTime: Int
|
|
let zrtExpiryTime: Int?
|
|
let firstLogin: Bool
|
|
let externalToken: String
|
|
let zat: String
|
|
let zrt: String?
|
|
let player: LoginPlayer
|
|
}
|
|
|
|
struct LoginPlayer: Content {
|
|
let idpId: String
|
|
let appId: String
|
|
let playerId: String
|
|
let agreemenet: AgreementResponse?
|
|
let pushOption: PushOptionResponse
|
|
let regTime: Int
|
|
let idpAlias: String
|
|
let idpCode: String?
|
|
let lang: String?
|
|
let lastLoginTime: Int?
|
|
let firstLoginTime: Int
|
|
let status: String
|
|
}
|
|
|
|
struct AgreementResponse: Content {
|
|
let E001: String
|
|
let E002: String
|
|
let E006: String
|
|
let N002: String
|
|
let N003: String
|
|
let timestamp: String
|
|
}
|
|
|
|
struct PushOptionResponse: Content {
|
|
let night: String
|
|
let player: String
|
|
}
|
|
|
|
enum SessionType: Int {
|
|
case ZAT = 0
|
|
case ZRT = 1
|
|
case VIEWER = 2
|
|
}
|