From e82053f7d89e2a34f93381d93aeed6a982fb55aa Mon Sep 17 00:00:00 2001 From: CounterFire2023 <136581895+CounterFire2023@users.noreply.github.com> Date: Wed, 26 Jun 2024 11:35:40 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0token=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.development | 3 ++ .env.production | 3 ++ src/controllers/login.controller.ts | 55 +++++++++++++++++++++++++++-- src/modules/Account.ts | 15 ++++++-- src/utils/jwt.utils.ts | 27 ++++++++++++++ 5 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/utils/jwt.utils.ts diff --git a/.env.development b/.env.development index 14f01b4..3fc8132 100644 --- a/.env.development +++ b/.env.development @@ -4,6 +4,9 @@ API_TOKEN_SECRET_PRIVATE=MC4CAQAwBQYDK2VwBCIEIKdK/eFQ2+Q/ml4ruDAItNIwGnQMQm76UX0 API_TOKEN_SECRET_PUBLIC=MCowBQYDK2VwAyEAySgE/YiiI2fzpXaco+OWeDAKymEoqqLYYb6RKOEU1n8= API_TOKEN_EXPIRESIN=1d +REFRESH_TOKEN_SECRET_PRIVATE=MC4CAQAwBQYDK2VwBCIEIMNKHEo6d3B6O4SiB4a5cFgKNNCMGj0BaRhPx5wG3DrZ +REFRESH_TOKEN_SECRET_PUBLIC=MCowBQYDK2VwAyEAWFiOqbdxFu1XW5MoI3YeVRBZ4JoEWQMwXg49v1ssaXM= + GOOGLE_OAUTH_CLIENT="53206975661-asnf3qe4bg29p8h981pgf099osvrjbme.apps.googleusercontent.com" GOOGLE_OAUTH_CLIENT2="53206975661-ih3r0ubph3rqejdq97b029difbrk2bqj.apps.googleusercontent.com" diff --git a/.env.production b/.env.production index 03f30ab..668a054 100644 --- a/.env.production +++ b/.env.production @@ -4,6 +4,9 @@ API_TOKEN_SECRET_PRIVATE=MC4CAQAwBQYDK2VwBCIEIKdK/eFQ2+Q/ml4ruDAItNIwGnQMQm76UX0 API_TOKEN_SECRET_PUBLIC=MCowBQYDK2VwAyEAySgE/YiiI2fzpXaco+OWeDAKymEoqqLYYb6RKOEU1n8= API_TOKEN_EXPIRESIN=1d +REFRESH_TOKEN_SECRET_PRIVATE=MC4CAQAwBQYDK2VwBCIEIMNKHEo6d3B6O4SiB4a5cFgKNNCMGj0BaRhPx5wG3DrZ +REFRESH_TOKEN_SECRET_PUBLIC=MCowBQYDK2VwAyEAWFiOqbdxFu1XW5MoI3YeVRBZ4JoEWQMwXg49v1ssaXM= + GOOGLE_OAUTH_CLIENT="53206975661-asnf3qe4bg29p8h981pgf099osvrjbme.apps.googleusercontent.com" GOOGLE_OAUTH_CLIENT2="53206975661-ih3r0ubph3rqejdq97b029difbrk2bqj.apps.googleusercontent.com" diff --git a/src/controllers/login.controller.ts b/src/controllers/login.controller.ts index 363a3fe..728a3ed 100644 --- a/src/controllers/login.controller.ts +++ b/src/controllers/login.controller.ts @@ -13,7 +13,9 @@ import { PlatFacebook } from 'plats/PlatFacebook' import { PlatGoogle } from 'plats/PlatGoogle' import { PlatTikTok } from 'plats/PlatTikTok' import { checkReleation } from 'service/game.svr' +import { generateRefreshToken, verifyRefreshToken } from 'utils/jwt.utils' import { ZError, BaseController, role, ROLE_ANON, router } from 'zutils' +import { uuid } from 'zutils/utils/security.util' const plats: Map = new Map([ [PlatEnum.GOOGLE, new PlatGoogle()], @@ -98,7 +100,8 @@ class LoginController extends BaseController { @role(ROLE_ANON) @router('post /wallet/login/general') async generalLogin(req, res) { - const { code, channel, account } = req.params + // nb: 是否不返回unionAccount相关信息 + const { code, channel, account, nb } = req.params logger.db('login', req) if (!code) { throw new ZError(10, 'code not found') @@ -113,7 +116,13 @@ class LoginController extends BaseController { data.platform = api_platform } const user = await Account.insertOrUpdate({ plat: channel, openId }, data) - const { unionAccount, walletUser } = await parseBindAccount(account, channel, user) + let unionAccount = {id: '', gameAccount: ''} + let walletUser = user + if (!nb) { + const res = await parseBindAccount(account, channel, user) + unionAccount = res.unionAccount + walletUser = res.walletUser + } if (plat.afterLogin) { await plat.afterLogin(user) } @@ -125,6 +134,46 @@ class LoginController extends BaseController { version: walletUser.accountVersion || 0, plat: walletUser.plat, }) - return { token: ztoken } + const refreshTokenKey = uuid() + walletUser.refreshTime = Date.now() + walletUser.refreshTokenKey = refreshTokenKey + await walletUser.save() + const refreshToken1 = generateRefreshToken({ id: refreshTokenKey }) + return { token: ztoken, refreshToken: refreshToken1 } + } + + @role(ROLE_ANON) + @router('post /wallet/refresh_token') + async refreshToken(req, res) { + logger.db('refresh_token', req) + const { refreshToken } = req.params + if (!refreshToken) { + throw new ZError(10, 'no refresh token') + } + const tokenData = verifyRefreshToken(refreshToken) + if (!tokenData || !tokenData.id) { + throw new ZError(11, 'refresh token invalid') + } + const user = await Account.findByRefreshToken(tokenData.id) + if (!user) { + throw new ZError(12, 'account not found') + } + if (user.locked) { + throw new ZError(13, 'account locked') + } + const refreshTokenKey = uuid() + user.refreshTime = Date.now() + user.refreshTokenKey = refreshTokenKey + await user.save() + const refreshToken1 = generateRefreshToken({ id: refreshTokenKey }) + const ztoken = await res.jwtSign({ + id: user.id, + uid: '', + gid: '', + openid: user.openId, + version: user.accountVersion || 0, + plat: user.plat, + }) + return { refreshToken: refreshToken1, token: ztoken } } } diff --git a/src/modules/Account.ts b/src/modules/Account.ts index 566b074..07a5bca 100644 --- a/src/modules/Account.ts +++ b/src/modules/Account.ts @@ -2,7 +2,7 @@ import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, import { dbconn } from 'decorators/dbconn' import { Base, TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' import { BaseModule } from './Base' -import { genRandomString, sha512 } from 'zutils/utils/security.util' +import { genRandomString, sha512, uuid } from 'zutils/utils/security.util' import { PlatEnum } from 'enums/PlatEnum' /** @@ -30,6 +30,7 @@ export function verifyPass(userpassword: string, passwordDb: string, salt: strin export interface AccountClass extends Base, TimeStamps {} @dbconn() @index({ plat: 1, openId: 1 }, { unique: true }) +@index({ refreshTokenKey: 1 }, { unique: false }) @index({ plat: 1, email: 1 }, { collation: { locale: 'en', strength: 2 } }) @modelOptions({ schemaOptions: { collection: 'account', timestamps: true }, options: { allowMixed: Severity.ALLOW } }) export class AccountClass extends BaseModule { @@ -100,7 +101,12 @@ export class AccountClass extends BaseModule { @prop() public platform: string - public static async findByEmail(this: ReturnModelType, email) { + @prop() + public refreshTokenKey: string + @prop() + public refreshTime: number + + public static async findByEmail(this: ReturnModelType, email: string) { return this.findOne({ email, plat: PlatEnum.EMAIL }).exec() } @@ -112,6 +118,11 @@ export class AccountClass extends BaseModule { } } + public static async findByRefreshToken(this: ReturnModelType, refreshToken + : string) { + return this.findOne({ refreshTokenKey: refreshToken, deleted: false }).exec() + } + public verifyPassword(password: string) { return verifyPass(password, this.password, this.salt) } diff --git a/src/utils/jwt.utils.ts b/src/utils/jwt.utils.ts new file mode 100644 index 0000000..9b4c2b8 --- /dev/null +++ b/src/utils/jwt.utils.ts @@ -0,0 +1,27 @@ +import { createSigner, createVerifier } from 'fast-jwt' + +const privateKey = `-----BEGIN PRIVATE KEY----- +${process.env.REFRESH_TOKEN_SECRET_PRIVATE} +-----END PRIVATE KEY----- +` +const publicKey = `-----BEGIN PUBLIC KEY----- +${process.env.REFRESH_TOKEN_SECRET_PUBLIC} +-----END PUBLIC KEY----- +` +const REFRESH_TOKEN_EXPIRES_IN = 30 * 24 * 60 * 60 * 1000 +export const generateRefreshToken = (data: any) => { + const signSync = createSigner({ + algorithm: 'EdDSA', + expiresIn: REFRESH_TOKEN_EXPIRES_IN, + key: privateKey, + }) + return signSync(data) +} + +export const verifyRefreshToken = (token: string) => { + const verifier = createVerifier({ + algorithms: ['EdDSA'], + key: publicKey, + }) + return verifier(token) +} \ No newline at end of file