Custom oauth lib
The package used is deprecated. And I want to be able to debug oauth errors better.
This commit is contained in:
@@ -16,6 +16,7 @@ export * from './message-utils';
|
||||
export * from './ms-to-human';
|
||||
export * from './name-sync';
|
||||
export * from './note-utils';
|
||||
export * from './oauth2';
|
||||
export * from './record-utils';
|
||||
export * from './refresh-commands';
|
||||
export * from './search-regex';
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
type ClientOptions = {
|
||||
clientId: string
|
||||
clientSecret: string
|
||||
clientToken: string
|
||||
redirectUri: string
|
||||
credentials: string
|
||||
};
|
||||
|
||||
type GenerateUrlParameters = {
|
||||
state: string
|
||||
scope: string[]
|
||||
type: 'code' | 'token'
|
||||
};
|
||||
|
||||
type TokenResponse = {
|
||||
access_token: string
|
||||
token_type: string
|
||||
expires_in: number
|
||||
refresh_token: string
|
||||
scope: string
|
||||
}
|
||||
|
||||
type DiscordUser = {
|
||||
id: string
|
||||
username: string
|
||||
// bunch of other stuff we don't use
|
||||
}
|
||||
|
||||
type AddMemberOptions = {
|
||||
accessToken: string
|
||||
botToken?: string
|
||||
guildId: string
|
||||
userId: string
|
||||
nickname?: string
|
||||
}
|
||||
|
||||
const OAUTH_BASE_URL = 'https://discord.com/oauth2';
|
||||
const OAUTH_API_BASE_URL = 'https://discord.com/api/oauth2';
|
||||
const API_BASE_URL = 'https://discord.com/api';
|
||||
|
||||
export class DiscordOAuth2 {
|
||||
constructor(private options: ClientOptions) { }
|
||||
|
||||
generateOauth2Url(options: GenerateUrlParameters) {
|
||||
const url = new URL(`${OAUTH_BASE_URL}/authorize`);
|
||||
|
||||
const params = new URLSearchParams({
|
||||
client_id: this.options.clientId,
|
||||
response_type: options.type,
|
||||
redirect_uri: this.options.redirectUri,
|
||||
scope: options.scope.join('+'),
|
||||
state: options.state
|
||||
});
|
||||
|
||||
url.search = params.toString();
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
async getAccessToken(code: string, scope: string[]): Promise<TokenResponse> {
|
||||
const res = await fetch(`${OAUTH_API_BASE_URL}/token`, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
client_id: this.options.clientId,
|
||||
client_secret: this.options.clientSecret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: this.options.redirectUri,
|
||||
scope: scope.join(' ')
|
||||
}).toString(),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
return data as TokenResponse;
|
||||
}
|
||||
|
||||
async getUser(accessToken: string): Promise<DiscordUser> {
|
||||
const res = await fetch(`${API_BASE_URL}/users/@me`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return await res.json() as DiscordUser;
|
||||
}
|
||||
|
||||
async addMember(options: AddMemberOptions) {
|
||||
const res = await fetch(`${API_BASE_URL}/guilds/${options.guildId}/members/${options.userId}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({
|
||||
nick: options.nickname,
|
||||
access_token: options.accessToken
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bot ${this.options.clientToken}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
async revokeToken(token: string) {
|
||||
const res = await fetch(`${OAUTH_API_BASE_URL}/token/revoke`, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
token
|
||||
}).toString(),
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: `Basic ${this.options.credentials}`,
|
||||
Accept: 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
return await res.json();
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -1,5 +1,4 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
import DiscordOAuth2 from 'discord-oauth2';
|
||||
import { config } from '../config';
|
||||
import { Database } from '../shared/Database';
|
||||
import crypto from 'crypto';
|
||||
@@ -11,7 +10,7 @@ import { Client } from 'discord.js';
|
||||
import bodyParser from 'body-parser';
|
||||
import { fixPings, removeIssueLinks } from '../utils/github-user-utils';
|
||||
import { logDebug } from '../utils/debug-utils';
|
||||
import { AltData, comprehensiveAltLookupFromE621 } from '../utils';
|
||||
import { AltData, comprehensiveAltLookupFromE621, DiscordOAuth2 } from '../utils';
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
@@ -25,12 +24,15 @@ const GITHUB_REPO_ID = 169334303;
|
||||
const DEV_BASE_URL = `http://localhost:${config.PORT}`;
|
||||
const PROD_BASE_URL = 'https://discord.e621.net';
|
||||
|
||||
const OAUTH_SCOPES = ['identify', 'guilds.join'];
|
||||
|
||||
const PAGE_TEMPLATE = fs.readFileSync(path.join(__dirname, 'templates', 'page.html'), { encoding: 'utf-8' });
|
||||
|
||||
const oauth = new DiscordOAuth2({
|
||||
clientId: config.DISCORD_CLIENT_ID!,
|
||||
clientSecret: config.DISCORD_CLIENT_SECRET!,
|
||||
redirectUri: `${config.DEV_MODE ? DEV_BASE_URL : PROD_BASE_URL}/callback`,
|
||||
clientToken: config.DISCORD_TOKEN!,
|
||||
credentials: Buffer.from(`${config.DISCORD_CLIENT_ID!}:${config.DISCORD_CLIENT_SECRET!}`).toString('base64')
|
||||
});
|
||||
|
||||
@@ -48,11 +50,7 @@ async function joinGuild(code: string, userId: string, username: string): Promis
|
||||
|
||||
const id = Number(userId);
|
||||
|
||||
tokenResponse = await oauth.tokenRequest({
|
||||
code,
|
||||
scope: 'identify guilds.join',
|
||||
grantType: 'authorization_code'
|
||||
});
|
||||
tokenResponse = await oauth.getAccessToken(code, OAUTH_SCOPES);
|
||||
|
||||
const user = await oauth.getUser(tokenResponse.access_token);
|
||||
|
||||
@@ -64,7 +62,6 @@ async function joinGuild(code: string, userId: string, username: string): Promis
|
||||
|
||||
await oauth.addMember({
|
||||
accessToken: tokenResponse.access_token,
|
||||
botToken: config.DISCORD_TOKEN!,
|
||||
guildId: config.DISCORD_GUILD_ID!,
|
||||
userId: user.id,
|
||||
nickname: username
|
||||
@@ -103,6 +100,12 @@ async function handleInitial(req: Request, res: Response): Promise<any> {
|
||||
|
||||
const oauthState = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
const oauthUrl = await oauth.generateOauth2Url({
|
||||
state: oauthState,
|
||||
scope: OAUTH_SCOPES,
|
||||
type: 'code'
|
||||
});
|
||||
|
||||
req.session.username = username as string;
|
||||
req.session.userId = user_id as string;
|
||||
req.session.oauthState = oauthState;
|
||||
@@ -114,10 +117,7 @@ async function handleInitial(req: Request, res: Response): Promise<any> {
|
||||
return sendInteralServerError(res);
|
||||
}
|
||||
|
||||
res.redirect(oauth.generateAuthUrl({
|
||||
state: oauthState,
|
||||
scope: ['identify', 'guilds.join']
|
||||
}));
|
||||
res.redirect(oauthUrl);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ async function handleCallback(req: Request, res: Response): Promise<any> {
|
||||
const response = await joinGuild(code, userId, username);
|
||||
if (response == JoinResponse.Error) {
|
||||
console.error(`Error joining user: ${username} (${userId})`);
|
||||
return sendInteralServerError(res, 'Unable to join user to guild.');
|
||||
return sendInteralServerError(res, 'Unable to join user to guild. Retry later. If issue persists, please contact staff.');
|
||||
} else if (response == JoinResponse.Banned) {
|
||||
return sendForbidden(res, 'User is banned.');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user