Merge pull request #1265 from thedudedies21/Update_Base_Webhooks
Finish Webhooks, and add config endpoint for client
This commit is contained in:
commit
89abd2ec12
@ -4620,7 +4620,9 @@
|
|||||||
"channel": {
|
"channel": {
|
||||||
"$ref": "#/components/schemas/RateLimitOptions"
|
"$ref": "#/components/schemas/RateLimitOptions"
|
||||||
},
|
},
|
||||||
"auth": {}
|
"auth": {
|
||||||
|
"$ref": "#/components/schemas/AuthRateLimit"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"auth",
|
"auth",
|
||||||
@ -4629,6 +4631,21 @@
|
|||||||
"webhook"
|
"webhook"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"AuthRateLimit": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"login": {
|
||||||
|
"$ref": "#/components/schemas/RateLimitOptions"
|
||||||
|
},
|
||||||
|
"register": {
|
||||||
|
"$ref": "#/components/schemas/RateLimitOptions"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"login",
|
||||||
|
"register"
|
||||||
|
]
|
||||||
|
},
|
||||||
"GlobalRateLimits": {
|
"GlobalRateLimits": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -5857,6 +5874,15 @@
|
|||||||
},
|
},
|
||||||
"mention_count": {
|
"mention_count": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"flags": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"last_viewed": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -8872,18 +8898,6 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"enforce_nonce": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"nonce": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"poll": {
|
|
||||||
"$ref": "#/definitions/Poll"
|
|
||||||
},
|
|
||||||
"sticker_ids": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"message_reference": {
|
"message_reference": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -8895,12 +8909,44 @@
|
|||||||
},
|
},
|
||||||
"guild_id": {
|
"guild_id": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fail_if_not_exists": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": [
|
"required": [
|
||||||
"message_id"
|
"message_id"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"sticker_ids": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nonce": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"enforce_nonce": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"poll": {
|
||||||
|
"$ref": "#/components/schemas/PollCreationSchema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"WebhookUpdateSchema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"avatar": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"channel_id": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -9144,6 +9190,104 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"webhooks"
|
"webhooks"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No description available"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "No description available"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "webhook_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "webhook_id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"webhooks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/WebhookUpdateSchema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/WebhookCreateResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "No description available"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "No description available"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "webhook_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "webhook_id"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"webhooks"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/webhooks/{webhook_id}/{token}/": {
|
"/webhooks/{webhook_id}/{token}/": {
|
||||||
@ -9268,6 +9412,122 @@
|
|||||||
"tags": [
|
"tags": [
|
||||||
"webhooks"
|
"webhooks"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No description available"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "No description available"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "webhook_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "webhook_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "token",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "token"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"webhooks"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/WebhookUpdateSchema"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Message"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/APIErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "No description available"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "No description available"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "webhook_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "webhook_id"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "token",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "token"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"webhooks"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/voice/regions/": {
|
"/voice/regions/": {
|
||||||
|
4979
assets/schemas.json
Normal file → Executable file
4979
assets/schemas.json
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
118
flake.lock
generated
118
flake.lock
generated
@ -1,61 +1,61 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"flake-utils": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1731533236,
|
||||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "numtide",
|
"owner": "numtide",
|
||||||
"repo": "flake-utils",
|
"repo": "flake-utils",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1737062831,
|
"lastModified": 1737062831,
|
||||||
"narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=",
|
"narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c",
|
"rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"ref": "nixos-unstable",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1681028828,
|
"lastModified": 1681028828,
|
||||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "default",
|
"repo": "default",
|
||||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-systems",
|
"owner": "nix-systems",
|
||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"root": "root",
|
"root": "root",
|
||||||
"version": 7
|
"version": 7
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ export const NO_AUTHORIZATION_ROUTES = [
|
|||||||
"POST /auth/reset",
|
"POST /auth/reset",
|
||||||
"GET /invites/",
|
"GET /invites/",
|
||||||
// Routes with a seperate auth system
|
// Routes with a seperate auth system
|
||||||
/^(POST|HEAD) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
|
/^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth
|
||||||
// Public information endpoints
|
// Public information endpoints
|
||||||
"GET /ping",
|
"GET /ping",
|
||||||
"GET /gateway",
|
"GET /gateway",
|
||||||
|
@ -28,6 +28,8 @@ import {
|
|||||||
handleFile,
|
handleFile,
|
||||||
isTextChannel,
|
isTextChannel,
|
||||||
trimSpecial,
|
trimSpecial,
|
||||||
|
FieldErrors,
|
||||||
|
ValidateName,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
@ -111,8 +113,9 @@ router.post(
|
|||||||
name = trimSpecial(name);
|
name = trimSpecial(name);
|
||||||
|
|
||||||
// TODO: move this
|
// TODO: move this
|
||||||
if (name === "clyde") throw new HTTPError("Invalid name", 400);
|
if (name) {
|
||||||
if (name === "Spacebar Ghost") throw new HTTPError("Invalid name", 400);
|
ValidateName(name);
|
||||||
|
}
|
||||||
|
|
||||||
if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar);
|
||||||
|
|
||||||
|
74
src/api/routes/policies/instance/config.ts
Executable file
74
src/api/routes/policies/instance/config.ts
Executable file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||||
|
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { route } from "@spacebar/api";
|
||||||
|
import { Config, getRights } from "@spacebar/util";
|
||||||
|
import { Request, Response, Router } from "express";
|
||||||
|
|
||||||
|
const router = Router();
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Object",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const general = Config.get();
|
||||||
|
let outputtedConfig;
|
||||||
|
if (req.user_id) {
|
||||||
|
const rights = await getRights(req.user_id);
|
||||||
|
if (rights.has("OPERATOR")) outputtedConfig = general;
|
||||||
|
} else {
|
||||||
|
outputtedConfig = {
|
||||||
|
limits_user_maxGuilds: general.limits.user.maxGuilds,
|
||||||
|
limits_user_maxBio: general.limits.user.maxBio,
|
||||||
|
limits_guild_maxEmojis: general.limits.guild.maxEmojis,
|
||||||
|
limits_guild_maxRoles: general.limits.guild.maxRoles,
|
||||||
|
limits_message_maxCharacters:
|
||||||
|
general.limits.message.maxCharacters,
|
||||||
|
limits_message_maxAttachmentSize:
|
||||||
|
general.limits.message.maxAttachmentSize,
|
||||||
|
limits_message_maxEmbedDownloadSize:
|
||||||
|
general.limits.message.maxEmbedDownloadSize,
|
||||||
|
limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
|
||||||
|
register_dateOfBirth_requiredc:
|
||||||
|
general.register.dateOfBirth.required,
|
||||||
|
register_password_required: general.register.password.required,
|
||||||
|
register_disabled: general.register.disabled,
|
||||||
|
register_requireInvite: general.register.requireInvite,
|
||||||
|
register_allowNewRegistration:
|
||||||
|
general.register.allowNewRegistration,
|
||||||
|
register_allowMultipleAccounts:
|
||||||
|
general.register.allowMultipleAccounts,
|
||||||
|
guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
|
||||||
|
guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
|
||||||
|
register_email_required: general.register.email.required,
|
||||||
|
can_recover_account:
|
||||||
|
general.email.provider != null &&
|
||||||
|
general.general.frontPage != null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
res.send(outputtedConfig);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export default router;
|
@ -10,6 +10,10 @@ import {
|
|||||||
WebhookExecuteSchema,
|
WebhookExecuteSchema,
|
||||||
emitEvent,
|
emitEvent,
|
||||||
uploadFile,
|
uploadFile,
|
||||||
|
WebhooksUpdateEvent,
|
||||||
|
WebhookUpdateSchema,
|
||||||
|
handleFile,
|
||||||
|
ValidateName,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
@ -129,13 +133,8 @@ router.post(
|
|||||||
|
|
||||||
// block username from containing certain words
|
// block username from containing certain words
|
||||||
// TODO: configurable additions
|
// TODO: configurable additions
|
||||||
const blockedContains = ["discord", "clyde", "spacebar"];
|
if (body.username) {
|
||||||
for (const word of blockedContains) {
|
ValidateName(body.username);
|
||||||
if (body.username?.toLowerCase().includes(word)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
username: [`Username cannot contain "${word}"`],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// block username from being certain words
|
// block username from being certain words
|
||||||
@ -248,4 +247,109 @@ router.post(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { webhook_id, token } = req.params;
|
||||||
|
|
||||||
|
const webhook = await Webhook.findOne({
|
||||||
|
where: {
|
||||||
|
id: webhook_id,
|
||||||
|
},
|
||||||
|
relations: ["channel", "guild", "application"],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!webhook) {
|
||||||
|
throw DiscordApiErrors.UNKNOWN_WEBHOOK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webhook.token !== token) {
|
||||||
|
throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED;
|
||||||
|
}
|
||||||
|
const channel_id = webhook.channel_id;
|
||||||
|
await Webhook.delete({ id: webhook_id });
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "WEBHOOKS_UPDATE",
|
||||||
|
channel_id,
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
guild_id: webhook.guild_id,
|
||||||
|
},
|
||||||
|
} as WebhooksUpdateEvent);
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/",
|
||||||
|
route({
|
||||||
|
requestBody: "WebhookUpdateSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "Message",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { webhook_id, token } = req.params;
|
||||||
|
const body = req.body as WebhookUpdateSchema;
|
||||||
|
|
||||||
|
const webhook = await Webhook.findOneOrFail({
|
||||||
|
where: { id: webhook_id },
|
||||||
|
relations: [
|
||||||
|
"user",
|
||||||
|
"channel",
|
||||||
|
"source_channel",
|
||||||
|
"guild",
|
||||||
|
"source_guild",
|
||||||
|
"application",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const channel_id = webhook.channel_id;
|
||||||
|
if (!body.name && !body.avatar) {
|
||||||
|
throw new HTTPError("Empty webhook updates are not allowed", 50006);
|
||||||
|
}
|
||||||
|
if (body.avatar)
|
||||||
|
body.avatar = await handleFile(
|
||||||
|
`/avatars/${webhook_id}`,
|
||||||
|
body.avatar as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (body.name) {
|
||||||
|
ValidateName(body.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook.assign(body);
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
webhook.save(),
|
||||||
|
emitEvent({
|
||||||
|
event: "WEBHOOKS_UPDATE",
|
||||||
|
channel_id,
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
guild_id: webhook.guild_id,
|
||||||
|
},
|
||||||
|
} as WebhooksUpdateEvent),
|
||||||
|
]);
|
||||||
|
res.status(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -4,8 +4,16 @@ import {
|
|||||||
DiscordApiErrors,
|
DiscordApiErrors,
|
||||||
getPermission,
|
getPermission,
|
||||||
Webhook,
|
Webhook,
|
||||||
|
WebhooksUpdateEvent,
|
||||||
|
emitEvent,
|
||||||
|
WebhookUpdateSchema,
|
||||||
|
Channel,
|
||||||
|
handleFile,
|
||||||
|
FieldErrors,
|
||||||
|
ValidateName,
|
||||||
} from "@spacebar/util";
|
} from "@spacebar/util";
|
||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
@ -54,4 +62,139 @@ router.get(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/",
|
||||||
|
route({
|
||||||
|
responses: {
|
||||||
|
204: {},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { webhook_id } = req.params;
|
||||||
|
|
||||||
|
const webhook = await Webhook.findOneOrFail({
|
||||||
|
where: { id: webhook_id },
|
||||||
|
relations: [
|
||||||
|
"user",
|
||||||
|
"channel",
|
||||||
|
"source_channel",
|
||||||
|
"guild",
|
||||||
|
"source_guild",
|
||||||
|
"application",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (webhook.guild_id) {
|
||||||
|
const permission = await getPermission(
|
||||||
|
req.user_id,
|
||||||
|
webhook.guild_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permission.has("MANAGE_WEBHOOKS"))
|
||||||
|
throw DiscordApiErrors.UNKNOWN_WEBHOOK;
|
||||||
|
} else if (webhook.user_id != req.user_id)
|
||||||
|
throw DiscordApiErrors.UNKNOWN_WEBHOOK;
|
||||||
|
|
||||||
|
const channel_id = webhook.channel_id;
|
||||||
|
await Webhook.delete({ id: webhook_id });
|
||||||
|
|
||||||
|
await emitEvent({
|
||||||
|
event: "WEBHOOKS_UPDATE",
|
||||||
|
channel_id,
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
guild_id: webhook.guild_id,
|
||||||
|
},
|
||||||
|
} as WebhooksUpdateEvent);
|
||||||
|
|
||||||
|
res.sendStatus(204);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
router.patch(
|
||||||
|
"/",
|
||||||
|
route({
|
||||||
|
requestBody: "WebhookUpdateSchema",
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
body: "WebhookCreateResponse",
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
body: "APIErrorResponse",
|
||||||
|
},
|
||||||
|
403: {},
|
||||||
|
404: {},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
async (req: Request, res: Response) => {
|
||||||
|
const { webhook_id } = req.params;
|
||||||
|
const body = req.body as WebhookUpdateSchema;
|
||||||
|
|
||||||
|
const webhook = await Webhook.findOneOrFail({
|
||||||
|
where: { id: webhook_id },
|
||||||
|
relations: [
|
||||||
|
"user",
|
||||||
|
"channel",
|
||||||
|
"source_channel",
|
||||||
|
"guild",
|
||||||
|
"source_guild",
|
||||||
|
"application",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (webhook.guild_id) {
|
||||||
|
const permission = await getPermission(
|
||||||
|
req.user_id,
|
||||||
|
webhook.guild_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!permission.has("MANAGE_WEBHOOKS"))
|
||||||
|
throw DiscordApiErrors.UNKNOWN_WEBHOOK;
|
||||||
|
} else if (webhook.user_id != req.user_id)
|
||||||
|
throw DiscordApiErrors.UNKNOWN_WEBHOOK;
|
||||||
|
|
||||||
|
if (!body.name && !body.avatar && !body.channel_id) {
|
||||||
|
throw new HTTPError("Empty webhook updates are not allowed", 50006);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.avatar)
|
||||||
|
body.avatar = await handleFile(
|
||||||
|
`/avatars/${webhook_id}`,
|
||||||
|
body.avatar as string,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (body.name) {
|
||||||
|
ValidateName(body.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const channel_id = body.channel_id || webhook.channel_id;
|
||||||
|
webhook.assign(body);
|
||||||
|
|
||||||
|
if (body.channel_id)
|
||||||
|
webhook.assign({
|
||||||
|
channel: await Channel.findOneOrFail({
|
||||||
|
where: { id: channel_id },
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
webhook.save(),
|
||||||
|
emitEvent({
|
||||||
|
event: "WEBHOOKS_UPDATE",
|
||||||
|
channel_id,
|
||||||
|
data: {
|
||||||
|
channel_id,
|
||||||
|
guild_id: webhook.guild_id,
|
||||||
|
},
|
||||||
|
} as WebhooksUpdateEvent),
|
||||||
|
]);
|
||||||
|
|
||||||
|
res.json(webhook);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -37,6 +37,7 @@ import {
|
|||||||
SecurityConfiguration,
|
SecurityConfiguration,
|
||||||
SentryConfiguration,
|
SentryConfiguration,
|
||||||
TemplateConfiguration,
|
TemplateConfiguration,
|
||||||
|
UserConfiguration,
|
||||||
} from "../config";
|
} from "../config";
|
||||||
|
|
||||||
export class ConfigValue {
|
export class ConfigValue {
|
||||||
@ -61,4 +62,5 @@ export class ConfigValue {
|
|||||||
email: EmailConfiguration = new EmailConfiguration();
|
email: EmailConfiguration = new EmailConfiguration();
|
||||||
passwordReset: PasswordResetConfiguration =
|
passwordReset: PasswordResetConfiguration =
|
||||||
new PasswordResetConfiguration();
|
new PasswordResetConfiguration();
|
||||||
|
user: UserConfiguration = new UserConfiguration();
|
||||||
}
|
}
|
||||||
|
22
src/util/config/types/UsersConfiguration.ts
Executable file
22
src/util/config/types/UsersConfiguration.ts
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||||
|
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class UserConfiguration {
|
||||||
|
blockedContains: string[] = ["discord", "clyde", "spacebar"];
|
||||||
|
blockedEquals: string[] = ["everyone", "here"];
|
||||||
|
}
|
@ -37,3 +37,4 @@ export * from "./SecurityConfiguration";
|
|||||||
export * from "./SentryConfiguration";
|
export * from "./SentryConfiguration";
|
||||||
export * from "./subconfigurations";
|
export * from "./subconfigurations";
|
||||||
export * from "./TemplateConfiguration";
|
export * from "./TemplateConfiguration";
|
||||||
|
export * from "./UsersConfiguration";
|
||||||
|
23
src/util/schemas/WebhookUpdateSchema.ts
Executable file
23
src/util/schemas/WebhookUpdateSchema.ts
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||||
|
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface WebhookUpdateSchema {
|
||||||
|
name?: string;
|
||||||
|
avatar?: string;
|
||||||
|
channel_id?: string;
|
||||||
|
}
|
@ -86,4 +86,5 @@ export * from "./VoiceVideoSchema";
|
|||||||
export * from "./WebAuthnSchema";
|
export * from "./WebAuthnSchema";
|
||||||
export * from "./WebhookCreateSchema";
|
export * from "./WebhookCreateSchema";
|
||||||
export * from "./WebhookExecuteSchema";
|
export * from "./WebhookExecuteSchema";
|
||||||
|
export * from "./WebhookUpdateSchema";
|
||||||
export * from "./WidgetModifySchema";
|
export * from "./WidgetModifySchema";
|
||||||
|
57
src/util/util/NameValidation.ts
Executable file
57
src/util/util/NameValidation.ts
Executable file
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||||
|
Copyright (C) 2023 Spacebar and Spacebar Contributors
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Config } from "./Config";
|
||||||
|
import { FieldErrors } from "./FieldError";
|
||||||
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
|
export function ValidateName(name: string) {
|
||||||
|
const check_username = name.replace(/\s/g, "");
|
||||||
|
if (!check_username) {
|
||||||
|
throw FieldErrors({
|
||||||
|
username: {
|
||||||
|
code: "BASE_TYPE_REQUIRED",
|
||||||
|
message: "common:field.BASE_TYPE_REQUIRED",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const general = Config.get();
|
||||||
|
const { maxUsername } = general.limits.user;
|
||||||
|
if (check_username.length > maxUsername || check_username.length < 2) {
|
||||||
|
throw FieldErrors({
|
||||||
|
username: {
|
||||||
|
code: "BASE_TYPE_BAD_LENGTH",
|
||||||
|
message: `Must be between 2 and ${maxUsername} in length.`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { blockedContains, blockedEquals } = general.user;
|
||||||
|
for (const word of blockedContains) {
|
||||||
|
if (name.toLowerCase().includes(word)) {
|
||||||
|
throw new HTTPError(`Username cannot contain "${word}"`, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const word of blockedEquals) {
|
||||||
|
if (name.toLowerCase() === word) {
|
||||||
|
throw new HTTPError(`Username cannot be "${word}"`, 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
@ -43,3 +43,4 @@ export * from "./TraverseDirectory";
|
|||||||
export * from "./WebAuthn";
|
export * from "./WebAuthn";
|
||||||
export * from "./Gifs";
|
export * from "./Gifs";
|
||||||
export * from "./Application";
|
export * from "./Application";
|
||||||
|
export * from "./NameValidation";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user