完善登录相关逻辑
This commit is contained in:
parent
dda8e192a1
commit
fce1614233
@ -1,36 +1,38 @@
|
|||||||
import BaseController, { ROLE_ANON } from "common/base.controller";
|
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||||
import { ZError } from "common/ZError";
|
import { ZError } from 'common/ZError'
|
||||||
import { role, router } from "decorators/router";
|
import { role, router } from 'decorators/router'
|
||||||
import logger from "logger/logger";
|
import logger from 'logger/logger'
|
||||||
import {
|
import { AuthRecord } from 'modules/AuthRecord'
|
||||||
DiscordSvr,
|
import { DiscordSvr, exchangeDiscrodCodeForToken, userInfo } from 'services/discord.svr'
|
||||||
exchangeDiscrodCodeForToken,
|
|
||||||
userInfo,
|
|
||||||
} from "services/discord.svr";
|
|
||||||
|
|
||||||
class DiscordController extends BaseController {
|
class DiscordController extends BaseController {
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router("get /discord/redirect_uri")
|
@router('get /discord/redirect_uri')
|
||||||
async discordCallback(req, res) {
|
async discordCallback(req, res) {
|
||||||
let { code } = req.params;
|
let { code, state } = req.params
|
||||||
logger.info("discord redirect: ", req.params);
|
if (code && state) {
|
||||||
let access_token = "";
|
const stateArr = state.split('|')
|
||||||
if (code) {
|
const address = stateArr[0]
|
||||||
access_token = await exchangeDiscrodCodeForToken(code);
|
const record = await AuthRecord.insertOrUpdate(
|
||||||
let uinfo = await userInfo(access_token);
|
{ address, platform: 7 },
|
||||||
console.log(uinfo);
|
{ address, platform: 7, $inc: { version: 1 } },
|
||||||
return res.view("/templates/discord_redirect.ejs");
|
)
|
||||||
|
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 {
|
} 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 };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,58 @@
|
|||||||
import BaseController, { ROLE_ANON } from "common/base.controller";
|
import BaseController, { ROLE_ANON } from 'common/base.controller'
|
||||||
import { ZError } from "common/ZError";
|
import { ZError } from 'common/ZError'
|
||||||
import { role, router } from "decorators/router";
|
import { role, router } from 'decorators/router'
|
||||||
import logger from "logger/logger";
|
import logger from 'logger/logger'
|
||||||
|
import { AuthRecord } from 'modules/AuthRecord'
|
||||||
|
import { DiscordSvr } from 'services/discord.svr'
|
||||||
|
|
||||||
class MainController extends BaseController {
|
class MainController extends BaseController {
|
||||||
/**
|
/**
|
||||||
* Refresh token
|
* Refresh token
|
||||||
*/
|
*/
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router("post /open/api/v3/merchant/getToken")
|
@router('get /user/status/:address')
|
||||||
async getToken(req, res) {
|
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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,36 @@ import BaseController, { ROLE_ANON } from 'common/base.controller'
|
|||||||
import { ZError } from 'common/ZError'
|
import { ZError } from 'common/ZError'
|
||||||
import { role, router } from 'decorators/router'
|
import { role, router } from 'decorators/router'
|
||||||
import logger from 'logger/logger'
|
import logger from 'logger/logger'
|
||||||
|
import { AuthRecord } from 'modules/AuthRecord'
|
||||||
|
import { exchangeTwitterCodeForToken, getTwitterUserInfo } from 'services/twitter.svr'
|
||||||
|
|
||||||
class TwitterController extends BaseController {
|
class TwitterController extends BaseController {
|
||||||
@role(ROLE_ANON)
|
@role(ROLE_ANON)
|
||||||
@router('get /twitter/redirect_uri')
|
@router('get /twitter/redirect_uri')
|
||||||
async discordCallback(req, res) {
|
async discordCallback(req, res) {
|
||||||
logger.info('twitter redirect: ', req.params)
|
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')
|
return res.view('/templates/twitter_redirect.ejs')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
73
src/modules/AuthRecord.ts
Normal file
73
src/modules/AuthRecord.ts
Normal file
@ -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<typeof AuthRecordClass>,
|
||||||
|
address: string,
|
||||||
|
platform: PlatEnum,
|
||||||
|
) {
|
||||||
|
return this.findOne({ address, platform }).exec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AuthRecord = getModelForClass(AuthRecordClass, { existingConnection: AuthRecordClass.db })
|
@ -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<typeof PayRecordClass>, merchantOrderNo: string) {
|
|
||||||
return this.findOne({ merchantOrderNo }).exec()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const PayRecord = getModelForClass(PayRecordClass, { existingConnection: PayRecordClass.db })
|
|
@ -1,66 +1,65 @@
|
|||||||
import { ZError } from "common/ZError";
|
import { ZError } from 'common/ZError'
|
||||||
import { singleton } from "decorators/singleton";
|
import { singleton } from 'decorators/singleton'
|
||||||
const { Client, Events, GatewayIntentBits } = require("discord.js");
|
const { Client, Events, GatewayIntentBits } = require('discord.js')
|
||||||
|
|
||||||
export async function exchangeDiscrodCodeForToken(code: string) {
|
export async function exchangeDiscrodCodeForToken(code: string) {
|
||||||
const clientId = process.env.DISCORD_CLIENT_ID;
|
const clientId = process.env.DISCORD_CLIENT_ID
|
||||||
const clientSecret = process.env.DISCORD_CLIENT_SECRET;
|
const clientSecret = process.env.DISCORD_CLIENT_SECRET
|
||||||
const redirectUri = "http://localhost:3010/discord/redirect_uri";
|
const redirectUri = 'https://oauth-svr.cebggame.com/discord/redirect_uri'
|
||||||
|
|
||||||
const response = await fetch("https://discord.com/api/oauth2/token", {
|
const response = await fetch('https://discord.com/api/oauth2/token', {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded",
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
grant_type: "authorization_code",
|
grant_type: 'authorization_code',
|
||||||
client_id: clientId,
|
client_id: clientId,
|
||||||
client_secret: clientSecret,
|
client_secret: clientSecret,
|
||||||
redirect_uri: redirectUri,
|
redirect_uri: redirectUri,
|
||||||
code,
|
code,
|
||||||
scope: "identify email",
|
scope: 'identify email',
|
||||||
}),
|
}),
|
||||||
});
|
})
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json()
|
||||||
console.log(data);
|
return data
|
||||||
return data.access_token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function userInfo(token: string) {
|
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: {
|
headers: {
|
||||||
authorization: `Bearer ${token}`,
|
authorization: `Bearer ${token}`,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json()
|
||||||
console.log(data);
|
console.log(data)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
export class DiscordSvr {
|
export class DiscordSvr {
|
||||||
private client: any;
|
private client: any
|
||||||
private guild: any;
|
private guild: any
|
||||||
public async init() {
|
public async init() {
|
||||||
console.log("DiscordSvr init");
|
console.log('DiscordSvr init')
|
||||||
this.client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
this.client = new Client({ intents: [GatewayIntentBits.Guilds] })
|
||||||
this.client.once(Events.ClientReady, async (c) => {
|
this.client.once(Events.ClientReady, async c => {
|
||||||
console.log(`Ready! Logged in as ${c.user.tag}`);
|
console.log(`Ready! Logged in as ${c.user.tag}`)
|
||||||
this.guild = await this.client.guilds.fetch(process.env.DISCROD_GUILD_ID);
|
this.guild = await this.client.guilds.fetch(process.env.DISCROD_GUILD_ID)
|
||||||
});
|
})
|
||||||
this.client.login(process.env.DISCORD_BOT_TOKEN);
|
this.client.login(process.env.DISCORD_BOT_TOKEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async checkUserRole(uid: string) {
|
public async checkUserRole(uid: string) {
|
||||||
if (!this.guild) {
|
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) {
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
36
src/services/twitter.svr.ts
Normal file
36
src/services/twitter.svr.ts
Normal file
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user