增加web端钱包登录相关接口

This commit is contained in:
zhl 2023-03-02 11:56:48 +08:00
parent 81ff8b5778
commit 4258a7fd26
6 changed files with 215 additions and 12 deletions

View File

@ -0,0 +1,65 @@
import BaseController from "common/base.controller";
import {ZError} from "common/ZError";
import {role, router} from "decorators/router";
import {BridgeSvr} from "service/bridge.svr";
class WalletBridgeController extends BaseController {
@role('anon')
@router('post /bridge/regist')
async registWebClient(req, res) {
let { clientId } = req.params;
if (!clientId) {
throw new ZError(10, 'params mismatch')
}
const qrId = new BridgeSvr().registClientReq(clientId)
const token = await res.jwtSign({ qrId })
return { token }
}
@role('anon')
@router('post /bridge/status')
async bridgeStatus(req, res) {
let { webtoken,clientId } = req.params;
if (!webtoken || !clientId) {
throw new ZError(10, 'params mismatch')
}
const reqData = await req.jwtVerify({ extractToken: () => webtoken})
if (!reqData || !reqData.qrId) {
throw new ZError(14, 'token error')
}
const { qrId } = reqData
const data = new BridgeSvr().fetchData(qrId)
if (!data) {
throw new ZError(11, 'data not found')
}
if (data.clientId !== clientId) {
throw new ZError(13, 'login info mismatch')
}
if (data.status === 0 && data.expire < Date.now()) {
throw new ZError(12, 'login expired')
}
return data
}
/**
* 1. token
* 2. web端的token, qrId
* 3. token, cache, web端获取
*/
@router('post /bridge/upload')
async uploadKeyInfo(req, res) {
let { key, webtoken } = req.params;
let user = req.user
if (!webtoken || !key ) {
throw new ZError(10, 'params mismatch')
}
const reqData = await req.jwtVerify({ extractToken: () => webtoken})
if (!reqData || !reqData.qrId) {
throw new ZError(14, 'token error')
}
const { qrId } = reqData
const token = await res.jwtSign({ id: user.id })
let result = new BridgeSvr().updateData(qrId, key, token)
return {result}
}
}

View File

