This commit is contained in:
parent
0a8b61c472
commit
4e7c2a1df2
178
lib/Database.ts
178
lib/Database.ts
|
@ -2,6 +2,9 @@ import { TeamMember, User } from '@/interfaces';
|
|||
import { getBadges } from '@/utils/badges';
|
||||
import { getPermissions } from '@/utils/permissions';
|
||||
import mysql, { Pool, QueryResult } from 'mysql2/promise';
|
||||
import Discord from './Discord';
|
||||
import { createSessionToken, DiscordTokenData } from '@/utils/token_util';
|
||||
import snowflake from '@/utils/snowflake';
|
||||
|
||||
class Database {
|
||||
static instance: (Database | null) = null;
|
||||
|
@ -32,11 +35,11 @@ class Database {
|
|||
*
|
||||
* @returns User
|
||||
*/
|
||||
async getUser(id: BigInt): Promise<User> {
|
||||
async getUser(id: string): Promise<User | undefined> {
|
||||
const [ rows ] = await this.mysqlPool!.query('SELECT * FROM users WHERE id = ?', [ id ]);
|
||||
|
||||
if((rows as any[]).length === 0)
|
||||
throw new Error('User Not Found');
|
||||
return undefined;
|
||||
|
||||
const row = (rows as any[])[0];
|
||||
|
||||
|
@ -63,18 +66,179 @@ class Database {
|
|||
} as User;
|
||||
}
|
||||
|
||||
// Meta -----------
|
||||
/**
|
||||
* Get a user from their username.
|
||||
*
|
||||
* @param username User's username
|
||||
*
|
||||
* @returns User
|
||||
*/
|
||||
async getUserUsername(username: string): Promise<User | undefined> {
|
||||
const [ rows ] = await this.mysqlPool!.query('SELECT * FROM users WHERE username = ?', [ username ]);
|
||||
|
||||
async getTeam(): Promise<TeamMember[]> {
|
||||
if((rows as any[]).length === 0)
|
||||
return undefined;
|
||||
|
||||
const row = (rows as any[])[0];
|
||||
|
||||
return {
|
||||
id: row.id as BigInt,
|
||||
|
||||
username: row.username,
|
||||
displayName: row.display_name,
|
||||
|
||||
email: row.email,
|
||||
|
||||
avatar: row.avatar,
|
||||
banner: row.banner,
|
||||
|
||||
accentColor: row.accent_color,
|
||||
|
||||
discordId: row.discord_id as BigInt,
|
||||
|
||||
permissions: getPermissions(row.permissions),
|
||||
badges: getBadges(row.badges),
|
||||
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
} as User;
|
||||
}
|
||||
|
||||
private async getUserDiscord(discordId: BigInt): Promise<User | undefined> {
|
||||
const [ rows ] = await this.mysqlPool!.query('SELECT * FROM users WHERE discord_id = ?', [ discordId ]);
|
||||
|
||||
if((rows as any[]).length === 0)
|
||||
return undefined;
|
||||
|
||||
const row = (rows as any[])[0];
|
||||
|
||||
return {
|
||||
id: row.id as BigInt,
|
||||
|
||||
username: row.username,
|
||||
displayName: row.display_name,
|
||||
|
||||
email: row.email,
|
||||
|
||||
avatar: row.avatar,
|
||||
banner: row.banner,
|
||||
|
||||
accentColor: row.accent_color,
|
||||
|
||||
discordId: row.discord_id as BigInt,
|
||||
|
||||
permissions: getPermissions(row.permissions),
|
||||
badges: getBadges(row.badges),
|
||||
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at
|
||||
} as User;
|
||||
}
|
||||
|
||||
async newUser(user: any): Promise<User> {
|
||||
const userExists = await this.getUserDiscord(user.id);
|
||||
|
||||
if(userExists)
|
||||
return userExists;
|
||||
|
||||
const id = snowflake.getUniqueID();
|
||||
|
||||
await this.mysqlPool!.query('INSERT INTO users (id, username, display_name, email, avatar, banner, accent_color, discord_id, permissions, badges) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [
|
||||
id.toString(),
|
||||
|
||||
user.username,
|
||||
user.global_name,
|
||||
user.email,
|
||||
user.avatar,
|
||||
user.banner,
|
||||
user.accent_color,
|
||||
user.id
|
||||
]);
|
||||
|
||||
return {
|
||||
id: id,
|
||||
|
||||
username: user.username,
|
||||
displayName: user.display_name,
|
||||
|
||||
email: user.email,
|
||||
|
||||
avatar: user.avatar,
|
||||
banner: user.banner,
|
||||
|
||||
accentColor: user.accent_color,
|
||||
|
||||
discordId: user.discord as BigInt,
|
||||
|
||||
permissions: getPermissions(user.permissions),
|
||||
badges: getBadges(user.badges),
|
||||
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date()
|
||||
} as User;
|
||||
}
|
||||
|
||||
// Session -------------
|
||||
|
||||
/**
|
||||
* Create a new session.
|
||||
*
|
||||
* @param oauthData The data returned from the `/oauth2/token` endpoint.
|
||||
* @param userAgent Request's User-Agent
|
||||
* @param ip Request's IP
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
async newSession(oauthData: DiscordTokenData, userAgent: string, ip: string): Promise<string | undefined> {
|
||||
const discord = new Discord(oauthData.access_token);
|
||||
|
||||
const dcUser = await discord.user();
|
||||
const token = await createSessionToken(oauthData);
|
||||
|
||||
if(!dcUser.ok || !token)
|
||||
return undefined;
|
||||
|
||||
const user = await this.newUser(dcUser.data);
|
||||
|
||||
if(!user)
|
||||
return undefined;
|
||||
|
||||
try {
|
||||
await this.mysqlPool!.query('INSERT INTO user_sessions (token, user_id, dc_access_token, dc_refresh_token, dc_id_token, user_agent, ip, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [
|
||||
token,
|
||||
user.id,
|
||||
oauthData.access_token,
|
||||
oauthData.refresh_token,
|
||||
oauthData.id_token,
|
||||
userAgent,
|
||||
ip,
|
||||
new Date(Date.now() + (oauthData.expires_in * 1000))
|
||||
]);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
// Meta ----------------
|
||||
|
||||
async getTeam(): Promise<(TeamMember | undefined)[]> {
|
||||
const [ rows ] = await this.mysqlPool!.query('SELECT * FROM team');
|
||||
|
||||
const usersPromises = (rows as any[]).map(row => this.getUser(row.user_id));
|
||||
const users = await Promise.all(usersPromises);
|
||||
|
||||
return users.map((user, i) => ({
|
||||
role: (rows as any[])[i].role,
|
||||
return users.map((user, i) => {
|
||||
if (!user) return undefined;
|
||||
|
||||
return {
|
||||
...user,
|
||||
} as TeamMember));
|
||||
role: (rows as any[])[i].role
|
||||
} as TeamMember;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
52
pages/api/v1/auth.ts
Normal file
52
pages/api/v1/auth.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import Database from '@/lib/Database';
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
|
||||
export default async function handler(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<any>,
|
||||
) {
|
||||
const db = new Database();
|
||||
|
||||
const { code, state } = req.query;
|
||||
|
||||
if (typeof code !== 'string' || typeof state !== 'string') {
|
||||
return res.status(400).json({
|
||||
code: 400,
|
||||
message: 'Invalid Request'
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await fetch(`${process.env.DISCORD_API}/oauth2/token`, {
|
||||
method: 'POST',
|
||||
body: new URLSearchParams({
|
||||
client_id: process.env.DISCORD_CLIENT!,
|
||||
client_secret: process.env.DISCORD_SECRET!,
|
||||
grant_type: 'authorization_code',
|
||||
code,
|
||||
redirect_uri: process.env.DISCORD_REDIRECT!,
|
||||
}),
|
||||
});
|
||||
|
||||
const json = await data.json();
|
||||
const token = await db.newSession(json, req.headers['user-agent']!, req.socket.remoteAddress!);
|
||||
|
||||
if (!token) {
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
message: 'Internal Server Error'
|
||||
});
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
token
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
return res.status(500).json({
|
||||
code: 500,
|
||||
message: 'Internal Server Error'
|
||||
});
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ import snowflake from './snowflake';
|
|||
|
||||
import Discord from '@/lib/Discord';
|
||||
|
||||
interface DiscordTokenData {
|
||||
export interface DiscordTokenData {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
|
@ -20,18 +20,18 @@ interface DiscordTokenData {
|
|||
*
|
||||
* @returns The session token.
|
||||
*/
|
||||
export async function createSessionToken(oauthData: DiscordTokenData): Promise<string> {
|
||||
export async function createSessionToken(oauthData: DiscordTokenData): Promise<string | undefined> {
|
||||
const discord = new Discord(oauthData.access_token);
|
||||
const res = await discord.user();
|
||||
|
||||
if (!res.ok)
|
||||
throw new Error('Failed to fetch user data from Discord.');
|
||||
return undefined;
|
||||
|
||||
const dcUser = res.data;
|
||||
|
||||
const hmac = crypto
|
||||
.createHmac('sha256', process.env.AUTH_SECRET!)
|
||||
.update(dcUser.access_token)
|
||||
.update(oauthData.access_token)
|
||||
.digest('hex');
|
||||
|
||||
const tuid = Buffer
|
||||
|
|
Loading…
Reference in a new issue