diff --git a/src/controllers/discord.controller.ts b/src/controllers/discord.controller.ts index 07edab8..88873c3 100644 --- a/src/controllers/discord.controller.ts +++ b/src/controllers/discord.controller.ts @@ -1,36 +1,38 @@ -import BaseController, { ROLE_ANON } from "common/base.controller"; -import { ZError } from "common/ZError"; -import { role, router } from "decorators/router"; -import logger from "logger/logger"; -import { - DiscordSvr, - exchangeDiscrodCodeForToken, - userInfo, -} from "services/discord.svr"; +import BaseController, { ROLE_ANON } from 'common/base.controller' +import { ZError } from 'common/ZError' +import { role, router } from 'decorators/router' +import logger from 'logger/logger' +import { AuthRecord } from 'modules/AuthRecord' +import { DiscordSvr, exchangeDiscrodCodeForToken, userInfo } from 'services/discord.svr' class DiscordController extends BaseController { @role(ROLE_ANON) - @router("get /discord/redirect_uri") + @router('get /discord/redirect_uri') async discordCallback(req, res) { - let { code } = req.params; - logger.info("discord redirect: ", req.params); - let access_token = ""; - if (code) { - access_token = await exchangeDiscrodCodeForToken(code); - let uinfo = await userInfo(access_token); - console.log(uinfo); - return res.view("/templates/discord_redirect.ejs"); + let { code, state } = req.params + if (code && state) { + const stateArr = state.split('|') + const address = stateArr[0] + const record = await AuthRecord.insertOrUpdate( + { address, platform: 7 }, + { address, platform: 7, $inc: { version: 1 } }, + ) + let tokenResponse = await exchangeDiscrodCodeForToken(code) + record.accessToken = tokenResponse.access_token + record.refreshToken = tokenResponse.refresh_token + record.scope = tokenResponse.scope + record.tokenType = tokenResponse.token_type + record.expiresIn = tokenResponse.expires_in + Date.now() + await record.save() + let uinfo = await userInfo(tokenResponse.access_token) + record.nickname = uinfo.username + record.username = uinfo.username + record.discriminator = uinfo.discriminator + record.openId = uinfo.id + await record.save() + return res.view('/templates/discord_redirect.ejs') } else { - return res.view("/templates/discord_redirect.ejs"); + return res.view('/templates/discord_redirect.ejs') } } - - @role(ROLE_ANON) - @router("get /discord/check_user_role") - async checkUserRole(req, res) { - // let { uid } = req.params; - let uid = "1034482894690861116"; - let role = await new DiscordSvr().checkUserRole(uid); - return { role }; - } } diff --git a/src/controllers/main.controller.ts b/src/controllers/main.controller.ts index c64b479..abc942e 100644 --- a/src/controllers/main.controller.ts +++ b/src/controllers/main.controller.ts @@ -1,15 +1,58 @@ -import BaseController, { ROLE_ANON } from "common/base.controller"; -import { ZError } from "common/ZError"; -import { role, router } from "decorators/router"; -import logger from "logger/logger"; +import BaseController, { ROLE_ANON } from 'common/base.controller' +import { ZError } from 'common/ZError' +import { role, router } from 'decorators/router' +import logger from 'logger/logger' +import { AuthRecord } from 'modules/AuthRecord' +import { DiscordSvr } from 'services/discord.svr' class MainController extends BaseController { /** * Refresh token */ @role(ROLE_ANON) - @router("post /open/api/v3/merchant/getToken") + @router('get /user/status/:address') async getToken(req, res) { - return {}; + let { address } = req.params + let records = await AuthRecord.find({ address }) + let result: any = { + discord: {}, + twitter: {}, + } + for (const record of records) { + switch (record.platform) { + case 4: + result.twitter = { + id: record.openId, + username: record.username, + } + break + case 7: + result.discord = { + id: record.openId, + username: record.username, + discriminator: record.discriminator, + verified: record.condition, + } + } + } + return result + } + + @role(ROLE_ANON) + @router('get /user/check_verify/:address') + async checkUserRole(req, res) { + let { address } = req.params + if (!address) { + throw new ZError(10, 'address is required') + } + let discordRecord = await AuthRecord.findByAddress(address, 7) + if (!discordRecord) { + throw new ZError(11, 'discord not found') + } + if (discordRecord.condition) { + return { verified: true } + } + let role = await new DiscordSvr().checkUserRole(discordRecord.openId) + return { verified: role } } } diff --git a/src/controllers/twitter.controller.ts b/src/controllers/twitter.controller.ts index 8dd75d7..084b179 100644 --- a/src/controllers/twitter.controller.ts +++ b/src/controllers/twitter.controller.ts @@ -2,12 +2,36 @@ import BaseController, { ROLE_ANON } from 'common/base.controller' import { ZError } from 'common/ZError' import { role, router } from 'decorators/router' import logger from 'logger/logger' +import { AuthRecord } from 'modules/AuthRecord' +import { exchangeTwitterCodeForToken, getTwitterUserInfo } from 'services/twitter.svr' class TwitterController extends BaseController { @role(ROLE_ANON) @router('get /twitter/redirect_uri') async discordCallback(req, res) { logger.info('twitter redirect: ', req.params) + const { code, state } = req.params + if (code && state) { + const stateArr = state.split('|') + const address = stateArr[0] + const record = await AuthRecord.insertOrUpdate( + { address, platform: 4 }, + { address, platform: 4, $inc: { version: 1 } }, + ) + const vcode = stateArr[1] || stateArr[0] + const tokenResponse = await exchangeTwitterCodeForToken(code, vcode) + record.accessToken = tokenResponse.access_token + record.refreshToken = tokenResponse.refresh_token + record.scope = tokenResponse.scope + record.tokenType = tokenResponse.token_type + record.expiresIn = tokenResponse.expires_in + Date.now() + await record.save() + const uinfo = await getTwitterUserInfo(tokenResponse.access_token) + record.nickname = uinfo.data.name + record.username = uinfo.data.username + record.openId = uinfo.data.id + await record.save() + } return res.view('/templates/twitter_redirect.ejs') } } diff --git a/src/modules/AuthRecord.ts b/src/modules/AuthRecord.ts new file mode 100644 index 0000000..9a2ec42 --- /dev/null +++ b/src/modules/AuthRecord.ts @@ -0,0 +1,73 @@ +import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose' +import { dbconn } from 'decorators/dbconn' +import { BaseModule } from './Base' + +export enum PlatEnum { + GOOGLE = 0, + APPLE = 1, + TIKTOK = 2, + FACEBOOK = 3, + TWITTER = 4, + TELEGRAM = 5, + EMAIL = 6, + DISCORD = 7, +} + +@dbconn() +@index({ address: 1, platform: 1 }, { unique: true }) +@modelOptions({ + schemaOptions: { collection: 'auth_record', timestamps: true }, + options: { allowMixed: Severity.ALLOW }, +}) +export class AuthRecordClass extends BaseModule { + @prop({ required: true }) + public address: string + + @prop({ enum: PlatEnum, default: PlatEnum.DISCORD }) + public platform: PlatEnum + + @prop() + public nickname?: string + + @prop() + public username?: string + // 对应discord上的discriminator + @prop() + public discriminator?: string + + @prop() + public openId?: string + + @prop() + public email?: string + + @prop({ default: 0 }) + public condition: number + + @prop({ default: 0 }) + public version: number + + @prop() + public tokenType: string + @prop() + public accessToken: string + @prop() + public refreshToken: string + @prop() + public expiresIn: number + @prop() + public scope: string + + @prop({ type: mongoose.Schema.Types.Mixed }) + public outData: any + + public static async findByAddress( + this: ReturnModelType, + address: string, + platform: PlatEnum, + ) { + return this.findOne({ address, platform }).exec() + } +} + +export const AuthRecord = getModelForClass(AuthRecordClass, { existingConnection: AuthRecordClass.db }) diff --git a/src/modules/PayRecord.ts b/src/modules/PayRecord.ts deleted file mode 100644 index ee6cd4b..0000000 --- a/src/modules/PayRecord.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { getModelForClass, index, modelOptions, mongoose, prop, ReturnModelType, Severity } from '@typegoose/typegoose' -import { dbconn } from 'decorators/dbconn' -import { BaseModule } from './Base' - -export enum PayType { - BUY = 1, - SELL = 2, -} - -export enum PayStatus { - PENDING = 0, - TRANSFERING = 1, - TRANSFERED = 2, //只有国库模式才会有该状态 - SUCCESS = 9, - TRANSFER_FAIL = 98, // 转账错误 - FAIL = 99, -} - -@dbconn() -@index({ merchantOrderNo: 1 }, { unique: true, partialFilterExpression: { outOrderId: { $exists: true } } }) -@modelOptions({ - schemaOptions: { collection: 'pay_record', timestamps: true }, - options: { allowMixed: Severity.ALLOW }, -}) -export class PayRecordClass extends BaseModule { - @prop({ required: true, default: PayType.BUY }) - public type: PayType - - @prop() - public address: string - - @prop() - public network?: string - - @prop() - public crypto?: string - - // 法币 - @prop() - public fiat?: string - // 法币数量 - @prop() - public fiatAmount?: string - - @prop() - public processFee?: string - - @prop() - public networkFee?: string - - // 加密货币数量 - @prop() - public cryptoAmount?: string - - // 加密货币价格 - @prop() - public cryptoPrice?: string - // 该笔交易渠道会给我们多少usdt - @prop() - public usdtAmount?: string - // 国家 - @prop() - public country?: string - - @prop({ required: true, default: PayStatus.PENDING }) - public status: PayStatus - // 渠道返回的原始资料 - @prop({ type: mongoose.Schema.Types.Mixed }) - public outData: any - - // 商户订单id - @prop() - public merchantOrderNo: string - - @prop() - public email: string - - @prop() - public callbackUrl: string - - @prop() - public merchantName: string - - // 交易的txHash - @prop() - public txHash?: string - - @prop({ default: 0 }) - public version: number - - public static async findByRecordId(this: ReturnModelType, merchantOrderNo: string) { - return this.findOne({ merchantOrderNo }).exec() - } -} - -export const PayRecord = getModelForClass(PayRecordClass, { existingConnection: PayRecordClass.db }) diff --git a/src/services/discord.svr.ts b/src/services/discord.svr.ts index d985f4d..6020f78 100644 --- a/src/services/discord.svr.ts +++ b/src/services/discord.svr.ts @@ -1,66 +1,65 @@ -import { ZError } from "common/ZError"; -import { singleton } from "decorators/singleton"; -const { Client, Events, GatewayIntentBits } = require("discord.js"); +import { ZError } from 'common/ZError' +import { singleton } from 'decorators/singleton' +const { Client, Events, GatewayIntentBits } = require('discord.js') export async function exchangeDiscrodCodeForToken(code: string) { - const clientId = process.env.DISCORD_CLIENT_ID; - const clientSecret = process.env.DISCORD_CLIENT_SECRET; - const redirectUri = "http://localhost:3010/discord/redirect_uri"; + const clientId = process.env.DISCORD_CLIENT_ID + const clientSecret = process.env.DISCORD_CLIENT_SECRET + const redirectUri = 'https://oauth-svr.cebggame.com/discord/redirect_uri' - const response = await fetch("https://discord.com/api/oauth2/token", { - method: "POST", + const response = await fetch('https://discord.com/api/oauth2/token', { + method: 'POST', headers: { - "Content-Type": "application/x-www-form-urlencoded", + 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ - grant_type: "authorization_code", + grant_type: 'authorization_code', client_id: clientId, client_secret: clientSecret, redirect_uri: redirectUri, code, - scope: "identify email", + scope: 'identify email', }), - }); + }) - const data = await response.json(); - console.log(data); - return data.access_token; + const data = await response.json() + return data } export async function userInfo(token: string) { - const response = await fetch("https://discord.com/api/users/@me", { + const response = await fetch('https://discord.com/api/users/@me', { headers: { authorization: `Bearer ${token}`, }, - }); + }) - const data = await response.json(); - console.log(data); - return data; + const data = await response.json() + console.log(data) + return data } @singleton export class DiscordSvr { - private client: any; - private guild: any; + private client: any + private guild: any public async init() { - console.log("DiscordSvr init"); - this.client = new Client({ intents: [GatewayIntentBits.Guilds] }); - this.client.once(Events.ClientReady, async (c) => { - console.log(`Ready! Logged in as ${c.user.tag}`); - this.guild = await this.client.guilds.fetch(process.env.DISCROD_GUILD_ID); - }); - this.client.login(process.env.DISCORD_BOT_TOKEN); + console.log('DiscordSvr init') + this.client = new Client({ intents: [GatewayIntentBits.Guilds] }) + this.client.once(Events.ClientReady, async c => { + console.log(`Ready! Logged in as ${c.user.tag}`) + this.guild = await this.client.guilds.fetch(process.env.DISCROD_GUILD_ID) + }) + this.client.login(process.env.DISCORD_BOT_TOKEN) } public async checkUserRole(uid: string) { if (!this.guild) { - throw new ZError(10, "DiscordSvr not init"); + throw new ZError(10, 'DiscordSvr not init') } - const member = await this.guild.members.fetch(uid); + const member = await this.guild.members.fetch(uid) if (!member) { - return false; + return false } - return member.roles.cache.has(process.env.DISCORD_ROLE_ID); + return member.roles.cache.has(process.env.DISCORD_ROLE_ID) } } diff --git a/src/services/twitter.svr.ts b/src/services/twitter.svr.ts new file mode 100644 index 0000000..2796ec7 --- /dev/null +++ b/src/services/twitter.svr.ts @@ -0,0 +1,36 @@ +const consumerKey = process.env.TWITTER_CLIENT_ID +const consumerSecret = process.env.TWITTER_CLIENT_SECRET + +export async function exchangeTwitterCodeForToken(code: string, vcode: string) { + const url = 'https://api.twitter.com/2/oauth2/token' + const credentials = Buffer.from(`${consumerKey}:${consumerSecret}`).toString('base64') + const response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${credentials}`, + }, + body: new URLSearchParams({ + code, + grant_type: 'authorization_code', + // client_id: process.env.TWITTER_CLIENT_ID, + redirect_uri: 'https://oauth-svr.cebggame.com/twitter/redirect_uri', + code_verifier: vcode, + }), + }) + const data = await response.json() + return data +} + +export async function getTwitterUserInfo(accessToken: string) { + const url = 'https://api.twitter.com/2/users/me' + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${accessToken}`, + }, + }) + const data = await response.json() + return data +}