diff --git a/initdatas/activity_info.json b/initdatas/activity_info.json index e078d39..64678b0 100644 --- a/initdatas/activity_info.json +++ b/initdatas/activity_info.json @@ -22,6 +22,16 @@ "autoclaim": false, "pretasks": ["TwitterConnect"], "params": {"time": 6, "failRate": 60} + }, { + "id": "OkxLogin", + "title": "okx wallet login", + "type": 1, + "desc": "", + "category": "", + "score": 100, + "autoclaim": false, + "pretasks": [], + "params": {} }, { "id": "TwitterRetweet", "title": "ReTwitt", diff --git a/package.json b/package.json index db27ec7..85276ec 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@typegoose/typegoose": "^7.4.6", "axios": "^0.21.1", "bson": "^4.0.4", + "crypto-js": "^4.2.0", "deepmerge": "^4.2.2", "dotenv": "^16.0.3", "ethereumjs-util": "^7.1.5", diff --git a/src/controllers/sign.controller.ts b/src/controllers/sign.controller.ts index 7386562..ff09961 100644 --- a/src/controllers/sign.controller.ts +++ b/src/controllers/sign.controller.ts @@ -4,8 +4,10 @@ import { role, router } from 'decorators/router' import logger from 'logger/logger' import { ActivityUser } from 'models/ActivityUser' import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord' +import { LoginRecordQueue } from 'queue/loginrecord.queue' import {SiweMessage} from 'siwe' import { checkParamsNeeded } from 'utils/net.util' +import { aesDecrypt, base58ToHex } from 'utils/security.util' const LOGIN_TIP = 'This signature is just to verify your identity' @@ -18,7 +20,7 @@ class SignController extends BaseController { await record.save() return { nonce: record.id, tips: LOGIN_TIP } } - + //TODO:: 增加okx的奖励 @role(ROLE_ANON) @router('post /api/wallet/login') async walletVerify(req, res) { @@ -27,7 +29,20 @@ class SignController extends BaseController { if (!message.nonce) { throw new ZError(11, 'Invalid nonce'); } - let record = await NonceRecord.findById(message.nonce) + let nonce = message.nonce + let source = 'unknow' + if (nonce.length > 24) { + nonce = base58ToHex(nonce); + let nonceStr = aesDecrypt(nonce, activity); + if (nonceStr.indexOf('|') >=0 ) { + const split = nonceStr.split('|') + nonce = split[0]; + source = split[1]; + } else { + nonce = nonceStr; + } + } + let record = await NonceRecord.findById(nonce) if (!record || record.status !== 0) { throw new ZError(12, 'nonce invalid') } @@ -41,7 +56,7 @@ class SignController extends BaseController { await record.save() const msgSign = new SiweMessage(message); try { - await msgSign.verify({ signature, nonce: record.id }); + await msgSign.verify({ signature, nonce: message.nonce }); } catch (e) { throw new ZError(15, 'signature invalid') } @@ -54,6 +69,7 @@ class SignController extends BaseController { } accountData.lastLogin = Date.now() await accountData.save() + new LoginRecordQueue().addLog(req, accountData.id, activity, source) const token = await res.jwtSign({ id: accountData.id, address: accountData.address, activity: accountData.activity }) return { token } diff --git a/src/models/LoginRecord.ts b/src/models/LoginRecord.ts new file mode 100644 index 0000000..30af115 --- /dev/null +++ b/src/models/LoginRecord.ts @@ -0,0 +1,26 @@ +import { dbconn } from 'decorators/dbconn' +import { getModelForClass, index, modelOptions, mongoose, prop } from '@typegoose/typegoose' +import { Severity } from '@typegoose/typegoose/lib/internal/constants' +import { BaseModule } from './Base' + +/** + * 用户登录记录 + */ +@dbconn() +@index({ user: 1, activity: 1, wallet: 1 }, { unique: false }) +@modelOptions({ schemaOptions: { collection: 'user_login_record', timestamps: true }, options: { allowMixed: Severity.ALLOW } }) +class LoginRecordClass extends BaseModule { + @prop() + public user: string + @prop() + public wallet: string + @prop() + public activity: string + @prop() + public referer: string + @prop() + public user_agent: string + @prop() + public ip: string +} +export const LoginRecord = getModelForClass(LoginRecordClass, { existingConnection: LoginRecordClass['db'] }) diff --git a/src/queue/loginrecord.queue.ts b/src/queue/loginrecord.queue.ts new file mode 100644 index 0000000..b349cdf --- /dev/null +++ b/src/queue/loginrecord.queue.ts @@ -0,0 +1,33 @@ +import { AsyncQueue, createAsyncQueue } from 'common/AsyncQueue' +import { singleton } from 'decorators/singleton' +import logger from 'logger/logger' +import { LoginRecord } from 'models/LoginRecord' + +@singleton +export class LoginRecordQueue { + private queue: AsyncQueue + + constructor() { + this.queue = createAsyncQueue() + } + + public addLog(req, user, activity, wallet) { + this.queue.push(async () => { + const ip = req.headers['x-forwarded-for'] || req.ip + try { + const history = new LoginRecord({ + user, + activity, + wallet, + referer: req.headers['referer'], + user_agent: req.headers['user-agent'], + ip, + }) + await history.save() + } catch (err) { + logger.error('error add user login record: ') + logger.error(err) + } + }) + } +} diff --git a/src/tasks/OkxLogin.ts b/src/tasks/OkxLogin.ts new file mode 100644 index 0000000..0d2c85e --- /dev/null +++ b/src/tasks/OkxLogin.ts @@ -0,0 +1,37 @@ +import { ITask } from "./base/ITask"; +import { ZError } from "common/ZError"; +import { TaskStatusEnum } from "models/ActivityUser"; +import { TaskCfg } from "models/ActivityInfo"; +import { UserLog } from "models/UserLog"; +import { LoginRecord } from "models/LoginRecord"; + +export default class OkxLogin extends ITask { + static desc = 'okx wallet login' + static show: boolean = true + + async execute(data: any) { + const { task } = data + const { activity } = this.params.user + let cfg = this.params.activity.tasks.find((t: TaskCfg) => t.id === this.constructor.name) + let wallet = 'okx'; + let record = LoginRecord.findOne({ user: this.params.user.id, activity, wallet}) + if (!record ) { + throw new ZError(11, 'task not finished') + } + task.status = TaskStatusEnum.SUCCESS + task.timeFinish = Date.now() + try { + await this.params.user.save() + } catch(err) { + throw new ZError(100, 'discord already binded') + } + if (cfg.autoclaim) { + try { + await this.claimReward(task); + } catch(err) { + console.log(err) + } + } + return true + } +} \ No newline at end of file diff --git a/src/utils/security.util.ts b/src/utils/security.util.ts index 35b1260..eb5ab9e 100644 --- a/src/utils/security.util.ts +++ b/src/utils/security.util.ts @@ -1,4 +1,5 @@ import crypto from 'crypto' +import CryptoJS from 'crypto-js' export function hmac(input, key, out) { return out @@ -73,3 +74,66 @@ export function checkSign({ let sign1 = hmacSha256(signStr, secretKey) return sign1 === sign } + +// export function aesDecrypt(encrypted: string, key: string) { +// let bytes = aes.decrypt(encrypted, key) +// var originalText = bytes.toString(enc.Utf8); +// return originalText +// } + +export const aesEncrypt = (plaintText, key) => { + key = CryptoJS.SHA1(key).toString().substring(0,16) + key = CryptoJS.enc.Base64.parse(key) + let encryptedData = CryptoJS.AES.encrypt(plaintText, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7 + }); + + return encryptedData.toString(CryptoJS.format.Hex); +} + +export const aesDecrypt = (encryptedDataHexStr, key) => { + key = CryptoJS.SHA1(key).toString().substring(0,16) + key = CryptoJS.enc.Base64.parse(key) + let encryptedHex = CryptoJS.enc.Hex.parse(encryptedDataHexStr); + let encryptedBase64 = CryptoJS.enc.Base64.stringify(encryptedHex); + + var decryptedData = CryptoJS.AES.decrypt(encryptedBase64, key, { + mode: CryptoJS.mode.ECB, + padding: CryptoJS.pad.Pkcs7, + }); + + return decryptedData.toString(CryptoJS.enc.Utf8); +} + + +const base58Alphabet = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + +export const hexToBase58 = (hexString: string) => { + const bytes = hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)); + let base58String = ''; + + let num = BigInt('0x' + hexString); + while (num > BigInt(0)) { + const remainder = num % BigInt(58); + num = num / BigInt(58); + base58String = base58Alphabet[Number(remainder)] + base58String; + } + + return base58String; +} + +export const base58ToHex = (base58String: string) => { + const base58Length = base58String.length; + let num = BigInt(0); + let leadingZeros = 0; + for (let i = 0; i < base58Length; i++) { + const charIndex = base58Alphabet.indexOf(base58String[i]); + if (charIndex === -1) { + throw new Error('Invalid Base58 string'); + } + + num = num * BigInt(58) + BigInt(charIndex); + } + return num.toString(16); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4ec6d36..701095b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1551,6 +1551,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +crypto-js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631" + integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q== + d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz"