feat: separate user and admin auth, redirect to settings after invite

This commit is contained in:
Isaac
2025-02-09 23:12:53 +00:00
parent 05c6ffa482
commit 2255d0d15d
8 changed files with 265 additions and 305 deletions

View File

@@ -1,7 +1,27 @@
const { domain } = require('../../lib/http');
module.exports.get = () => ({
handler: async function (req, res) { // MUST NOT use arrow function syntax
handler: async function (req, res) {
const cookie = req.cookies['oauth2-state'];
if (!cookie) {
return res.code(400).send({
error: 'Bad Request',
message: 'State is missing.',
statusCode: 400,
});
}
const state = new URLSearchParams(cookie);
if (state.get('secret') !== req.query.state) {
return res.code(400).send({
error: 'Bad Request',
message: 'Invalid state.',
statusCode: 400,
});
}
// TODO: check if req.query.permissions are correct
const data = await (await fetch('https://discord.com/api/oauth2/token', {
body: new URLSearchParams({
client_id: req.routeOptions.config.client.user.id,
@@ -14,22 +34,30 @@ module.exports.get = () => ({
method: 'POST',
})).json();
const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${data.access_token}` } })).json();
const redirect = (data.guild?.id && `/settings/${data.guild?.id}`) || state.get('redirect') || '/';
const bearerOptions = { headers: { 'Authorization': `Bearer ${data.access_token}` } };
const user = await (await fetch('https://discordapp.com/api/users/@me', bearerOptions)).json();
let scopes;
if (data.scope) {
scopes = data.scope.split(' ');
} else {
const auth = await (await fetch('https://discordapp.com/api/oauth2/@me', bearerOptions)).json();
scopes = auth.scopes;
}
const token = this.jwt.sign({
accessToken: data.access_token,
avatar: user.avatar,
expiresAt: Date.now() + (data.expires_in * 1000),
id: user.id,
locale: user.locale,
scopes,
username: user.username,
});
// note: if data.guild is present, guild_id and permissions should also be in req.query
const redirect = this.states.get(req.query.state) || (data.guild?.id && `/settings/${data.guild?.id}`) || '/';
this.states.delete(req.query.state);
res.setCookie('token', token, {
domain,
httpOnly: true,
maxAge: data.expires_in,
path: '/',

44
src/routes/auth/login.js Normal file
View File

@@ -0,0 +1,44 @@
const { randomBytes } = require('crypto');
module.exports.get = () => ({
handler: async function (req, res) {
const { client } = req.routeOptions.config;
const state = new URLSearchParams({
redirect: req.query.r ?? '',
secret: randomBytes(8).toString('hex'),
});
res.setCookie('oauth2-state', state.toString(), {
httpOnly: true,
sameSite: 'lax',
});
const params = {
client_id: client.user.id,
// ? prompt: 'none',
redirect_uri: `${process.env.HTTP_EXTERNAL}/auth/callback`, // if not set defaults to first allowed
response_type: 'code',
scope: 'guilds identify',
state: state.get('secret'),
};
if (req.query.invite !== undefined) {
params.prompt = 'consent'; // already implied by the bot scope
params.scope = 'applications.commands applications.commands.permissions.update bot ' + params.scope;
params.integration_type = '0';
params.permissions = '268561488';
if (req.query.guild) {
params.guild_id = req.query.guild;
params.disable_guild_select = 'true';
}
} else if (req.query.role === 'admin') { // invite implies admin already
params.scope = 'applications.commands.permissions.update ' + params.scope;
}
const url = new URL('https://discord.com/oauth2/authorize');
url.search = new URLSearchParams(params);
res.redirect(url.toString());
},
});

View File

@@ -1,5 +1,3 @@
const { domain } = require('../../lib/http');
module.exports.get = fastify => ({
handler: async function (req, res) {
const { accessToken } = req.user;
@@ -15,7 +13,6 @@ module.exports.get = fastify => ({
});
res.clearCookie('token', {
domain,
httpOnly: true,
path: '/',
sameSite: 'Strict',

View File

@@ -1,29 +1,5 @@
const { randomBytes } = require('crypto');
module.exports.get = () => ({
handler: async function (req, res) {
const { client } = req.routeOptions.config;
const state = randomBytes(8).toString('hex');
this.states.set(state, null);
const url = new URL('https://discord.com/oauth2/authorize');
url.searchParams.set('response_type', 'code');
url.searchParams.set('client_id', client.user.id);
url.searchParams.set('prompt', 'none');
url.searchParams.set('redirect_uri', `${process.env.HTTP_EXTERNAL}/auth/callback`); // window.location.origin
url.searchParams.set('scope', 'applications.commands applications.commands.permissions.update bot guilds identify');
url.searchParams.set('permissions', '268561488');
if (req.query.guild) {
url.searchParams.set('guild_id', req.query.guild);
url.searchParams.set('disable_guild_select', 'true');
}
res.setCookie('oauth2-redirect-state', state, {
httpOnly: true,
sameSite: 'lax',
});
res.redirect(url.toString());
res.redirect(`/auth/login?invite&guild=${req.query.guild || ''}`);
},
});