2024-01-28 11:07:10 +01:00
import jwt from "jsonwebtoken" ;
import qs from "querystring" ;
2024-01-28 16:54:32 +01:00
import j from "joi" ;
2024-01-28 11:07:10 +01:00
import { Response , Router } from "express" ;
2024-02-04 22:28:42 +01:00
import { DASHBOARD_ROOT , DISCORD_CLIENT_ID , DISCORD_CLIENT_SECRET , DISCORD_SERVER_ID , FULL_SERVER_ROOT , JWT_KEY } from "../Modules/Constants" ;
2024-01-28 11:07:10 +01:00
import { User , UserPermissions } from "../Schemas/User" ;
2024-01-28 16:54:32 +01:00
import { ValidateQuery } from "../Modules/Middleware" ;
2024-02-04 22:28:42 +01:00
import { Bot } from "../Handlers/DiscordBot" ;
import { Debug } from "../Modules/Logger" ;
import { DiscordRole } from "../Schemas/DiscordRole" ;
import { In } from "typeorm" ;
import { magenta } from "colorette" ;
2024-01-28 11:07:10 +01:00
const App = Router ( ) ;
// ? hacky, if you want, make it less hacky
async function QuickRevokeToken ( res : Response , Token : string ) {
2024-02-05 00:24:53 +01:00
await fetch ( "https://discord.com/api/oauth2/token/revoke" , {
method : "POST" ,
headers : {
"Content-Type" : "application/x-www-form-urlencoded" ,
"Authorization" : ` Basic ${ Buffer . from ( ` ${ DISCORD_CLIENT_ID } : ${ DISCORD_CLIENT_SECRET } ` ) . toString ( "base64" ) } `
} ,
body : qs.stringify ( { token : Token , token_type_hint : "access_token" } )
} )
2024-01-28 11:07:10 +01:00
return res ;
}
2024-02-05 00:24:53 +01:00
App . get ( "/discord/url" , ( _ , res ) = > res . send ( ` https://discord.com/api/oauth2/authorize?client_id= ${ qs . escape ( DISCORD_CLIENT_ID ! ) } &response_type=code&redirect_uri= ${ qs . escape ( ` ${ FULL_SERVER_ROOT } /api/discord ` ) } &scope=identify ` ) )
2024-01-28 11:07:10 +01:00
2024-01-28 16:54:32 +01:00
App . get ( "/discord" ,
2024-02-05 00:24:53 +01:00
ValidateQuery ( j . object ( {
code : j.string ( ) . pattern ( /^(\w|\d)+$/i ) . required ( ) ,
state : j.string ( )
} ) ) ,
async ( req , res ) = > {
//const Discord = await axios.post(`https://discord.com/api/oauth2/token`, qs.stringify({ grant_type: "authorization_code", code: req.query.code as string, redirect_uri: `${FULL_SERVER_ROOT}/api/discord` }), { auth: { username: DISCORD_CLIENT_ID!, password: DISCORD_CLIENT_SECRET! } });
const Discord = await fetch (
"https://discord.com/api/oauth2/token" ,
{
method : "POST" ,
headers : {
"Content-Type" : "application/x-www-form-urlencoded" ,
"Authorization" : ` Basic ${ Buffer . from ( ` ${ DISCORD_CLIENT_ID } : ${ DISCORD_CLIENT_SECRET } ` ) . toString ( "base64" ) } `
} ,
body : qs.stringify ( { grant_type : "authorization_code" , code : req.query.code as string , redirect_uri : ` ${ FULL_SERVER_ROOT } /api/discord ` } )
}
)
if ( Discord . status !== 200 )
return res . status ( 500 ) . send ( "Failed to request OAuth token from Discord's services." ) ;
const DiscordData = await Discord . json ( ) as any ; // :waaaaa:
if ( ! DiscordData . scope . includes ( "identify" ) )
return ( await QuickRevokeToken ( res , DiscordData . access_token ) ) . status ( 400 ) . send ( "Missing identify scope. Please check if your OAuth link is correctly set up!" ) ;
const UserData = await fetch ( "https://discord.com/api/v10/users/@me" , {
method : "GET" ,
headers : {
"Authorization" : ` ${ DiscordData . token_type } ${ DiscordData . access_token } `
}
} )
const UserDataBody = await UserData . json ( ) as any ;
if ( UserData . status !== 200 )
return ( await QuickRevokeToken ( res , DiscordData . access_token ) ) . status ( 500 ) . send ( "Failed to request user data from Discord's services." ) ;
await QuickRevokeToken ( res , DiscordData . access_token ) ;
const AnyUserExists = await User . exists ( ) ; // automatically grant the first user on the database administrator permissions
let UserPermissionLevel = ! AnyUserExists ? UserPermissions.Administrator : UserPermissions.User ;
if ( AnyUserExists && Bot . isReady ( ) ) {
Debug ( "Using Discord roles to determine user permission level since the Discord bot exists and is ready." ) ;
const Sewer = Bot . guilds . cache . get ( DISCORD_SERVER_ID as string ) ;
const Membuh = await Sewer ? . members . fetch ( UserDataBody . id ) ;
if ( Membuh ) {
const RoulInDeightabaise = await DiscordRole . find ( { where : { ID : In ( Membuh . roles . cache . map ( x = > x . id ) ) } , order : { GrantedPermissions : "DESC" } } ) ;
if ( RoulInDeightabaise . length > 0 )
UserPermissionLevel = RoulInDeightabaise [ 0 ] . GrantedPermissions ;
Debug ( ` Detected ${ magenta ( RoulInDeightabaise . length ) } roles that override Database permissions for user ${ magenta ( ` @ ${ UserDataBody . username } ` ) } . Giving permission level ${ magenta ( UserPermissionLevel ) } . ` )
}
}
2024-02-04 22:28:42 +01:00
2024-02-05 00:24:53 +01:00
let DBUser = await User . findOne ( { where : { ID : UserDataBody.id } } ) ;
if ( ! DBUser )
DBUser = await User . create ( {
ID : UserDataBody.id ,
Username : UserDataBody.username ,
DisplayName : UserDataBody.global_name ? ? UserDataBody . username ,
ProfilePictureURL : ` https://cdn.discordapp.com/avatars/ ${ UserDataBody . id } / ${ UserDataBody . avatar } .webp ` ,
Library : [ ] ,
PermissionLevel : UserPermissionLevel
} ) . save ( ) ;
else {
DBUser . Username = UserDataBody . username ;
DBUser . DisplayName = UserDataBody . global_name ? ? UserDataBody . username ;
DBUser . ProfilePictureURL = ` https://cdn.discordapp.com/avatars/ ${ UserDataBody . id } / ${ UserDataBody . avatar } .webp ` ;
DBUser . PermissionLevel = UserPermissionLevel ;
await DBUser . save ( ) ;
2024-02-04 22:28:42 +01:00
}
2024-02-05 00:24:53 +01:00
const JWT = jwt . sign ( { ID : UserDataBody.id } , JWT_KEY ! , { algorithm : "HS256" } ) ;
const UserDetails = Buffer . from ( JSON . stringify ( { ID : UserDataBody.id , Username : UserDataBody.username , GlobalName : UserDataBody.global_name , Avatar : ` https://cdn.discordapp.com/avatars/ ${ UserDataBody . id } / ${ UserDataBody . avatar } .webp ` , IsAdmin : DBUser.PermissionLevel >= UserPermissions . Administrator , Role : DBUser.PermissionLevel } ) ) . toString ( "hex" )
if ( req . query . state ) {
try {
const Decoded = JSON . parse ( Buffer . from ( decodeURI ( req . query . state as string ) , "base64" ) . toString ( "utf-8" ) ) ;
if ( Decoded . Client === "PartypackerDesktop" )
return res . redirect ( ` http://localhost:14968/?token= ${ encodeURI ( JWT ) } &user= ${ encodeURI ( UserDetails ) } ` )
else
return res . status ( 400 ) . send ( "Unsupported API client." ) ; // idk maybe in the future we will maek more clients
} catch {
return res . status ( 400 ) . send ( "Invalid state." ) ;
}
2024-01-28 11:07:10 +01:00
}
2024-02-05 00:24:53 +01:00
res
. cookie ( "Token" , JWT )
. cookie ( "UserDetails" , UserDetails )
. redirect ( ` ${ DASHBOARD_ROOT } /profile ` ) ;
} )
2024-01-28 11:07:10 +01:00
export default {
App ,
DefaultAPI : "/api"
2024-01-22 00:41:59 +01:00
}