add task for okxlogin
This commit is contained in:
parent
2b5ae26454
commit
cc85523359
@ -22,6 +22,16 @@
|
|||||||
"autoclaim": false,
|
"autoclaim": false,
|
||||||
"pretasks": ["TwitterConnect"],
|
"pretasks": ["TwitterConnect"],
|
||||||
"params": {"time": 6, "failRate": 60}
|
"params": {"time": 6, "failRate": 60}
|
||||||
|
}, {
|
||||||
|
"id": "OkxLogin",
|
||||||
|
"title": "okx wallet login",
|
||||||
|
"type": 1,
|
||||||
|
"desc": "",
|
||||||
|
"category": "",
|
||||||
|
"score": 100,
|
||||||
|
"autoclaim": false,
|
||||||
|
"pretasks": [],
|
||||||
|
"params": {}
|
||||||
}, {
|
}, {
|
||||||
"id": "TwitterRetweet",
|
"id": "TwitterRetweet",
|
||||||
"title": "ReTwitt",
|
"title": "ReTwitt",
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
"@typegoose/typegoose": "^7.4.6",
|
"@typegoose/typegoose": "^7.4.6",
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"bson": "^4.0.4",
|
"bson": "^4.0.4",
|
||||||
|
"crypto-js": "^4.2.0",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"ethereumjs-util": "^7.1.5",
|
"ethereumjs-util": "^7.1.5",
|
||||||
|
@ -4,8 +4,10 @@ import { role, router } from 'decorators/router'
|
|||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
import { ActivityUser } from 'models/ActivityUser'
|
import { ActivityUser } from 'models/ActivityUser'
|
||||||
import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord'
|
import {DEFAULT_EXPIRED, NonceRecord} from 'models/NonceRecord'
|
||||||
|
import { LoginRecordQueue } from 'queue/loginrecord.queue'
|
||||||
import {SiweMessage} from 'siwe'
|
import {SiweMessage} from 'siwe'
|
||||||
import { checkParamsNeeded } from 'utils/net.util'
|
import { checkParamsNeeded } from 'utils/net.util'
|
||||||
|
import { aesDecrypt, base58ToHex } from 'utils/security.util'
|
||||||
|
|
||||||
const LOGIN_TIP = 'This signature is just to verify your identity'
|
const LOGIN_TIP = 'This signature is just to verify your identity'
|
||||||
|
|
||||||
@ -18,7 +20,7 @@ class SignController extends BaseController {
|
|||||||
await record.save()
|
await record.save()
|
||||||
return { nonce: record.id, tips: LOGIN_TIP }
|
return { nonce: record.id, tips: LOGIN_TIP }
|
||||||
}
|
}
|
||||||
|
//TODO:: 增加okx的奖励
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router('post /api/wallet/login')
|
@router('post /api/wallet/login')
|
||||||
async walletVerify(req, res) {
|
async walletVerify(req, res) {
|
||||||
@ -27,7 +29,20 @@ class SignController extends BaseController {
|
|||||||
if (!message.nonce) {
|
if (!message.nonce) {
|
||||||
throw new ZError(11, 'Invalid 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) {
|
if (!record || record.status !== 0) {
|
||||||
throw new ZError(12, 'nonce invalid')
|
throw new ZError(12, 'nonce invalid')
|
||||||
}
|
}
|
||||||
@ -41,7 +56,7 @@ class SignController extends BaseController {
|
|||||||
await record.save()
|
await record.save()
|
||||||
const msgSign = new SiweMessage(message);
|
const msgSign = new SiweMessage(message);
|
||||||
try {
|
try {
|
||||||
await msgSign.verify({ signature, nonce: record.id });
|
await msgSign.verify({ signature, nonce: message.nonce });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new ZError(15, 'signature invalid')
|
throw new ZError(15, 'signature invalid')
|
||||||
}
|
}
|
||||||
@ -54,6 +69,7 @@ class SignController extends BaseController {
|
|||||||
}
|
}
|
||||||
accountData.lastLogin = Date.now()
|
accountData.lastLogin = Date.now()
|
||||||
await accountData.save()
|
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 })
|
const token = await res.jwtSign({ id: accountData.id, address: accountData.address, activity: accountData.activity })
|
||||||
return { token }
|
return { token }
|
||||||
|
26
src/models/LoginRecord.ts
Normal file
26
src/models/LoginRecord.ts
Normal file
@ -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'] })
|
33
src/queue/loginrecord.queue.ts
Normal file
33
src/queue/loginrecord.queue.ts
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
37
src/tasks/OkxLogin.ts
Normal file
37
src/tasks/OkxLogin.ts
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
|
import CryptoJS from 'crypto-js'
|
||||||
|
|
||||||
export function hmac(input, key, out) {
|
export function hmac(input, key, out) {
|
||||||
return out
|
return out
|
||||||
@ -73,3 +74,66 @@ export function checkSign({
|
|||||||
let sign1 = hmacSha256(signStr, secretKey)
|
let sign1 = hmacSha256(signStr, secretKey)
|
||||||
return sign1 === sign
|
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);
|
||||||
|
}
|
@ -1551,6 +1551,11 @@ cross-spawn@^7.0.2:
|
|||||||
shebang-command "^2.0.0"
|
shebang-command "^2.0.0"
|
||||||
which "^2.0.1"
|
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:
|
d@1, d@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz"
|
resolved "https://registry.npmjs.org/d/-/d-1.0.1.tgz"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user