feat: prepare db stuff for the new api
All checks were successful
Lint Codebase / lint (push) Successful in 56s

This commit is contained in:
TheClashFruit 2024-09-01 14:37:26 +02:00
parent 57e5f69bc8
commit 36ac57378a
Signed by: TheClashFruit
GPG key ID: 09BB24C34C2F3204
6 changed files with 96 additions and 203 deletions

View file

@ -1,58 +1,8 @@
import { TeamRole } from '@/utils/permissions';
import mysql, { Pool, QueryResult } from 'mysql2/promise';
import crypto from 'node:crypto';
interface DiscordTokenData {
token_type: string;
access_token: string;
expires_in: number;
refresh_token: string;
scope: string;
id_token: string;
}
interface UserTable {
id: number;
did: string;
username: string;
global_name: string;
email: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
}
interface UserTableWithoutEmail {
id: number;
did: string;
username: string;
global_name: string;
avatar?: string;
banner?: string;
accent_color?: number;
permissions: number;
}
interface TeamMember {
id: number;
uid: number;
did: string;
username: string;
global_name: string;
email: string;
avatar: string;
banner: string;
accent_color: number;
permissions: number;
role: TeamRole;
}
class Database {
static instance: (Database | null) = null;
mysqlPool: (Pool | null) = null;
static instance: (Database | null) = null;
private mysqlPool: (Pool | null) = null;
constructor() {
if(Database.instance)
@ -71,154 +21,6 @@ class Database {
Database.instance = this;
}
// Auth Stuff
async createUser(user: any): Promise<number> {
const [ result ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE did = ?', [ user.id ]);
if ((result as any).length > 0) {
return (result as any)[0].id;
}
const [ res ] = await this.mysqlPool!.execute('INSERT INTO users (did, username, global_name, email, avatar, banner, accent_color) VALUES (?, ?, ?, ?, ?, ?, ?)', [
user.id,
user.username,
(user.global_name || user.username),
user.email,
user.avatar,
user.banner,
user.accent_color
]);
return (res as any).insertId;
}
/**
* Creates a session for the user.
*
* Should only be called by backend!
*
* @param userData The data returned from the `/oauth2/token` endpoint.
* @param userAgent The user agent of the user.
*
* @returns `string` The session token.
*/
async createSession(userData: DiscordTokenData, userAgent?: string): Promise<string | null> {
const res = await fetch(`${process.env.DISCORD_API}/users/@me`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${userData.access_token}`,
},
});
if (res.ok) {
const user = await res.json();
const uid = await this.createUser(user);
const sum = crypto.createHmac('sha256', process.env.AUTH_SECRET!);
const base = Buffer.from(user.id).toString('base64').replaceAll('=', '');
const date = Buffer.from((Date.now() - 1688940000000).toString()).toString('base64').replaceAll('=', '');
sum.update(userData.access_token);
const sid = base + '.' + date + '.' + sum.digest('hex');
const [ result ] = await this.mysqlPool!.execute('INSERT INTO sessions (sid, uid, access_token, refresh_token, id_token, user_agent, expires) VALUES (?, ?, ?, ?, ?, ?, ?)', [
sid,
uid,
userData.access_token,
userData.refresh_token,
userData.id_token,
userAgent,
new Date(Date.now() + (userData.expires_in * 1000)),
]);
return sid;
} else {
throw new Error('Error Fetching Discord User Data');
}
}
async deleteSession(sid: string): Promise<void> {
await this.mysqlPool!.execute('DELETE FROM sessions WHERE sid = ?', [ sid ]);
}
async getSession(sid: string): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE sid = ?', [ sid ]);
return (rows as any)[0];
}
// End of Auth Stuff
async getUser(id: number): Promise<UserTable> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE id = ?', [ id ]);
return (rows as UserTable[])[0];
}
async getUsers(): Promise<UserTableWithoutEmail[]> {
const [ rows ] = await this.mysqlPool!.execute('SELECT id, did, username, global_name, avatar, banner, accent_color, permissions FROM users');
return rows as UserTableWithoutEmail[];
}
async getUserUsername(username: string): Promise<UserTable> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM users WHERE username = ?', [ username ]);
return (rows as UserTable[])[0];
}
async getUserSessions(uid: number): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM sessions WHERE uid = ?', [ uid ]);
return rows;
}
async getTeam(): Promise<TeamMember[]> {
const [ rows ] = await this.mysqlPool!.execute('SELECT team_members.id AS tid, team_members.uid AS uid, users.did, users.username, users.global_name, users.email, users.avatar, users.banner, users.accent_color, users.permissions, team_members.role FROM team_members JOIN users ON team_members.uid = users.id;');
return (rows as any).map((row: any) => {
return {
id: row.tid,
uid: row.uid,
did: row.did,
username: row.username,
global_name: row.global_name,
email: row.email,
avatar: row.avatar,
banner: row.banner,
accent_color: row.accent_color,
permissions: row.permissions,
role: (row.role as TeamRole),
};
});
}
async getNations(): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations');
return rows;
}
async getNationCode(code: string): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM nations WHERE code = ?', [ code ]);
return (rows as any)[0];
}
async getCompaies(nid: number): Promise<any> {
const [ rows ] = await this.mysqlPool!.execute('SELECT * FROM companies WHERE nid = ?', [ nid ]);
return rows;
}
async updateUserPermissions(id: number, permissions: any) {
await this.mysqlPool!.execute('UPDATE users SET permissions = ? WHERE id = ?', [ permissions, id ]);
return;
}
}
export default Database;

22
lib/Discord.ts Normal file
View file

@ -0,0 +1,22 @@
class Discord {
private token: string;
constructor(token: string) {
this.token = token;
}
async user(): Promise<{ ok: boolean, data?: any }> {
const res = await fetch(`${process.env.DISCORD_API}/users/@me`, {
headers: {
Authorization: `Bearer ${this.token}`,
}
});
return {
ok: res.ok,
data: res.ok ? await res.json() : null,
};
}
}
export default Discord;

View file

@ -19,6 +19,7 @@
"lucide-react": "^0.429.0",
"mysql2": "^3.11.0",
"next": "14.2.6",
"nodejs-snowflake": "^2.0.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"showdown": "^2.1.0",

View file

@ -38,6 +38,9 @@ importers:
next:
specifier: 14.2.6
version: 14.2.6(@babel/core@7.25.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.8)
nodejs-snowflake:
specifier: ^2.0.1
version: 2.0.1
react:
specifier: ^18.3.1
version: 18.3.1
@ -2470,6 +2473,9 @@ packages:
node-releases@2.0.18:
resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==}
nodejs-snowflake@2.0.1:
resolution: {integrity: sha512-zMfDorNNsJm1OWWx/OUUGVT0bQ3TwC2ti4URD8UjnpffVLtLpy3eBDIaV5TnLs91YrRhwBD6L0StqU6YtnZt9A==}
nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
@ -5525,7 +5531,7 @@ snapshots:
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
eslint-plugin-react: 7.35.0(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
@ -5556,7 +5562,7 @@ snapshots:
is-bun-module: 1.1.0
is-glob: 4.0.3
optionalDependencies:
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
transitivePeerDependencies:
- '@typescript-eslint/parser'
- eslint-import-resolver-node
@ -5574,7 +5580,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.0):
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
dependencies:
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
@ -6260,6 +6266,8 @@ snapshots:
node-releases@2.0.18: {}
nodejs-snowflake@2.0.1: {}
nopt@5.0.0:
dependencies:
abbrev: 1.1.1

8
utils/snowflake.ts Normal file
View file

@ -0,0 +1,8 @@
import { Snowflake } from 'nodejs-snowflake';
const snowflake = new Snowflake({
custom_epoch: new Date('2023-07-10T00:00:00Z').getTime(),
instance_id: 1
});
export default snowflake;

52
utils/token_util.ts Normal file
View file

@ -0,0 +1,52 @@
import crypto from 'node:crypto';
import snowflake from './snowflake';
import Discord from '@/lib/Discord';
interface DiscordTokenData {
token_type: string;
access_token: string;
expires_in: number;
refresh_token: string;
scope: string;
id_token: string;
};
/**
* Creates a session token.
*
* @param oauthData The data returned from the `/oauth2/token` endpoint.
*
* @returns The session token.
*/
export async function createSessionToken(oauthData: DiscordTokenData): Promise<string> {
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.');
const dcUser = res.data;
const hmac = crypto
.createHmac('sha256', process.env.AUTH_SECRET!)
.update(dcUser.access_token)
.digest('hex');
const tuid = Buffer
.from(dcUser.id)
.toString('base64')
.replaceAll('=', '');
const tdid = Buffer
.from(
snowflake
.getUniqueID()
.toString()
)
.toString('base64')
.replaceAll('=', '');
return `${tuid}.${tdid}.${hmac}`;
}