feat: add API service keys

This commit is contained in:
Isaac 2023-03-12 22:21:21 +00:00
parent d09598dd3f
commit 6773d9ddbe
No known key found for this signature in database
GPG Key ID: 0DE40AE37BBA5C33
16 changed files with 105 additions and 80 deletions

View File

@ -1,6 +1,6 @@
{
"name": "discord-tickets",
"version": "4.0.0-beta.7",
"version": "4.0.0-beta.8",
"private": "true",
"description": "An open-source Discord bot for ticket management",
"main": "src/",
@ -40,7 +40,7 @@
"node": ">=18"
},
"dependencies": {
"@discord-tickets/settings": "^2.0.0",
"@discord-tickets/settings": "^2.1.0",
"@eartharoid/dbf": "^0.3.3",
"@eartharoid/dtf": "^2.0.1",
"@eartharoid/i18n": "^1.2.1",
@ -50,7 +50,7 @@
"@prisma/client": "^4.11.0",
"boxen": "^7.0.2",
"cryptr": "^6.2.0",
"discord.js": "^14.7.1",
"discord.js": "^14.8.0",
"dotenv": "^16.0.3",
"fastify": "^4.14.1",
"figlet": "^1.5.2",

View File

@ -3,7 +3,7 @@ lockfileVersion: 5.4
specifiers:
'@commitlint/cli': ^17.4.4
'@commitlint/config-conventional': ^17.4.4
'@discord-tickets/settings': ^2.0.0
'@discord-tickets/settings': ^2.1.0
'@eartharoid/dbf': ^0.3.3
'@eartharoid/dtf': ^2.0.1
'@eartharoid/i18n': ^1.2.1
@ -16,7 +16,7 @@ specifiers:
bufferutil: ^4.0.7
conventional-changelog-cli: ^2.2.2
cryptr: ^6.2.0
discord.js: ^14.7.1
discord.js: ^14.8.0
dotenv: ^16.0.3
erlpack: github:discord/erlpack
eslint: ^8.36.0
@ -43,7 +43,7 @@ specifiers:
zlib-sync: ^0.1.8
dependencies:
'@discord-tickets/settings': 2.0.0_svelte@3.56.0
'@discord-tickets/settings': 2.1.0_svelte@3.56.0
'@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq
'@eartharoid/dtf': 2.0.1
'@eartharoid/i18n': 1.2.1
@ -53,7 +53,7 @@ dependencies:
'@prisma/client': 4.11.0_prisma@4.11.0
boxen: 7.0.2
cryptr: 6.2.0
discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq
discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq
dotenv: 16.0.3
fastify: 4.14.1
figlet: 1.5.2
@ -293,8 +293,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@discord-tickets/settings/2.0.0_svelte@3.56.0:
resolution: {integrity: sha512-oVG8qfX21cmufi/jyJV4LrbzsZK86KUmFfgb5W39LCD/+zIg1+l8XrOfkONjwlbFUKbjtV0CV2xgx9Ns+0Wg2w==}
/@discord-tickets/settings/2.1.0_svelte@3.56.0:
resolution: {integrity: sha512-eWs4hQ/5VtbMsPcs/jOHwrPBOB5XBKuGYY0xILUqYFINa5oJsHIzHEjHjEIDcxW2xUJ3g92soBe5GVM3PjkfMA==}
dependencies:
'@fortawesome/fontawesome-free': 6.3.0
'@skyra/discord-components-core': 3.6.0
@ -310,11 +310,12 @@ packages:
- svelte
dev: false
/@discordjs/builders/1.4.0:
resolution: {integrity: sha512-nEeTCheTTDw5kO93faM1j8ZJPonAX86qpq/QVoznnSa8WWcCgJpjlu6GylfINTDW6o7zZY0my2SYdxx2mfNwGA==}
/@discordjs/builders/1.5.0:
resolution: {integrity: sha512-7XxT78mnNBPigHn2y6KAXkicxIBFtZREGWaRZ249EC1l6gBUEP8IyVY5JTciIjJArxkF+tg675aZvsTNTKBpmA==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/util': 0.1.0
'@discordjs/formatters': 0.2.0
'@discordjs/util': 0.2.0
'@sapphire/shapeshift': 3.8.1
discord-api-types: 0.37.35
fast-deep-equal: 3.1.3
@ -322,17 +323,24 @@ packages:
tslib: 2.5.0
dev: false
/@discordjs/collection/1.3.0:
resolution: {integrity: sha512-ylt2NyZ77bJbRij4h9u/wVy7qYw/aDqQLWnadjvDqW/WoWCxrsX6M3CIw9GVP5xcGCDxsrKj5e0r5evuFYwrKg==}
/@discordjs/collection/1.4.0:
resolution: {integrity: sha512-hiOJyk2CPFf1+FL3a4VKCuu1f448LlROVuu8nLz1+jCOAPokUcdFAV+l4pd3B3h6uJlJQSASoZzrdyNdjdtfzQ==}
engines: {node: '>=16.9.0'}
dev: false
/@discordjs/rest/1.5.0:
resolution: {integrity: sha512-lXgNFqHnbmzp5u81W0+frdXN6Etf4EUi8FAPcWpSykKd8hmlWh1xy6BmE0bsJypU1pxohaA8lQCgp70NUI3uzA==}
/@discordjs/formatters/0.2.0:
resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/collection': 1.3.0
'@discordjs/util': 0.1.0
discord-api-types: 0.37.35
dev: false
/@discordjs/rest/1.6.0:
resolution: {integrity: sha512-HGvqNCZ5Z5j0tQHjmT1lFvE5ETO4hvomJ1r0cbnpC1zM23XhCpZ9wgTCiEmaxKz05cyf2CI9p39+9LL+6Yz1bA==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/collection': 1.4.0
'@discordjs/util': 0.2.0
'@sapphire/async-queue': 1.5.0
'@sapphire/snowflake': 3.4.0
discord-api-types: 0.37.35
@ -341,15 +349,15 @@ packages:
undici: 5.20.0
dev: false
/@discordjs/util/0.1.0:
resolution: {integrity: sha512-e7d+PaTLVQav6rOc2tojh2y6FE8S7REkqLldq1XF4soCx74XB/DIjbVbVLtBemf0nLW77ntz0v+o5DytKwFNLQ==}
/@discordjs/util/0.2.0:
resolution: {integrity: sha512-/8qNbebFzLWKOOg+UV+RB8itp4SmU5jw0tBUD3ifElW6rYNOj1Ku5JaSW7lLl/WgjjxF01l/1uQPCzkwr110vg==}
engines: {node: '>=16.9.0'}
dev: false
/@eartharoid/dbf/0.3.3_3cxu5zja4e2r5wmvge7mdcljwq:
resolution: {integrity: sha512-eVDdpFlDV5CAvqoV5g1iAvoYhPjnvcyJ0Nnepc1YihlE1KIYGhVIK/2RaDsltzxRuiweO3Y7dvDj/cUpJnnFPA==}
dependencies:
discord.js: 14.7.1_3cxu5zja4e2r5wmvge7mdcljwq
discord.js: 14.8.0_3cxu5zja4e2r5wmvge7mdcljwq
node-dir: 0.1.17
transitivePeerDependencies:
- bufferutil
@ -1419,14 +1427,15 @@ packages:
resolution: {integrity: sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg==}
dev: false
/discord.js/14.7.1_3cxu5zja4e2r5wmvge7mdcljwq:
resolution: {integrity: sha512-1FECvqJJjjeYcjSm0IGMnPxLqja/pmG1B0W2l3lUY2Gi4KXiyTeQmU1IxWcbXHn2k+ytP587mMWqva2IA87EbA==}
/discord.js/14.8.0_3cxu5zja4e2r5wmvge7mdcljwq:
resolution: {integrity: sha512-UOxYtc/YnV7jAJ2gISluJyYeBw4e+j8gWn+IoqG8unaHAVuvZ13DdYN0M1f9fbUgUvSarV798inIrYFtDNDjwQ==}
engines: {node: '>=16.9.0'}
dependencies:
'@discordjs/builders': 1.4.0
'@discordjs/collection': 1.3.0
'@discordjs/rest': 1.5.0
'@discordjs/util': 0.1.0
'@discordjs/builders': 1.5.0
'@discordjs/collection': 1.4.0
'@discordjs/formatters': 0.2.0
'@discordjs/rest': 1.6.0
'@discordjs/util': 0.2.0
'@sapphire/snowflake': 3.4.0
'@types/ws': 8.5.4
discord-api-types: 0.37.35
@ -1646,8 +1655,8 @@ packages:
strip-final-newline: 2.0.0
dev: true
/execa/7.0.0:
resolution: {integrity: sha512-tQbH0pH/8LHTnwTrsKWideqi6rFB/QNUawEwrn+WHyz7PX1Tuz2u7wfTvbaNBdP5JD5LVWxNo8/A8CHNZ3bV6g==}
/execa/7.1.0:
resolution: {integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
dependencies:
cross-spawn: 7.0.3
@ -2369,9 +2378,9 @@ packages:
cli-truncate: 3.1.0
commander: 10.0.0
debug: 4.3.4
execa: 7.0.0
execa: 7.1.0
lilconfig: 2.1.0
listr2: 5.0.7
listr2: 5.0.8
micromatch: 4.0.5
normalize-path: 3.0.0
object-inspect: 1.12.3
@ -2383,8 +2392,8 @@ packages:
- supports-color
dev: true
/listr2/5.0.7:
resolution: {integrity: sha512-MD+qXHPmtivrHIDRwPYdfNkrzqDiuaKU/rfBcec3WMyMF3xylQj3jMq344OtvQxz7zaCFViRAeqlr2AFhPvXHw==}
/listr2/5.0.8:
resolution: {integrity: sha512-mC73LitKHj9w6v30nLNGPetZIlfpUniNSsxxrbaPcWOjDb92SHPzJPi/t+v1YC/lxKz/AJ9egOjww0qUuFxBpA==}
engines: {node: ^14.13.1 || >=16.0.0}
peerDependencies:
enquirer: '>= 2.3.0 < 3'

View File

@ -33,6 +33,7 @@ const env = {
!!v ||
new Error('is required'),
HTTP_TRUST_PROXY: () => true, // optional
INVALIDATE_TOKENS: () => true, // optional
OVERRIDE_ARCHIVE: () => true, // optional
PUBLIC_BOT: () => true, // optional
PUBLISH_COMMANDS: () => true, // optional
@ -53,4 +54,4 @@ const load = options => {
module.exports = {
env,
load,
};
};

View File

@ -53,22 +53,20 @@ module.exports = async client => {
fastify.decorate('authenticate', async (req, res) => {
try {
const data = await req.jwtVerify();
if (data.payload.expiresAt < Date.now()) {
return res.code(401).send({
error: 'Unauthorised',
message: 'You are not authenticated.',
statusCode: 401,
});
}
} catch (err) {
res.send(err);
if (data.expiresAt < Date.now()) throw 'expired';
if (data.createdAt < new Date(process.env.INVALIDATE_TOKENS).getTime()) throw 'expired';
} catch (error) {
return res.code(401).send({
error: 'Unauthorised',
message: error === 'expired' ? 'Your token has expired; please re-authenticate.' : 'You are not authenticated.',
statusCode: 401,
});
}
});
fastify.decorate('isAdmin', async (req, res) => {
try {
const userId = req.user.payload.id;
const userId = req.user.id;
const guildId = req.params.guild;
const guild = client.guilds.cache.get(guildId);
if (!guild) {
@ -175,4 +173,4 @@ module.exports = async client => {
}) => {
client.log.error.http(`SvelteKit ${errorId} ${error}`);
});
};
};

View File

@ -23,7 +23,7 @@ module.exports.delete = fastify => ({
name: category.name,
type: 'category',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return category;
@ -146,7 +146,7 @@ module.exports.patch = fastify => ({
await client.tickets.getCategory(categoryId, true);
await updateStaffRoles(guild);
if (req.user.payload.accessToken && JSON.stringify(category.staffRoles) !== JSON.stringify(original.staffRoles)) {
if (req.user.accessToken && JSON.stringify(category.staffRoles) !== JSON.stringify(original.staffRoles)) {
Promise.all([
'Create ticket for user',
'claim',
@ -170,7 +170,7 @@ module.exports.patch = fastify => ({
type: ApplicationCommandPermissionType.Role,
})),
],
token: req.user.payload.accessToken,
token: req.user.accessToken,
}),
))
.then(() => client.log.success('Updated application command permissions in "%s"', guild.name))
@ -189,10 +189,10 @@ module.exports.patch = fastify => ({
name: category.name,
type: 'category',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return category;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -20,10 +20,10 @@ module.exports.delete = fastify => ({
name: question.label,
type: 'question',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return question;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -54,7 +54,7 @@ module.exports.post = fastify => ({
/** @type {import('client')} */
const client = res.context.config.client;
const user = await client.users.fetch(req.user.payload.id);
const user = await client.users.fetch(req.user.id);
const guild = client.guilds.cache.get(req.params.guild);
const data = req.body;
const allow = ['ViewChannel', 'ReadMessageHistory', 'SendMessages', 'EmbedLinks', 'AttachFiles'];
@ -101,7 +101,7 @@ module.exports.post = fastify => ({
await client.tickets.getCategory(category.id, true);
await updateStaffRoles(guild);
if (req.user.payload.accessToken) {
if (req.user.accessToken) {
Promise.all([
'Create ticket for user',
'claim',
@ -125,7 +125,7 @@ module.exports.post = fastify => ({
type: ApplicationCommandPermissionType.Role,
})),
],
token: req.user.payload.accessToken,
token: req.user.accessToken,
}),
))
.then(() => client.log.success('Updated application command permissions in "%s"', guild.name))
@ -140,10 +140,10 @@ module.exports.post = fastify => ({
name: category.name,
type: 'category',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return category;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -134,10 +134,10 @@ module.exports.post = fastify => ({
id: channel.toString(),
type: 'panel',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return true;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -16,7 +16,7 @@ module.exports.delete = fastify => ({
name: client.guilds.cache.get(id),
type: 'settings',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return settings;
},
@ -69,9 +69,9 @@ module.exports.patch = fastify => ({
name: client.guilds.cache.get(id).name,
type: 'settings',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return settings;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -30,7 +30,7 @@ module.exports.delete = fastify => ({
name: tag.name,
type: 'tag',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return tag;
@ -97,10 +97,10 @@ module.exports.patch = fastify => ({
name: tag.name,
type: 'tag',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return tag;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -56,10 +56,10 @@ module.exports.post = fastify => ({
name: tag.name,
type: 'tag',
},
userId: req.user.payload.id,
userId: req.user.id,
});
return tag;
},
onRequest: [fastify.authenticate, fastify.isAdmin],
});
});

View File

@ -3,7 +3,7 @@ const { PermissionsBitField } = require('discord.js');
module.exports.get = fastify => ({
handler: async (req, res) => {
const { client } = res.context.config;
const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.payload.accessToken}` } })).json();
const guilds = await (await fetch('https://discordapp.com/api/users/@me/guilds', { headers: { 'Authorization': `Bearer ${req.user.accessToken}` } })).json();
res.send(
guilds
.filter(guild => guild.owner || new PermissionsBitField(guild.permissions.toString()).has(PermissionsBitField.Flags.ManageGuild))
@ -16,4 +16,4 @@ module.exports.get = fastify => ({
);
},
onRequest: [fastify.authenticate],
});
});

View File

@ -1,4 +1,4 @@
module.exports.get = fastify => ({
handler: req => req.user.payload,
handler: req => req.user,
onRequest: [fastify.authenticate],
});
});

View File

@ -0,0 +1,19 @@
module.exports.get = fastify => ({
handler: async function (req, res) { // MUST NOT use arrow function syntax
if (process.env.PUBLIC_BOT === 'true') {
return res.code(400).send({
error: 'Bad Request',
message: 'API keys are not available on public bots.',
statusCode: 400,
});
} else {
return {
token: this.jwt.sign({
createdAt: Date.now(),
id: req.user.id,
}),
};
}
},
onRequest: [fastify.authenticate],
});

View File

@ -7,17 +7,15 @@ module.exports.get = () => ({
expires_in: expiresIn,
} = await this.discord.getAccessTokenFromAuthorizationCodeFlow(req);
const user = await (await fetch('https://discordapp.com/api/users/@me', { headers: { 'Authorization': `Bearer ${accessToken}` } })).json();
const payload = {
const token = this.jwt.sign({
accessToken,
avatar: `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`,
avatar: user.avatar,
discriminator: user.discriminator,
expiresAt: Date.now() + (expiresIn * 1000),
id: user.id,
locale: user.locale,
username: user.username,
};
const token = this.jwt.sign({ payload });
});
res
.setCookie('token', token, {
domain,
@ -30,4 +28,4 @@ module.exports.get = () => ({
.redirect(this.states.get(req.query.state) || '/');
this.states.delete(req.query.state);
},
});
});

View File

@ -1,7 +1,7 @@
module.exports.get = fastify => ({
handler: async function (req, res) {
await fetch('https://discord.com/api/oauth2/token/revoke', {
body: new URLSearchParams({ token: req.user.payload.accessToken }).toString(),
body: new URLSearchParams({ token: req.user.accessToken }).toString(),
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
method: 'POST',
});
@ -10,4 +10,4 @@ module.exports.get = fastify => ({
.send('The token has been revoked.');
},
onRequest: [fastify.authenticate],
});
});