@ -27,12 +27,15 @@ class WalletController extends BaseController {
@router('post /wallet/info')
async uploadWalletInfo(req, res) {
let user = req.user
let { key } = req.params
let { key, address } = req.params
if (!key) {
throw new ZError(10, 'no data to save')
}
let record = await Wallet.insertOrUpdate({ account: user.id }, {})
record.key = key
if (address) {
record.address = address
}
await record.save()
return {}
}

View File

@ -17,6 +17,9 @@ class WalletClass extends BaseModule {
@prop()
public key: string
@prop()
public address: string
/**
*
*/

92
src/service/bridge.svr.ts Normal file
View File

@ -0,0 +1,92 @@
import { ZError } from 'common/ZError'
import { singleton } from 'decorators/singleton'
import { shortUuid, uuid } from 'utils/security.util'
export interface IQrData {
clientId: string
qrId: string
expire: number
remove: number
status: number
userId?: string
key?: string
token?: string
}
const DEFAULT_EXPIRE_TIME = 5 * 60 * 1000
const DEFAULT_DELETE_TIME = 10 * 60 * 1000
@singleton
export class BridgeSvr {
private clientMap: Map<string, IQrData> = new Map()
private qrMap: Map<string, IQrData> = new Map()
public registClientReq(clientId: string): string {
const qrId = shortUuid()
const now = Date.now()
if (this.clientMap.has(clientId)) {
const data = this.clientMap.get(clientId)
if (data.expire > now && data.status == 0) {
data.expire = now + DEFAULT_EXPIRE_TIME
data.remove = now + DEFAULT_DELETE_TIME
return data.qrId
} else {
if (this.qrMap.has(data.qrId)) {
this.qrMap.delete(data.qrId)
}
this.clientMap.delete(clientId)
}
}
let data = {
clientId,
qrId,
expire: now + DEFAULT_EXPIRE_TIME,
remove: now + DEFAULT_DELETE_TIME,
status: 0,
}
this.clientMap.set(clientId, data)
this.qrMap.set(qrId, data)
let self = this
setImmediate(function () {
self.cacheGc()
})
return qrId
}
public cacheGc() {
let now = Date.now()
for (let [key, data] of this.qrMap.entries()) {
if (data.remove <= now) {
this.qrMap.delete(key)
}
}
for (let [key, data] of this.clientMap.entries()) {
if (data.remove <= now) {
this.clientMap.delete(key)
}
}
}
public fetchData(qrId: string) {
const data = this.qrMap.get(qrId)
return data
}
public updateData(qrId: string, key: string, token: string) {
if (!this.qrMap.has(qrId)) {
throw new ZError(21, 'login data not found')
}
let data = this.qrMap.get(qrId)
const now = Date.now()
if (data.status === 0 && data.expire < now) {
throw new ZError(22, 'login data expired')
}
if (data.status != 0) {
throw new ZError(23, 'already logined')
}
data.status = 1
data.key = key
data.token = token
return true
}
}

View File

@ -1,4 +1,5 @@
import crypto from 'crypto'
import {compressUuid} from './string.util'
const ENCODER = 'base64'
const REG_KEY = /^[0-9a-fA-F]{63,64}$/
@ -22,3 +23,12 @@ export function aesDecrypt(encryptedText: string, password: string, iv: string)
let decrypted = decipher.update(encryptedText, ENCODER, 'utf8')
return decrypted + decipher.final('utf8')
}
export function uuid() {
return crypto.randomUUID()
}
export function shortUuid() {
let uid = uuid()
return compressUuid(uid)
}

View File

@ -15,7 +15,7 @@ export function generateKeyValStr(data: {}, ignoreNull = true, splitChar: string
return
}
if (i++ > 0) result += splitChar
result += `${key}${equalChar}${data[key]}`
result += `${key}${equalChar}${data[key]}`
}
return result
}
@ -47,15 +47,15 @@ export function keyValToObject(str: string, splitChar: string = '&', equalChar =
export function isTrue(obj) {
return (
obj === 'true' ||
obj === 'TRUE' ||
obj === 'True' ||
obj === 'on' ||
obj === 'ON' ||
obj === true ||
obj === 1 ||
obj === '1' ||
obj === 'YES' ||
obj === 'yes'
obj === 'TRUE' ||
obj === 'True' ||
obj === 'on' ||
obj === 'ON' ||
obj === true ||
obj === 1 ||
obj === '1' ||
obj === 'YES' ||
obj === 'yes'
)
}
@ -84,7 +84,7 @@ export function string10to62(number: string | number) {
qutient = (qutient - mod) / radix
arr.unshift(chars[mod])
} while (qutient)
return arr.join('')
return arr.join('')
}
/**
@ -104,3 +104,33 @@ export function string62to10(numberCode: string) {
}
return originNumber
}
const reNormalUUID = /^[0-9a-fA-F-]{36}$/;
const reLongUUID = /^[0-9a-fA-F]{32}$/;
const reShortUUID = /^[0-9a-zA-Z+/]{22,23}$/;
const n = /-/g;
export function compressUuid(e:string, t: boolean = false) {
if (reNormalUUID.test(e)) {
e = e.replace(n, '');
} else if (!reLongUUID.test(e)) {
return e;
}
var r = !0 === t ? 2 : 5;
return compressHex(e, r)
}
const CHARS_BASE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
export function compressHex(e: string, r: number) {
var i, n = e.length;
i = void 0 !== r ? r : n % 3;
for (var s = e.slice(0, i), o = []; i < n;) {
var u = parseInt(e[i], 16),
a = parseInt(e[i + 1], 16),
c = parseInt(e[i + 2], 16);
o.push(CHARS_BASE64[u << 2 | a >> 2]);
o.push(CHARS_BASE64[(3 & a) << 4 | c]);
i += 3;
}
return s + o.join('')
}