Custom oauth lib

The package used is deprecated. And I want to be able to debug oauth errors better.
This commit is contained in:
Tarrgon
2025-12-13 17:58:10 -05:00
parent c2e4d1bd3c
commit 9d18b97144
5 changed files with 138 additions and 20 deletions
+1
View File
@@ -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';
+124
View File
@@ -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
View File
@@ -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.');
}