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 }