完善登录相关逻辑
This commit is contained in:
parent
dda8e192a1
commit
fce1614233
@ -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 };
|
||||
}
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
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 { 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)
|
||||
}
|
||||
}
|
||||
|
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