增加token刷新机制

This commit is contained in:
CounterFire2023 2024-06-26 11:35:40 +08:00
parent 5982af8ce6
commit e82053f7d8
5 changed files with 98 additions and 5 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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<PlatEnum, IPlat> = 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 }
}
}

View File

@ -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<typeof AccountClass>, email) {
@prop()
public refreshTokenKey: string
@prop()
public refreshTime: number
public static async findByEmail(this: ReturnModelType<typeof AccountClass>, 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<typeof AccountClass>, refreshToken
: string) {
return this.findOne({ refreshTokenKey: refreshToken, deleted: false }).exec()
}
public verifyPassword(password: string) {
return verifyPass(password, this.password, this.salt)
}

27
src/utils/jwt.utils.ts Normal file
View File

@ -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)
}