forked from NotNet/gluestick
170 lines
4.1 KiB
TypeScript
170 lines
4.1 KiB
TypeScript
import { AuthProvider, AuthProviderState } from "./AuthProvider";
|
|
import prisma from "@/prisma";
|
|
|
|
export type DiscordAccessTokenResponse = {
|
|
access_token: string;
|
|
token_type: string;
|
|
expires_in: number;
|
|
refresh_token: string;
|
|
scope: string;
|
|
};
|
|
|
|
type DiscordUserResponse = {
|
|
id: string;
|
|
avatar: string | null;
|
|
username: string;
|
|
email: string | null;
|
|
discriminator: string;
|
|
};
|
|
|
|
type DiscordGuildResponse = {
|
|
id: string;
|
|
};
|
|
|
|
export class DiscordAuthProvider extends AuthProvider {
|
|
private async getMe(): Promise<DiscordUserResponse> {
|
|
const req = await fetch("https://discord.com/api/users/@me", {
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`
|
|
}
|
|
});
|
|
const res: DiscordUserResponse = await req.json();
|
|
return res;
|
|
}
|
|
|
|
async isPermitted(): Promise<boolean> {
|
|
const req = await fetch("https://discord.com/api/users/@me/guilds", {
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`
|
|
}
|
|
});
|
|
|
|
const res: DiscordGuildResponse[] = await req.json();
|
|
const guilds = res.map((guild) => guild.id);
|
|
const allowedGuilds = process.env.DISCORD_ALLOWED_GUILDS?.split(",") ?? [];
|
|
|
|
let allowed = false;
|
|
for (const guild of allowedGuilds) {
|
|
if (guilds.includes(guild)) allowed = true;
|
|
}
|
|
return allowed;
|
|
}
|
|
|
|
async getDisplayName(): Promise<string> {
|
|
const me = await this.getMe();
|
|
return me.username;
|
|
}
|
|
|
|
async getUsername(): Promise<string> {
|
|
const me = await this.getMe();
|
|
return me.username + "#" + me.discriminator;
|
|
}
|
|
|
|
async getId(): Promise<string> {
|
|
const me = await this.getMe();
|
|
return me.id;
|
|
}
|
|
|
|
async getAvatar(): Promise<string | null> {
|
|
const me = await this.getMe();
|
|
return me.avatar !== null
|
|
? `https://cdn.discordapp.com/avatars/${me.id}/${me.avatar}.png`
|
|
: null;
|
|
}
|
|
|
|
async getEmail(): Promise<string | null> {
|
|
const me = await this.getMe();
|
|
return me.email;
|
|
}
|
|
|
|
async getState(): Promise<AuthProviderState> {
|
|
const username = await this.getUsername();
|
|
const id = await this.getId();
|
|
|
|
return {
|
|
name: "Discord",
|
|
connected: true,
|
|
id,
|
|
username
|
|
};
|
|
}
|
|
|
|
static get redirectUri(): string {
|
|
return `${process.env.BASE_DOMAIN}oauth/discord/redirect`;
|
|
}
|
|
|
|
static async getToken(
|
|
code: string
|
|
): Promise<DiscordAccessTokenResponse | null> {
|
|
const form = new URLSearchParams();
|
|
form.append("client_id", process.env.DISCORD_CLIENT_ID);
|
|
form.append("client_secret", process.env.DISCORD_CLIENT_SECRET);
|
|
form.append("grant_type", "authorization_code");
|
|
form.append("code", code);
|
|
form.append("redirect_uri", this.redirectUri);
|
|
|
|
const tokenResponse = await fetch("https://discord.com/api/oauth2/token", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
},
|
|
body: form.toString()
|
|
});
|
|
|
|
if (!tokenResponse.ok) return null;
|
|
return await tokenResponse.json();
|
|
}
|
|
|
|
static async refreshToken(
|
|
refreshToken: string
|
|
): Promise<DiscordAccessTokenResponse | null> {
|
|
const req = await fetch("https://discord.com/api/oauth2/token", {
|
|
method: "POST",
|
|
headers: {
|
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
},
|
|
body: new URLSearchParams({
|
|
client_id: process.env.DISCORD_CLIENT_ID,
|
|
client_secret: process.env.DISCORD_CLIENT_SECRET,
|
|
grant_type: "refresh_token",
|
|
refresh_token: refreshToken
|
|
}).toString()
|
|
});
|
|
|
|
if (!req.ok) return null;
|
|
return await req.json();
|
|
}
|
|
|
|
static async update(
|
|
id: string,
|
|
accessToken: string,
|
|
refreshToken: string,
|
|
expiresAt: Date,
|
|
userId?: number
|
|
): Promise<number> {
|
|
const a = await prisma.discordAuth.upsert({
|
|
where: {
|
|
id
|
|
},
|
|
create: {
|
|
id,
|
|
accessToken,
|
|
refreshToken,
|
|
expiresAt,
|
|
user:
|
|
userId != null
|
|
? { connect: { id: userId } }
|
|
: { create: { username: null } },
|
|
invalid: false
|
|
},
|
|
update: {
|
|
accessToken,
|
|
refreshToken,
|
|
expiresAt,
|
|
invalid: false
|
|
}
|
|
});
|
|
|
|
return a.userId;
|
|
}
|
|
}
|