Files
hexerade/src/utils/oauth2.ts
T
2026-05-29 08:43:48 -04:00

137 lines
3.3 KiB
TypeScript

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'
}
});
if (res.status < 200 || res.status >= 300) {
console.error(`Non 200 code while joining user (${options.userId}) to discord (${res.status}):`);
const text = await res.text();
console.error(text);
let data = { code: 0 };
try {
data = JSON.parse(text);
} catch { }
throw data;
}
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();
}
}