From cf3873d62ff30da691dae7f7424c6365b5a72edc Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Sun, 17 Jul 2022 18:52:41 +0200 Subject: Add local disk caching for fetched assets --- api/src/middlewares/TestClient.ts | 165 ++++++++++++++++++++------------ api/src/util/entities/AssetCacheItem.ts | 16 ++++ api/src/util/index.ts | 1 + 3 files changed, 120 insertions(+), 62 deletions(-) create mode 100644 api/src/util/entities/AssetCacheItem.ts (limited to 'api/src') diff --git a/api/src/middlewares/TestClient.ts b/api/src/middlewares/TestClient.ts index ecf87681..e52a5e59 100644 --- a/api/src/middlewares/TestClient.ts +++ b/api/src/middlewares/TestClient.ts @@ -1,54 +1,47 @@ import express, { Request, Response, Application } from "express"; -import fs from "fs"; +import fs, { writeFile } from "fs"; import path from "path"; -import fetch, { Response as FetchResponse } from "node-fetch"; +import fetch, { Response as FetchResponse, Headers } from "node-fetch"; import ProxyAgent from 'proxy-agent'; import { Config } from "@fosscord/util"; +import { AssetCacheItem } from "../util/entities/AssetCacheItem" +import { FileLogger } from "typeorm"; export default function TestClient(app: Application) { const agent = new ProxyAgent(); - const assetCache = new Map(); - const indexHTML = fs.readFileSync(path.join(__dirname, "..", "..", "client_test", "index.html"), { encoding: "utf8" }); - - var html = indexHTML; - const CDN_ENDPOINT = (Config.get().cdn.endpointClient || Config.get()?.cdn.endpointPublic || process.env.CDN || "").replace( - /(https?)?(:\/\/?)/g, - "" - ); - const GATEWAY_ENDPOINT = Config.get().gateway.endpointClient || Config.get()?.gateway.endpointPublic || process.env.GATEWAY || ""; + + //build client page + let html = fs.readFileSync(path.join(__dirname, "..", "..", "client_test", "index.html"), { encoding: "utf8" }); + html = applyEnv(html); + html = applyInlinePlugins(html); + html = applyPlugins(html); + html = applyPreloadPlugins(html); - if (CDN_ENDPOINT) { - html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${CDN_ENDPOINT}\`,`); + //load asset cache + let newAssetCache: Map = new Map(); + if(!fs.existsSync(path.join(__dirname, "..", "..", "assets", "cache"))) { + fs.mkdirSync(path.join(__dirname, "..", "..", "assets", "cache")); } - if (GATEWAY_ENDPOINT) { - html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${GATEWAY_ENDPOINT}\`,`); + if(fs.existsSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json"))) { + let rawdata = fs.readFileSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json")); + newAssetCache = new Map(Object.entries(JSON.parse(rawdata.toString()))); } - // inline plugins - var files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "preload-plugins")); - var plugins = ""; - files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n`; }); - html = html.replaceAll("", plugins); - // plugins - files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "plugins")); - plugins = ""; - files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n`; }); - html = html.replaceAll("", plugins); - //preload plugins - files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "preload-plugins")); - plugins = ""; - files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n`; }); - html = html.replaceAll("", plugins); - - - app.use("/assets", express.static(path.join(__dirname, "..", "..", "assets"))); - + //define routes + app.use("/assets", express.static(path.join(__dirname, "..", "..", "assets"))); app.get("/assets/:file", async (req: Request, res: Response) => { delete req.headers.host; - var response: FetchResponse; - var buffer: Buffer; - const cache = assetCache.get(req.params.file); - if (!cache) { + let response: FetchResponse; + let buffer: Buffer; + let assetCacheItem: AssetCacheItem = new AssetCacheItem(req.params.file); + if(newAssetCache.has(req.params.file)){ + assetCacheItem = newAssetCache.get(req.params.file)!; + assetCacheItem.Headers.forEach((value: any, name: any) => { + res.set(name, value); + }); + } + else { + console.log(`CACHE MISS! Asset file: ${req.params.file}`); response = await fetch(`https://discord.com/assets/${req.params.file}`, { agent, // @ts-ignore @@ -56,34 +49,24 @@ export default function TestClient(app: Application) { ...req.headers } }); - buffer = await response.buffer(); - } else { - response = cache.response; - buffer = cache.buffer; + + //set cache info + assetCacheItem.Headers = Object.fromEntries(stripHeaders(response.headers)); + assetCacheItem.FilePath = path.join(__dirname, "..", "..", "assets", "cache", req.params.file); + assetCacheItem.Key = req.params.file; + //add to cache and save + newAssetCache.set(req.params.file, assetCacheItem); + fs.writeFileSync(path.join(__dirname, "..", "..", "assets", "cache", "index.json"), JSON.stringify(Object.fromEntries(newAssetCache), null, 4)); + //download file + fs.writeFileSync(assetCacheItem.FilePath, await response.buffer()); } - - response.headers.forEach((value, name) => { - if ( - [ - "content-length", - "content-security-policy", - "strict-transport-security", - "set-cookie", - "transfer-encoding", - "expect-ct", - "access-control-allow-origin", - "content-encoding" - ].includes(name.toLowerCase()) - ) { - return; - } + + assetCacheItem.Headers.forEach((value: string, name: string) => { res.set(name, value); }); - assetCache.set(req.params.file, { buffer, response }); - - return res.send(buffer); + return res.send(fs.readFileSync(assetCacheItem.FilePath)); }); - app.get("/developers*", (req: Request, res: Response) => { + app.get("/developers*", (_req: Request, res: Response) => { const { useTestClient } = Config.get().client; res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); res.set("content-type", "text/html"); @@ -104,4 +87,62 @@ export default function TestClient(app: Application) { res.send(html); }); + + +} + +function applyEnv(html: string): string { + const CDN_ENDPOINT = (Config.get().cdn.endpointClient || Config.get()?.cdn.endpointPublic || process.env.CDN || "").replace( + /(https?)?(:\/\/?)/g, + "" + ); + const GATEWAY_ENDPOINT = Config.get().gateway.endpointClient || Config.get()?.gateway.endpointPublic || process.env.GATEWAY || ""; + + if (CDN_ENDPOINT) { + html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${CDN_ENDPOINT}\`,`); + } + if (GATEWAY_ENDPOINT) { + html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${GATEWAY_ENDPOINT}\`,`); + } + return html; +} + +function applyPlugins(html: string): string { + // plugins + let files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "plugins")); + let plugins = ""; + files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n`; }); + return html.replaceAll("", plugins); +} + +function applyInlinePlugins(html: string): string{ + // inline plugins + let files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "inline-plugins")); + let plugins = ""; + files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n\n`; }); + return html.replaceAll("", plugins); +} + +function applyPreloadPlugins(html: string): string{ + //preload plugins + let files = fs.readdirSync(path.join(__dirname, "..", "..", "assets", "preload-plugins")); + let plugins = ""; + files.forEach(x =>{if(x.endsWith(".js")) plugins += `\n`; }); + return html.replaceAll("", plugins); +} + +function stripHeaders(headers: Headers): Headers { + [ + "content-length", + "content-security-policy", + "strict-transport-security", + "set-cookie", + "transfer-encoding", + "expect-ct", + "access-control-allow-origin", + "content-encoding" + ].forEach(headerName => { + headers.delete(headerName); + }); + return headers; } \ No newline at end of file diff --git a/api/src/util/entities/AssetCacheItem.ts b/api/src/util/entities/AssetCacheItem.ts new file mode 100644 index 00000000..7fb4e1e6 --- /dev/null +++ b/api/src/util/entities/AssetCacheItem.ts @@ -0,0 +1,16 @@ +import { Headers } from "node-fetch"; + +export class AssetCacheItem { + public Key: string; + public FilePath: string; + public Headers: any; + + constructor(key: string){ + this.Key = key; + } + /*constructor(key: string, filePath: string, headers: Headers) { + this.Key = key; + this.FilePath = filePath; + this.Headers = headers; + }*/ +} \ No newline at end of file diff --git a/api/src/util/index.ts b/api/src/util/index.ts index ffbcf24e..ac439371 100644 --- a/api/src/util/index.ts +++ b/api/src/util/index.ts @@ -6,3 +6,4 @@ export * from "./utility/RandomInviteID"; export * from "./handlers/route"; export * from "./utility/String"; export * from "./handlers/Voice"; +export * from "./entities/AssetCacheItem"; \ No newline at end of file -- cgit 1.5.1 From 1c2cddb7c4be9fa75eabf3ec9424afd6cb032a7c Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Wed, 20 Jul 2022 17:39:16 +1000 Subject: 2fa --- api/assets/schemas.json | 2597 +++++++++++++++++++------- api/locales/en/auth.json | 4 +- api/src/middlewares/Authentication.ts | 1 + api/src/routes/auth/login.ts | 17 +- api/src/routes/auth/mfa/totp.ts | 49 + api/src/routes/users/@me/mfa/codes.ts | 48 + api/src/routes/users/@me/mfa/totp/disable.ts | 45 + api/src/routes/users/@me/mfa/totp/enable.ts | 51 + util/src/entities/BackupCodes.ts | 35 + util/src/entities/User.ts | 8 +- util/src/entities/index.ts | 3 +- 11 files changed, 2212 insertions(+), 646 deletions(-) create mode 100644 api/src/routes/auth/mfa/totp.ts create mode 100644 api/src/routes/users/@me/mfa/codes.ts create mode 100644 api/src/routes/users/@me/mfa/totp/disable.ts create mode 100644 api/src/routes/users/@me/mfa/totp/enable.ts create mode 100644 util/src/entities/BackupCodes.ts (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 2102292b..4249eb81 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -1072,46 +1072,32 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "RegisterSchema": { + "TotpSchema": { "type": "object", "properties": { - "username": { - "minLength": 2, - "maxLength": 32, - "type": "string" - }, - "password": { - "minLength": 1, - "maxLength": 72, - "type": "string" - }, - "consent": { - "type": "boolean" - }, - "email": { - "format": "email", - "type": "string" - }, - "fingerprint": { - "type": "string" - }, - "invite": { + "code": { "type": "string" }, - "date_of_birth": { + "ticket": { "type": "string" }, "gift_code_sku_id": { - "type": "string" + "type": [ + "null", + "string" + ] }, - "captcha_key": { - "type": "string" + "login_source": { + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, "required": [ - "consent", - "username" + "code", + "ticket" ], "definitions": { "Embed": { @@ -1424,104 +1410,47 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "ChannelModifySchema": { + "RegisterSchema": { "type": "object", "properties": { - "name": { - "maxLength": 100, + "username": { + "minLength": 2, + "maxLength": 32, "type": "string" }, - "type": { - "enum": [ - 0, - 1, - 10, - 11, - 12, - 13, - 14, - 15, - 2, - 255, - 3, - 33, - 34, - 35, - 4, - 5, - 6, - 64, - 7, - 8, - 9 - ], - "type": "number" - }, - "topic": { + "password": { + "minLength": 1, + "maxLength": 72, "type": "string" }, - "icon": { - "type": [ - "null", - "string" - ] - }, - "bitrate": { - "type": "integer" - }, - "user_limit": { - "type": "integer" - }, - "rate_limit_per_user": { - "type": "integer" - }, - "position": { - "type": "integer" + "consent": { + "type": "boolean" }, - "permission_overwrites": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "$ref": "#/definitions/ChannelPermissionOverwriteType" - }, - "allow": { - "type": "string" - }, - "deny": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "allow", - "deny", - "id", - "type" - ] - } + "email": { + "format": "email", + "type": "string" }, - "parent_id": { + "fingerprint": { "type": "string" }, - "id": { + "invite": { "type": "string" }, - "nsfw": { - "type": "boolean" + "date_of_birth": { + "type": "string" }, - "rtc_region": { + "gift_code_sku_id": { "type": "string" }, - "default_auto_archive_duration": { - "type": "integer" + "captcha_key": { + "type": "string" } }, "additionalProperties": false, + "required": [ + "consent", + "username" + ], "definitions": { "Embed": { "type": "object", @@ -1833,34 +1762,100 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "InviteCreateSchema": { + "ChannelModifySchema": { "type": "object", "properties": { - "target_user_id": { + "name": { + "maxLength": 100, "type": "string" }, - "target_type": { - "type": "string" + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 2, + 255, + 3, + 33, + 34, + 35, + 4, + 5, + 6, + 64, + 7, + 8, + 9 + ], + "type": "number" }, - "validate": { + "topic": { "type": "string" }, - "max_age": { + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { "type": "integer" }, - "max_uses": { + "user_limit": { "type": "integer" }, - "temporary": { - "type": "boolean" + "rate_limit_per_user": { + "type": "integer" }, - "unique": { + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { "type": "boolean" }, - "target_user": { + "rtc_region": { "type": "string" }, - "target_user_type": { + "default_auto_archive_duration": { "type": "integer" } }, @@ -2176,17 +2171,1345 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "MessageAcknowledgeSchema": { + "InviteCreateSchema": { "type": "object", "properties": { - "manual": { - "type": "boolean" + "target_user_id": { + "type": "string" }, - "mention_count": { + "target_type": { + "type": "string" + }, + "validate": { + "type": "string" + }, + "max_age": { + "type": "integer" + }, + "max_uses": { "type": "integer" + }, + "temporary": { + "type": "boolean" + }, + "unique": { + "type": "boolean" + }, + "target_user": { + "type": "string" + }, + "target_user_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1, + 2 + ], + "type": "number" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 2, + 255, + 3, + 33, + 34, + 35, + 4, + 5, + 6, + 64, + 7, + 8, + 9 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "premium_since", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verified" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "MessageAcknowledgeSchema": { + "type": "object", + "properties": { + "manual": { + "type": "boolean" + }, + "mention_count": { + "type": "integer" + } + }, + "additionalProperties": false, + "definitions": { + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1, + 2 + ], + "type": "number" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 2, + 255, + 3, + 33, + 34, + 35, + 4, + 5, + 6, + 64, + 7, + 8, + 9 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "premium_since", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verified" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "BulkDeleteSchema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "messages" + ], + "definitions": { + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1, + 2 + ], + "type": "number" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 2, + 255, + 3, + 33, + 34, + 35, + 4, + 5, + 6, + 64, + 7, + 8, + 9 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "premium_since", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verified" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "ChannelPermissionOverwriteSchema": { + "type": "object", + "properties": { + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ], + "definitions": { + "Embed": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "type": { + "enum": [ + "article", + "gifv", + "image", + "link", + "rich", + "video" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "timestamp": { + "type": "string", + "format": "date-time" + }, + "color": { + "type": "integer" + }, + "footer": { + "type": "object", + "properties": { + "text": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "text" + ] + }, + "image": { + "$ref": "#/definitions/EmbedImage" + }, + "thumbnail": { + "$ref": "#/definitions/EmbedImage" + }, + "video": { + "$ref": "#/definitions/EmbedImage" + }, + "provider": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "author": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "proxy_icon_url": { + "type": "string" + } + }, + "additionalProperties": false + }, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + }, + "inline": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "value" + ] + } + } + }, + "additionalProperties": false + }, + "EmbedImage": { + "type": "object", + "properties": { + "url": { + "type": "string" + }, + "proxy_url": { + "type": "string" + }, + "height": { + "type": "integer" + }, + "width": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "Record": { + "type": "object", + "additionalProperties": false + }, + "ChannelPermissionOverwriteType": { + "enum": [ + 0, + 1, + 2 + ], + "type": "number" + }, + "ChannelModifySchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "type": { + "enum": [ + 0, + 1, + 10, + 11, + 12, + 13, + 14, + 15, + 2, + 255, + 3, + 33, + 34, + 35, + 4, + 5, + 6, + 64, + 7, + 8, + 9 + ], + "type": "number" + }, + "topic": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "bitrate": { + "type": "integer" + }, + "user_limit": { + "type": "integer" + }, + "rate_limit_per_user": { + "type": "integer" + }, + "position": { + "type": "integer" + }, + "permission_overwrites": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/ChannelPermissionOverwriteType" + }, + "allow": { + "type": "string" + }, + "deny": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "allow", + "deny", + "id", + "type" + ] + } + }, + "parent_id": { + "type": "string" + }, + "id": { + "type": "string" + }, + "nsfw": { + "type": "boolean" + }, + "rtc_region": { + "type": "string" + }, + "default_auto_archive_duration": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "UserPublic": { + "type": "object", + "properties": { + "username": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "id": { + "type": "string" + }, + "public_flags": { + "type": "integer" + }, + "avatar": { + "type": "string" + }, + "accent_color": { + "type": "integer" + }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "premium_since", + "public_flags", + "username" + ] + }, + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verified" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "PurgeSchema": { + "type": "object", + "properties": { + "before": { + "type": "string" + }, + "after": { + "type": "string" } }, "additionalProperties": false, + "required": [ + "after", + "before" + ], "definitions": { "Embed": { "type": "object", @@ -2498,19 +3821,21 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "BulkDeleteSchema": { + "WebhookCreateSchema": { "type": "object", "properties": { - "messages": { - "type": "array", - "items": { - "type": "string" - } + "name": { + "maxLength": 80, + "type": "string" + }, + "avatar": { + "type": "string" } }, "additionalProperties": false, "required": [ - "messages" + "avatar", + "name" ], "definitions": { "Embed": { @@ -2823,28 +4148,45 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "ChannelPermissionOverwriteSchema": { + "GatewayBotResponse": { "type": "object", "properties": { - "allow": { - "type": "string" - }, - "deny": { + "url": { "type": "string" }, - "id": { - "type": "string" + "shards": { + "type": "integer" }, - "type": { - "$ref": "#/definitions/ChannelPermissionOverwriteType" + "session_start_limit": { + "type": "object", + "properties": { + "total": { + "type": "integer" + }, + "remaining": { + "type": "integer" + }, + "reset_after": { + "type": "integer" + }, + "max_concurrency": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "max_concurrency", + "remaining", + "reset_after", + "total" + ] } }, "additionalProperties": false, "required": [ - "allow", - "deny", - "id", - "type" + "session_start_limit", + "shards", + "url" ], "definitions": { "Embed": { @@ -3157,20 +4499,16 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "PurgeSchema": { + "GatewayResponse": { "type": "object", "properties": { - "before": { - "type": "string" - }, - "after": { + "url": { "type": "string" } }, "additionalProperties": false, "required": [ - "after", - "before" + "url" ], "definitions": { "Embed": { @@ -3483,22 +4821,17 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "WebhookCreateSchema": { + "BanCreateSchema": { "type": "object", "properties": { - "name": { - "maxLength": 80, + "delete_message_days": { "type": "string" }, - "avatar": { + "reason": { "type": "string" } }, "additionalProperties": false, - "required": [ - "avatar", - "name" - ], "definitions": { "Embed": { "type": "object", @@ -3810,45 +5143,34 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GatewayBotResponse": { + "BanRegistrySchema": { "type": "object", "properties": { - "url": { + "id": { "type": "string" }, - "shards": { - "type": "integer" + "user_id": { + "type": "string" }, - "session_start_limit": { - "type": "object", - "properties": { - "total": { - "type": "integer" - }, - "remaining": { - "type": "integer" - }, - "reset_after": { - "type": "integer" - }, - "max_concurrency": { - "type": "integer" - } - }, - "additionalProperties": false, - "required": [ - "max_concurrency", - "remaining", - "reset_after", - "total" - ] + "guild_id": { + "type": "string" + }, + "executor_id": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "reason": { + "type": "string" } }, "additionalProperties": false, "required": [ - "session_start_limit", - "shards", - "url" + "executor_id", + "guild_id", + "id", + "user_id" ], "definitions": { "Embed": { @@ -4161,16 +5483,31 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GatewayResponse": { + "BanModeratorSchema": { "type": "object", "properties": { - "url": { + "id": { + "type": "string" + }, + "user_id": { + "type": "string" + }, + "guild_id": { + "type": "string" + }, + "executor_id": { + "type": "string" + }, + "reason": { "type": "string" } }, "additionalProperties": false, "required": [ - "url" + "executor_id", + "guild_id", + "id", + "user_id" ], "definitions": { "Embed": { @@ -4483,17 +5820,29 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "BanCreateSchema": { - "type": "object", - "properties": { - "delete_message_days": { - "type": "string" + "ChannelReorderSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + }, + "lock_permissions": { + "type": "boolean" + }, + "parent_id": { + "type": "string" + } }, - "reason": { - "type": "string" - } + "additionalProperties": false, + "required": [ + "id" + ] }, - "additionalProperties": false, "definitions": { "Embed": { "type": "object", @@ -4805,34 +6154,31 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "BanRegistrySchema": { + "EmojiCreateSchema": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "user_id": { - "type": "string" - }, - "guild_id": { + "name": { "type": "string" }, - "executor_id": { + "image": { "type": "string" }, - "ip": { - "type": "string" + "require_colons": { + "type": [ + "null", + "boolean" + ] }, - "reason": { - "type": "string" + "roles": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false, "required": [ - "executor_id", - "guild_id", - "id", - "user_id" + "image" ], "definitions": { "Embed": { @@ -5145,32 +6491,20 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "BanModeratorSchema": { + "EmojiModifySchema": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "user_id": { - "type": "string" - }, - "guild_id": { - "type": "string" - }, - "executor_id": { + "name": { "type": "string" }, - "reason": { - "type": "string" + "roles": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false, - "required": [ - "executor_id", - "guild_id", - "id", - "user_id" - ], "definitions": { "Embed": { "type": "object", @@ -5478,33 +6812,46 @@ "type", "verified" ] - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "ChannelReorderSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - }, - "lock_permissions": { - "type": "boolean" - }, - "parent_id": { - "type": "string" + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "GuildCreateSchema": { + "type": "object", + "properties": { + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "channels": { + "type": "array", + "items": { + "$ref": "#/definitions/ChannelModifySchema" } }, - "additionalProperties": false, - "required": [ - "id" - ] + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" + } }, + "additionalProperties": false, + "required": [ + "name" + ], "definitions": { "Embed": { "type": "object", @@ -5816,31 +7163,80 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "EmojiCreateSchema": { + "GuildUpdateSchema": { "type": "object", "properties": { - "name": { - "type": "string" - }, - "image": { - "type": "string" + "banner": { + "type": [ + "null", + "string" + ] }, - "require_colons": { + "splash": { "type": [ "null", - "boolean" + "string" ] }, - "roles": { + "description": { + "type": "string" + }, + "features": { "type": "array", "items": { "type": "string" } + }, + "verification_level": { + "type": "integer" + }, + "default_message_notifications": { + "type": "integer" + }, + "system_channel_flags": { + "type": "integer" + }, + "explicit_content_filter": { + "type": "integer" + }, + "public_updates_channel_id": { + "type": "string" + }, + "afk_timeout": { + "type": "integer" + }, + "afk_channel_id": { + "type": "string" + }, + "preferred_locale": { + "type": "string" + }, + "name": { + "maxLength": 100, + "type": "string" + }, + "region": { + "type": "string" + }, + "icon": { + "type": [ + "null", + "string" + ] + }, + "guild_template_code": { + "type": "string" + }, + "system_channel_id": { + "type": "string" + }, + "rules_channel_id": { + "type": "string" } }, "additionalProperties": false, "required": [ - "image" + "name" ], "definitions": { "Embed": { @@ -6153,12 +7549,9 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "EmojiModifySchema": { + "MemberChangeSchema": { "type": "object", "properties": { - "name": { - "type": "string" - }, "roles": { "type": "array", "items": { @@ -6478,41 +7871,16 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GuildCreateSchema": { + "MemberNickChangeSchema": { "type": "object", "properties": { - "name": { - "maxLength": 100, - "type": "string" - }, - "region": { - "type": "string" - }, - "icon": { - "type": [ - "null", - "string" - ] - }, - "channels": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelModifySchema" - } - }, - "guild_template_code": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "rules_channel_id": { + "nick": { "type": "string" } }, "additionalProperties": false, "required": [ - "name" + "nick" ], "definitions": { "Embed": { @@ -6779,126 +8147,62 @@ }, "banner": { "type": "string" - }, - "bio": { - "type": "string" - }, - "bot": { - "type": "boolean" - }, - "premium_since": { - "type": "string", - "format": "date-time" - } - }, - "additionalProperties": false, - "required": [ - "bio", - "bot", - "discriminator", - "id", - "premium_since", - "public_flags", - "username" - ] - }, - "PublicConnectedAccount": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "verified": { - "type": "boolean" - } - }, - "additionalProperties": false, - "required": [ - "name", - "type", - "verified" - ] - } - }, - "$schema": "http://json-schema.org/draft-07/schema#" - }, - "GuildUpdateSchema": { - "type": "object", - "properties": { - "banner": { - "type": [ - "null", - "string" - ] - }, - "splash": { - "type": [ - "null", - "string" - ] - }, - "description": { - "type": "string" - }, - "features": { - "type": "array", - "items": { - "type": "string" - } - }, - "verification_level": { - "type": "integer" - }, - "default_message_notifications": { - "type": "integer" - }, - "system_channel_flags": { - "type": "integer" - }, - "explicit_content_filter": { - "type": "integer" - }, - "public_updates_channel_id": { - "type": "string" - }, - "afk_timeout": { - "type": "integer" - }, - "afk_channel_id": { - "type": "string" - }, - "preferred_locale": { - "type": "string" - }, - "name": { - "maxLength": 100, - "type": "string" - }, - "region": { - "type": "string" - }, - "icon": { - "type": [ - "null", - "string" + }, + "bio": { + "type": "string" + }, + "bot": { + "type": "boolean" + }, + "premium_since": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": false, + "required": [ + "bio", + "bot", + "discriminator", + "id", + "premium_since", + "public_flags", + "username" ] }, - "guild_template_code": { - "type": "string" - }, - "system_channel_id": { - "type": "string" - }, - "rules_channel_id": { - "type": "string" + "PublicConnectedAccount": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "verified": { + "type": "boolean" + } + }, + "additionalProperties": false, + "required": [ + "name", + "type", + "verified" + ] + } + }, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "PruneSchema": { + "type": "object", + "properties": { + "days": { + "type": "integer" } }, "additionalProperties": false, "required": [ - "name" + "days" ], "definitions": { "Embed": { @@ -7211,14 +8515,32 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "MemberChangeSchema": { + "RoleModifySchema": { "type": "object", "properties": { - "roles": { - "type": "array", - "items": { - "type": "string" - } + "name": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "color": { + "type": "integer" + }, + "hoist": { + "type": "boolean" + }, + "mentionable": { + "type": "boolean" + }, + "position": { + "type": "integer" + }, + "icon": { + "type": "string" + }, + "unicode_emoji": { + "type": "string" } }, "additionalProperties": false, @@ -7533,17 +8855,24 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "MemberNickChangeSchema": { - "type": "object", - "properties": { - "nick": { - "type": "string" - } + "RolePositionUpdateSchema": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "position": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "id", + "position" + ] }, - "additionalProperties": false, - "required": [ - "nick" - ], "definitions": { "Embed": { "type": "object", @@ -7855,16 +9184,27 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "PruneSchema": { + "ModifyGuildStickerSchema": { "type": "object", "properties": { - "days": { - "type": "integer" + "name": { + "minLength": 2, + "maxLength": 30, + "type": "string" + }, + "description": { + "maxLength": 100, + "type": "string" + }, + "tags": { + "maxLength": 200, + "type": "string" } }, "additionalProperties": false, "required": [ - "days" + "name", + "tags" ], "definitions": { "Embed": { @@ -8177,35 +9517,20 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "RoleModifySchema": { + "TemplateCreateSchema": { "type": "object", "properties": { "name": { "type": "string" }, - "permissions": { - "type": "string" - }, - "color": { - "type": "integer" - }, - "hoist": { - "type": "boolean" - }, - "mentionable": { - "type": "boolean" - }, - "position": { - "type": "integer" - }, - "icon": { - "type": "string" - }, - "unicode_emoji": { + "description": { "type": "string" } }, "additionalProperties": false, + "required": [ + "name" + ], "definitions": { "Embed": { "type": "object", @@ -8517,24 +9842,20 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "RolePositionUpdateSchema": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "position": { - "type": "integer" - } + "TemplateModifySchema": { + "type": "object", + "properties": { + "name": { + "type": "string" }, - "additionalProperties": false, - "required": [ - "id", - "position" - ] + "description": { + "type": "string" + } }, + "additionalProperties": false, + "required": [ + "name" + ], "definitions": { "Embed": { "type": "object", @@ -8846,28 +10167,16 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "ModifyGuildStickerSchema": { + "VanityUrlSchema": { "type": "object", "properties": { - "name": { - "minLength": 2, - "maxLength": 30, - "type": "string" - }, - "description": { - "maxLength": 100, - "type": "string" - }, - "tags": { - "maxLength": 200, + "code": { + "minLength": 1, + "maxLength": 20, "type": "string" } }, "additionalProperties": false, - "required": [ - "name", - "tags" - ], "definitions": { "Embed": { "type": "object", @@ -9179,19 +10488,35 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "TemplateCreateSchema": { + "VoiceStateUpdateSchema": { "type": "object", "properties": { - "name": { + "channel_id": { "type": "string" }, - "description": { + "guild_id": { "type": "string" + }, + "suppress": { + "type": "boolean" + }, + "request_to_speak_timestamp": { + "type": "string", + "format": "date-time" + }, + "self_mute": { + "type": "boolean" + }, + "self_deaf": { + "type": "boolean" + }, + "self_video": { + "type": "boolean" } }, "additionalProperties": false, "required": [ - "name" + "channel_id" ], "definitions": { "Embed": { @@ -9504,20 +10829,43 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "TemplateModifySchema": { + "GuildUpdateWelcomeScreenSchema": { "type": "object", "properties": { - "name": { - "type": "string" + "welcome_channels": { + "type": "array", + "items": { + "type": "object", + "properties": { + "channel_id": { + "type": "string" + }, + "description": { + "type": "string" + }, + "emoji_id": { + "type": "string" + }, + "emoji_name": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "channel_id", + "description", + "emoji_name" + ] + } + }, + "enabled": { + "type": "boolean" }, "description": { "type": "string" } }, "additionalProperties": false, - "required": [ - "name" - ], "definitions": { "Embed": { "type": "object", @@ -9829,16 +11177,21 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "VanityUrlSchema": { + "WidgetModifySchema": { "type": "object", "properties": { - "code": { - "minLength": 1, - "maxLength": 20, + "enabled": { + "type": "boolean" + }, + "channel_id": { "type": "string" } }, "additionalProperties": false, + "required": [ + "channel_id", + "enabled" + ], "definitions": { "Embed": { "type": "object", @@ -10150,35 +11503,22 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "VoiceStateUpdateSchema": { + "GuildTemplateCreateSchema": { "type": "object", "properties": { - "channel_id": { - "type": "string" - }, - "guild_id": { + "name": { "type": "string" }, - "suppress": { - "type": "boolean" - }, - "request_to_speak_timestamp": { - "type": "string", - "format": "date-time" - }, - "self_mute": { - "type": "boolean" - }, - "self_deaf": { - "type": "boolean" - }, - "self_video": { - "type": "boolean" + "avatar": { + "type": [ + "null", + "string" + ] } }, "additionalProperties": false, "required": [ - "channel_id" + "name" ], "definitions": { "Embed": { @@ -10491,43 +11831,29 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GuildUpdateWelcomeScreenSchema": { + "UserProfileResponse": { "type": "object", "properties": { - "welcome_channels": { - "type": "array", - "items": { - "type": "object", - "properties": { - "channel_id": { - "type": "string" - }, - "description": { - "type": "string" - }, - "emoji_id": { - "type": "string" - }, - "emoji_name": { - "type": "string" - } - }, - "additionalProperties": false, - "required": [ - "channel_id", - "description", - "emoji_name" - ] - } + "user": { + "$ref": "#/definitions/UserPublic" }, - "enabled": { - "type": "boolean" + "connected_accounts": { + "$ref": "#/definitions/PublicConnectedAccount" }, - "description": { - "type": "string" + "premium_guild_since": { + "type": "string", + "format": "date-time" + }, + "premium_since": { + "type": "string", + "format": "date-time" } }, "additionalProperties": false, + "required": [ + "connected_accounts", + "user" + ], "definitions": { "Embed": { "type": "object", @@ -10839,20 +12165,34 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "WidgetModifySchema": { + "UserRelationsResponse": { "type": "object", "properties": { - "enabled": { - "type": "boolean" - }, - "channel_id": { - "type": "string" + "object": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "avatar": { + "type": "string" + }, + "discriminator": { + "type": "string" + }, + "public_flags": { + "type": "integer" + } + }, + "additionalProperties": false } }, "additionalProperties": false, "required": [ - "channel_id", - "enabled" + "object" ], "definitions": { "Embed": { @@ -11165,22 +12505,22 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "GuildTemplateCreateSchema": { + "DmChannelCreateSchema": { "type": "object", "properties": { "name": { "type": "string" }, - "avatar": { - "type": [ - "null", - "string" - ] + "recipients": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false, "required": [ - "name" + "recipients" ], "definitions": { "Embed": { @@ -11493,29 +12833,44 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "UserProfileResponse": { + "UserModifySchema": { "type": "object", "properties": { - "user": { - "$ref": "#/definitions/UserPublic" + "username": { + "minLength": 1, + "maxLength": 100, + "type": "string" }, - "connected_accounts": { - "$ref": "#/definitions/PublicConnectedAccount" + "avatar": { + "type": [ + "null", + "string" + ] }, - "premium_guild_since": { - "type": "string", - "format": "date-time" + "bio": { + "maxLength": 1024, + "type": "string" }, - "premium_since": { - "type": "string", - "format": "date-time" + "accent_color": { + "type": "integer" + }, + "banner": { + "type": [ + "null", + "string" + ] + }, + "password": { + "type": "string" + }, + "new_password": { + "type": "string" + }, + "code": { + "type": "string" } }, "additionalProperties": false, - "required": [ - "connected_accounts", - "user" - ], "definitions": { "Embed": { "type": "object", @@ -11827,34 +13182,19 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "UserRelationsResponse": { + "MfaCodesSchema": { "type": "object", "properties": { - "object": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "username": { - "type": "string" - }, - "avatar": { - "type": "string" - }, - "discriminator": { - "type": "string" - }, - "public_flags": { - "type": "integer" - } - }, - "additionalProperties": false + "password": { + "type": "string" + }, + "regenerate": { + "type": "boolean" } }, "additionalProperties": false, "required": [ - "object" + "password" ], "definitions": { "Embed": { @@ -12167,22 +13507,16 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "DmChannelCreateSchema": { + "TotpDisableSchema": { "type": "object", "properties": { - "name": { + "code": { "type": "string" - }, - "recipients": { - "type": "array", - "items": { - "type": "string" - } } }, "additionalProperties": false, "required": [ - "recipients" + "code" ], "definitions": { "Embed": { @@ -12495,44 +13829,23 @@ }, "$schema": "http://json-schema.org/draft-07/schema#" }, - "UserModifySchema": { + "TotpEnableSchema": { "type": "object", "properties": { - "username": { - "minLength": 1, - "maxLength": 100, - "type": "string" - }, - "avatar": { - "type": [ - "null", - "string" - ] - }, - "bio": { - "maxLength": 1024, - "type": "string" - }, - "accent_color": { - "type": "integer" - }, - "banner": { - "type": [ - "null", - "string" - ] - }, "password": { "type": "string" }, - "new_password": { + "code": { "type": "string" }, - "code": { + "secret": { "type": "string" } }, "additionalProperties": false, + "required": [ + "password" + ], "definitions": { "Embed": { "type": "object", diff --git a/api/locales/en/auth.json b/api/locales/en/auth.json index e19547a0..a78d4d60 100644 --- a/api/locales/en/auth.json +++ b/api/locales/en/auth.json @@ -2,7 +2,9 @@ "login": { "INVALID_LOGIN": "E-Mail or Phone not found", "INVALID_PASSWORD": "Invalid Password", - "ACCOUNT_DISABLED": "This account is disabled" + "ACCOUNT_DISABLED": "This account is disabled", + "INVALID_TOTP_CODE": "Invalid two-factor code.", + "INVALID_TOTP_SECRET": "Invalid two-factor secret." }, "register": { "REGISTRATION_DISABLED": "New user registration is disabled", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index 5a08caf3..1df7911b 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -7,6 +7,7 @@ export const NO_AUTHORIZATION_ROUTES = [ "/auth/login", "/auth/register", "/auth/location-metadata", + "/auth/mfa/totp", // Routes with a seperate auth system "/webhooks/", // Public information endpoints diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index a89721ea..5df9e252 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -2,6 +2,7 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { Config, User, generateToken, adjustEmail, FieldErrors } from "@fosscord/util"; +import crypto from "crypto"; const router: Router = Router(); export default router; @@ -37,7 +38,7 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo const user = await User.findOneOrFail({ where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "settings"] + select: ["data", "id", "disabled", "deleted", "settings", "totp_secret", "mfa_enabled"] }).catch((e) => { throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); }); @@ -57,6 +58,20 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); } + if (user.mfa_enabled) { + // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy + const ticket = crypto.randomBytes(40).toString("hex"); + + await User.update({ id: user.id }, { totp_last_ticket: ticket }); + + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null, + }) + } + const token = await generateToken(user.id); // Notice this will have a different token structure, than discord diff --git a/api/src/routes/auth/mfa/totp.ts b/api/src/routes/auth/mfa/totp.ts new file mode 100644 index 00000000..cec6e5ee --- /dev/null +++ b/api/src/routes/auth/mfa/totp.ts @@ -0,0 +1,49 @@ +import { Router, Request, Response } from "express"; +import { route } from "@fosscord/api"; +import { BackupCode, FieldErrors, generateToken, User } from "@fosscord/util"; +import { verifyToken } from "node-2fa"; +import { HTTPError } from "lambert-server"; +const router = Router(); + +export interface TotpSchema { + code: string, + ticket: string, + gift_code_sku_id?: string | null, + login_source?: string | null, +} + +router.post("/", route({ body: "TotpSchema" }), async (req: Request, res: Response) => { + const { code, ticket, gift_code_sku_id, login_source } = req.body as TotpSchema; + + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket, + }, + select: [ + "id", + "totp_secret", + "settings", + ], + }); + + const backup = await BackupCode.findOne({ code: code, expired: false, consumed: false, user: { id: user.id }}); + + if (!backup) { + const ret = verifyToken(user.totp_secret!, code); + if (!ret || ret.delta != 0) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } + else { + backup.consumed = true; + await backup.save(); + } + + await User.update({ id: user.id }, { totp_last_ticket: "" }); + + return res.json({ + token: await generateToken(user.id), + user_settings: user.settings, + }); +}); + +export default router; diff --git a/api/src/routes/users/@me/mfa/codes.ts b/api/src/routes/users/@me/mfa/codes.ts new file mode 100644 index 00000000..2a1fb498 --- /dev/null +++ b/api/src/routes/users/@me/mfa/codes.ts @@ -0,0 +1,48 @@ +import { Router, Request, Response } from "express"; +import { route } from "@fosscord/api"; +import { BackupCode, FieldErrors, generateMfaBackupCodes, User } from "@fosscord/util"; +import bcrypt from "bcrypt"; + +const router = Router(); + +export interface MfaCodesSchema { + password: string; + regenerate?: boolean; +} + +// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients + +router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => { + const { password, regenerate } = req.body as MfaCodesSchema; + + const user = await User.findOneOrFail({ id: req.user_id }, { select: ["data"] }); + + if (!await bcrypt.compare(password, user.data.hash || "")) { + throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); + } + + var codes: BackupCode[]; + if (regenerate) { + await BackupCode.update( + { user: { id: req.user_id } }, + { expired: true } + ); + + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map(x => x.save())); + } + else { + codes = await BackupCode.find({ + user: { + id: req.user_id, + }, + expired: false, + }); + } + + return res.json({ + backup_codes: codes.map(x => ({ ...x, expired: undefined })), + }) +}); + +export default router; diff --git a/api/src/routes/users/@me/mfa/totp/disable.ts b/api/src/routes/users/@me/mfa/totp/disable.ts new file mode 100644 index 00000000..5e039ea3 --- /dev/null +++ b/api/src/routes/users/@me/mfa/totp/disable.ts @@ -0,0 +1,45 @@ +import { Router, Request, Response } from "express"; +import { route } from "@fosscord/api"; +import { verifyToken } from 'node-2fa'; +import { HTTPError } from "lambert-server"; +import { User, generateToken, BackupCode } from "@fosscord/util"; + +const router = Router(); + +export interface TotpDisableSchema { + code: string; +} + +router.post("/", route({ body: "TotpDisableSchema" }), async (req: Request, res: Response) => { + const body = req.body as TotpDisableSchema; + + const user = await User.findOneOrFail({ id: req.user_id }, { select: ["totp_secret"] }); + + const backup = await BackupCode.findOne({ code: body.code }); + if (!backup) { + const ret = verifyToken(user.totp_secret!, body.code); + if (!ret || ret.delta != 0) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } + + await User.update( + { id: req.user_id }, + { + mfa_enabled: false, + totp_secret: "", + }, + ); + + await BackupCode.update( + { user: { id: req.user_id } }, + { + expired: true, + } + ); + + return res.json({ + token: await generateToken(user.id), + }); +}); + +export default router; \ No newline at end of file diff --git a/api/src/routes/users/@me/mfa/totp/enable.ts b/api/src/routes/users/@me/mfa/totp/enable.ts new file mode 100644 index 00000000..bc5f16ad --- /dev/null +++ b/api/src/routes/users/@me/mfa/totp/enable.ts @@ -0,0 +1,51 @@ +import { Router, Request, Response } from "express"; +import { User, generateToken, BackupCode, generateMfaBackupCodes } from "@fosscord/util"; +import { route } from "@fosscord/api"; +import bcrypt from "bcrypt"; +import { HTTPError } from "lambert-server"; +import { verifyToken } from 'node-2fa'; +import crypto from "crypto"; + +const router = Router(); + +export interface TotpEnableSchema { + password: string; + code?: string; + secret?: string; +} + +router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => { + const body = req.body as TotpEnableSchema; + + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); + + // TODO: Are guests allowed to enable 2fa? + if (user.data.hash) { + if (!await bcrypt.compare(body.password, user.data.hash)) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } + + if (!body.secret) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); + + if (!body.code) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + if (verifyToken(body.secret, body.code)?.delta != 0) + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + let backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map(x => x.save())); + await User.update( + { id: req.user_id }, + { mfa_enabled: true, totp_secret: body.secret } + ); + + res.send({ + token: await generateToken(user.id), + backup_codes: backup_codes.map(x => ({ ...x, expired: undefined })), + }); +}); + +export default router; \ No newline at end of file diff --git a/util/src/entities/BackupCodes.ts b/util/src/entities/BackupCodes.ts new file mode 100644 index 00000000..d532a39a --- /dev/null +++ b/util/src/entities/BackupCodes.ts @@ -0,0 +1,35 @@ +import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; +import crypto from "crypto"; + +@Entity("backup_codes") +export class BackupCode extends BaseClass { + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + user: User; + + @Column() + code: string; + + @Column() + consumed: boolean; + + @Column() + expired: boolean; +} + +export function generateMfaBackupCodes(user_id: string) { + let backup_codes: BackupCode[] = []; + for (let i = 0; i < 10; i++) { + const code = BackupCode.create({ + user: { id: user_id }, + code: crypto.randomBytes(4).toString("hex"), // 8 characters + consumed: false, + expired: false, + }); + backup_codes.push(code); + } + + return backup_codes; +} \ No newline at end of file diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 9b1c494e..79d415ca 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -1,4 +1,4 @@ -import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm"; +import { Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm"; import { BaseClass } from "./BaseClass"; import { BitField } from "../util/BitField"; import { Relationship } from "./Relationship"; @@ -108,6 +108,12 @@ export class User extends BaseClass { @Column({ select: false }) mfa_enabled: boolean; // if multi factor authentication is enabled + @Column({ select: false, nullable: true }) + totp_secret?: string; + + @Column({ nullable: true, select: false }) + totp_last_ticket?: string; + @Column() created_at: Date; // registration date diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts index f023d5a6..1b6259ae 100644 --- a/util/src/entities/index.ts +++ b/util/src/entities/index.ts @@ -27,4 +27,5 @@ export * from "./Template"; export * from "./User"; export * from "./VoiceState"; export * from "./Webhook"; -export * from "./ClientRelease"; \ No newline at end of file +export * from "./ClientRelease"; +export * from "./BackupCodes"; \ No newline at end of file -- cgit 1.5.1 From eb7f2c7b72f545b99949e4290bc38cb448903141 Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Wed, 20 Jul 2022 22:04:19 +1000 Subject: Add config `security_twoFactor_generateBackupCodes` to control backup code generation --- api/src/routes/users/@me/mfa/codes.ts | 4 ++-- api/src/routes/users/@me/mfa/totp/enable.ts | 11 +++++++---- util/src/entities/Config.ts | 6 ++++++ 3 files changed, 15 insertions(+), 6 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/mfa/codes.ts b/api/src/routes/users/@me/mfa/codes.ts index 2a1fb498..6ddf32f0 100644 --- a/api/src/routes/users/@me/mfa/codes.ts +++ b/api/src/routes/users/@me/mfa/codes.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { BackupCode, FieldErrors, generateMfaBackupCodes, User } from "@fosscord/util"; +import { BackupCode, Config, FieldErrors, generateMfaBackupCodes, User } from "@fosscord/util"; import bcrypt from "bcrypt"; const router = Router(); @@ -22,7 +22,7 @@ router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Re } var codes: BackupCode[]; - if (regenerate) { + if (regenerate && Config.get().security.twoFactor.generateBackupCodes) { await BackupCode.update( { user: { id: req.user_id } }, { expired: true } diff --git a/api/src/routes/users/@me/mfa/totp/enable.ts b/api/src/routes/users/@me/mfa/totp/enable.ts index bc5f16ad..87f36d55 100644 --- a/api/src/routes/users/@me/mfa/totp/enable.ts +++ b/api/src/routes/users/@me/mfa/totp/enable.ts @@ -1,10 +1,9 @@ import { Router, Request, Response } from "express"; -import { User, generateToken, BackupCode, generateMfaBackupCodes } from "@fosscord/util"; +import { User, generateToken, BackupCode, generateMfaBackupCodes, Config } from "@fosscord/util"; import { route } from "@fosscord/api"; import bcrypt from "bcrypt"; import { HTTPError } from "lambert-server"; import { verifyToken } from 'node-2fa'; -import crypto from "crypto"; const router = Router(); @@ -35,8 +34,12 @@ router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - let backup_codes = generateMfaBackupCodes(req.user_id); - await Promise.all(backup_codes.map(x => x.save())); + let backup_codes: BackupCode[] = []; + if (Config.get().security.twoFactor.generateBackupCodes) { + backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map(x => x.save())); + } + await User.update( { id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret } diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index 3756d686..c84ea4aa 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -121,6 +121,9 @@ export interface ConfigValue { secret: string | null; }; ipdataApiKey: string | null; + twoFactor: { + generateBackupCodes: boolean; + }; }; login: { requireCaptcha: boolean; @@ -312,6 +315,9 @@ export const DefaultConfigOptions: ConfigValue = { secret: null, }, ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9", + twoFactor: { + generateBackupCodes: true, + }, }, login: { requireCaptcha: false, -- cgit 1.5.1 From c73dbe9c5bffea1960cd1a113e62e6c69def386c Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Sat, 23 Jul 2022 20:52:48 +1000 Subject: Moved user notes into separate table --- api/src/routes/users/@me/notes.ts | 39 ++++++++++++++++++++++++++++----------- util/src/entities/Note.ts | 18 ++++++++++++++++++ util/src/entities/User.ts | 4 +--- util/src/entities/index.ts | 3 ++- 4 files changed, 49 insertions(+), 15 deletions(-) create mode 100644 util/src/entities/Note.ts (limited to 'api/src') diff --git a/api/src/routes/users/@me/notes.ts b/api/src/routes/users/@me/notes.ts index 4887b191..e3406d18 100644 --- a/api/src/routes/users/@me/notes.ts +++ b/api/src/routes/users/@me/notes.ts @@ -1,37 +1,54 @@ import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; -import { User, emitEvent } from "@fosscord/util"; +import { User, Note, emitEvent, Snowflake } from "@fosscord/util"; const router: Router = Router(); router.get("/:id", route({}), async (req: Request, res: Response) => { const { id } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["notes"] }); - const note = user.notes[id]; + const note = await Note.findOneOrFail({ + where: { + owner: { id: req.user_id }, + target: { id: id }, + } + }); + return res.json({ - note: note, + note: note?.content, note_user_id: id, - user_id: user.id, + user_id: req.user_id, }); }); router.put("/:id", route({}), async (req: Request, res: Response) => { const { id } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const noteUser = await User.findOneOrFail({ where: { id: id }}); //if noted user does not exist throw + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw const { note } = req.body; - await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } }); + // await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } }); + + if (await Note.findOne({ owner: { id: owner.id }, target: { id: target.id } })) { + Note.update( + { owner: { id: owner.id }, target: { id: target.id } }, + { owner, target, content: note } + ); + } + else { + Note.insert( + { id: Snowflake.generate(), owner, target, content: note } + ); + } await emitEvent({ event: "USER_NOTE_UPDATE", data: { note: note, - id: noteUser.id + id: target.id }, - user_id: user.id, - }) + user_id: owner.id, + }); return res.status(204); }); diff --git a/util/src/entities/Note.ts b/util/src/entities/Note.ts new file mode 100644 index 00000000..36017c5e --- /dev/null +++ b/util/src/entities/Note.ts @@ -0,0 +1,18 @@ +import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; + +@Entity("notes") +@Unique(["owner", "target"]) +export class Note extends BaseClass { + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + owner: User; + + @JoinColumn({ name: "target_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + target: User; + + @Column() + content: string; +} \ No newline at end of file diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts index 9b1c494e..8deb7ed5 100644 --- a/util/src/entities/User.ts +++ b/util/src/entities/User.ts @@ -5,6 +5,7 @@ import { Relationship } from "./Relationship"; import { ConnectedAccount } from "./ConnectedAccount"; import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; import { Member, Session } from "."; +import { Note } from "./Note"; export enum PublicUserEnum { username, @@ -168,9 +169,6 @@ export class User extends BaseClass { @Column({ type: "simple-json", select: false }) extended_settings: string; - @Column({ type: "simple-json" }) - notes: { [key: string]: string }; //key is ID of user - toPublicUser() { const user: any = {}; PublicUserProjection.forEach((x) => { diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts index f023d5a6..cb087136 100644 --- a/util/src/entities/index.ts +++ b/util/src/entities/index.ts @@ -27,4 +27,5 @@ export * from "./Template"; export * from "./User"; export * from "./VoiceState"; export * from "./Webhook"; -export * from "./ClientRelease"; \ No newline at end of file +export * from "./ClientRelease"; +export * from "./Note"; \ No newline at end of file -- cgit 1.5.1 From 78acb2a013d109e6a5179f8a9fe28f23819c5f3d Mon Sep 17 00:00:00 2001 From: Madeline <46743919+MaddyUnderStars@users.noreply.github.com> Date: Sat, 23 Jul 2022 21:06:31 +1000 Subject: Delete Note if no content --- api/src/routes/users/@me/notes.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/notes.ts b/api/src/routes/users/@me/notes.ts index e3406d18..3c503942 100644 --- a/api/src/routes/users/@me/notes.ts +++ b/api/src/routes/users/@me/notes.ts @@ -27,18 +27,22 @@ router.put("/:id", route({}), async (req: Request, res: Response) => { const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw const { note } = req.body; - // await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } }); - - if (await Note.findOne({ owner: { id: owner.id }, target: { id: target.id } })) { - Note.update( - { owner: { id: owner.id }, target: { id: target.id } }, - { owner, target, content: note } - ); + if (note && note.length) { + // upsert a note + if (await Note.findOne({ owner: { id: owner.id }, target: { id: target.id } })) { + Note.update( + { owner: { id: owner.id }, target: { id: target.id } }, + { owner, target, content: note } + ); + } + else { + Note.insert( + { id: Snowflake.generate(), owner, target, content: note } + ); + } } else { - Note.insert( - { id: Snowflake.generate(), owner, target, content: note } - ); + await Note.delete({ owner: { id: owner.id }, target: { id: target.id } }); } await emitEvent({ -- cgit 1.5.1 From 9ab01e040495b60d4f29217191d3ae9ee774608c Mon Sep 17 00:00:00 2001 From: Puyodead1 <23562356riley@gmail.com> Date: Mon, 25 Jul 2022 23:02:04 -0400 Subject: fix: not being able to enable community --- api/assets/schemas.json | 10 +++------- api/src/routes/guilds/#guild_id/index.ts | 3 ++- 2 files changed, 5 insertions(+), 8 deletions(-) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index 2102292b..ca42e676 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -6828,6 +6828,9 @@ "GuildUpdateSchema": { "type": "object", "properties": { + "name": { + "type": "string" + }, "banner": { "type": [ "null", @@ -6873,10 +6876,6 @@ "preferred_locale": { "type": "string" }, - "name": { - "maxLength": 100, - "type": "string" - }, "region": { "type": "string" }, @@ -6897,9 +6896,6 @@ } }, "additionalProperties": false, - "required": [ - "name" - ], "definitions": { "Embed": { "type": "object", diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 4ec3df72..be556fb2 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -7,7 +7,8 @@ import { GuildCreateSchema } from "../index"; const router = Router(); -export interface GuildUpdateSchema extends Omit { +export interface GuildUpdateSchema extends Omit { + name?: string; banner?: string | null; splash?: string | null; description?: string; -- cgit 1.5.1 From f3b1e9b02641376177c4c358074e01f201ba0ff8 Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Sat, 30 Jul 2022 14:33:01 -0400 Subject: add discriminator to user modify schema --- api/assets/schemas.json | 5 +++++ api/src/routes/users/@me/index.ts | 1 + 2 files changed, 6 insertions(+) (limited to 'api/src') diff --git a/api/assets/schemas.json b/api/assets/schemas.json index ca42e676..f7efb888 100644 --- a/api/assets/schemas.json +++ b/api/assets/schemas.json @@ -12499,6 +12499,11 @@ "maxLength": 100, "type": "string" }, + "discriminator": { + "minLength": 4, + "maxLength": 4, + "type": "string" + }, "avatar": { "type": [ "null", diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 1af413c4..7fc20457 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -11,6 +11,7 @@ export interface UserModifySchema { * @maxLength 100 */ username?: string; + discriminator?: string; avatar?: string | null; /** * @maxLength 1024 -- cgit 1.5.1 From 6e9bd98711f488dd06d2ce93027f8d9ea4cd3ad5 Mon Sep 17 00:00:00 2001 From: TheArcaneBrony Date: Sun, 31 Jul 2022 13:27:15 +0200 Subject: Make requested changes (pr792) --- .gitignore | 2 ++ api/src/middlewares/TestClient.ts | 1 - api/src/util/entities/AssetCacheItem.ts | 15 +-------------- fosscord-server.code-workspace | 6 +----- 4 files changed, 4 insertions(+), 20 deletions(-) (limited to 'api/src') diff --git a/.gitignore b/.gitignore index cc60b305..607b4f5a 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ config.json .vscode/settings.json api/assets/plugins/*.js .idea/ +*.code-workspace + diff --git a/api/src/middlewares/TestClient.ts b/api/src/middlewares/TestClient.ts index e52a5e59..7292868c 100644 --- a/api/src/middlewares/TestClient.ts +++ b/api/src/middlewares/TestClient.ts @@ -27,7 +27,6 @@ export default function TestClient(app: Application) { newAssetCache = new Map(Object.entries(JSON.parse(rawdata.toString()))); } - //define routes app.use("/assets", express.static(path.join(__dirname, "..", "..", "assets"))); app.get("/assets/:file", async (req: Request, res: Response) => { delete req.headers.host; diff --git a/api/src/util/entities/AssetCacheItem.ts b/api/src/util/entities/AssetCacheItem.ts index 7fb4e1e6..160dece6 100644 --- a/api/src/util/entities/AssetCacheItem.ts +++ b/api/src/util/entities/AssetCacheItem.ts @@ -1,16 +1,3 @@ -import { Headers } from "node-fetch"; - export class AssetCacheItem { - public Key: string; - public FilePath: string; - public Headers: any; - - constructor(key: string){ - this.Key = key; - } - /*constructor(key: string, filePath: string, headers: Headers) { - this.Key = key; - this.FilePath = filePath; - this.Headers = headers; - }*/ + constructor(public Key: string, public FilePath: string = "", public Headers: any = null as any) {} } \ No newline at end of file diff --git a/fosscord-server.code-workspace b/fosscord-server.code-workspace index fff238f1..7491cb35 100644 --- a/fosscord-server.code-workspace +++ b/fosscord-server.code-workspace @@ -24,9 +24,5 @@ { "path": "webrtc" } - ], - "settings": { - "awooga.originalColorCustomizations": {}, - "workbench.colorCustomizations": {} - } + ] } -- cgit 1.5.1