mirror of
https://github.com/spacebarchat/server.git
synced 2026-10-23 08:43:02 +00:00
Merge branch 'spacebarchat:master' into fix/gh-webhook-events-more
This commit is contained in:
195 files changed
+3917
-788
No files matched your search
@@ -70,6 +70,7 @@ declare global {
|
||||
user_bot: boolean;
|
||||
token: { id: string; iat: number };
|
||||
rights: Rights;
|
||||
fingerprint?: string;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,6 +78,15 @@ declare global {
|
||||
export async function Authentication(req: Request, res: Response, next: NextFunction) {
|
||||
if (req.method === "OPTIONS") return res.sendStatus(204);
|
||||
const url = req.url.replace(API_PREFIX, "");
|
||||
|
||||
if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid=")))
|
||||
req.fingerprint = req.headers.cookie
|
||||
.split("; ")
|
||||
.find((x) => x.startsWith("__sb_sessid="))!
|
||||
.split("=")[1];
|
||||
// for some reason we need to require here, else the openapi generator fails with "route is not a function"
|
||||
else res.setHeader("Set-Cookie", `__sb_sessid=${req.fingerprint = (await require("../util")).randomString(32)}; Secure; HttpOnly; SameSite=None; Path=/`);
|
||||
|
||||
if (
|
||||
NO_AUTHORIZATION_ROUTES.some((x) => {
|
||||
if (typeof x !== "string") {
|
||||
@@ -102,10 +112,14 @@ export async function Authentication(req: Request, res: Response, next: NextFunc
|
||||
})
|
||||
)
|
||||
return next();
|
||||
|
||||
if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401));
|
||||
|
||||
try {
|
||||
const { decoded, user } = await checkToken(req.headers.authorization);
|
||||
const { decoded, user } = await checkToken(req.headers.authorization, {
|
||||
ipAddress: req.ip,
|
||||
fingerprint: req.fingerprint,
|
||||
});
|
||||
|
||||
req.token = decoded;
|
||||
req.user_id = decoded.id;
|
||||
|
||||
@@ -21,20 +21,16 @@ import { NextFunction, Request, Response } from "express";
|
||||
// TODO: config settings
|
||||
|
||||
export function CORS(req: Request, res: Response, next: NextFunction) {
|
||||
res.set("Access-Control-Allow-Origin", "*");
|
||||
res.set("Access-Control-Allow-Credentials", "true");
|
||||
res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*");
|
||||
res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*");
|
||||
res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*");
|
||||
res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically
|
||||
// TODO: use better CSP
|
||||
res.set(
|
||||
"Content-security-policy",
|
||||
"default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';",
|
||||
);
|
||||
res.set(
|
||||
"Access-Control-Allow-Headers",
|
||||
req.header("Access-Control-Request-Headers") || "*",
|
||||
);
|
||||
res.set(
|
||||
"Access-Control-Allow-Methods",
|
||||
req.header("Access-Control-Request-Methods") || "*",
|
||||
);
|
||||
|
||||
if (req.method === "OPTIONS") {
|
||||
res.status(204).end();
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress } from "@spacebar/api";
|
||||
import { Config, getRights, listenEvent } from "@spacebar/util";
|
||||
import { NextFunction, Request, Response, Router } from "express";
|
||||
import { API_PREFIX_TRAILING_SLASH } from "./Authentication";
|
||||
@@ -65,21 +64,14 @@ export default function rateLimit(opts: {
|
||||
if (rights.has("BYPASS_RATE_LIMITS")) return next();
|
||||
}
|
||||
|
||||
const bucket_id =
|
||||
opts.bucket ||
|
||||
req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
|
||||
let executor_id = getIpAdress(req);
|
||||
const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, "");
|
||||
let executor_id = req.ip || "127.0.0.1";
|
||||
if (!opts.onlyIp && req.user_id) executor_id = req.user_id;
|
||||
|
||||
let max_hits = opts.count;
|
||||
if (opts.bot && req.user_bot) max_hits = opts.bot;
|
||||
if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method))
|
||||
max_hits = opts.GET;
|
||||
else if (
|
||||
opts.MODIFY &&
|
||||
["POST", "DELETE", "PATCH", "PUT"].includes(req.method)
|
||||
)
|
||||
max_hits = opts.MODIFY;
|
||||
if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET;
|
||||
else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY;
|
||||
|
||||
const offender = Cache.get(executor_id + bucket_id);
|
||||
|
||||
@@ -104,18 +96,13 @@ export default function rateLimit(opts: {
|
||||
}
|
||||
|
||||
res.set("X-RateLimit-Reset", `${reset}`);
|
||||
res.set(
|
||||
"X-RateLimit-Reset-After",
|
||||
`${Math.max(0, Math.ceil(resetAfterSec))}`,
|
||||
);
|
||||
res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`);
|
||||
|
||||
if (offender.blocked) {
|
||||
const global = bucket_id === "global";
|
||||
// each block violation pushes the expiry one full window further
|
||||
reset += opts.window * 1000;
|
||||
offender.expires_at = new Date(
|
||||
offender.expires_at.getTime() + opts.window * 1000,
|
||||
);
|
||||
offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000);
|
||||
resetAfterMs = reset - Date.now();
|
||||
resetAfterSec = Math.ceil(resetAfterMs / 1000);
|
||||
|
||||
@@ -129,10 +116,7 @@ export default function rateLimit(opts: {
|
||||
res
|
||||
.status(429)
|
||||
.set("X-RateLimit-Remaining", "0")
|
||||
.set(
|
||||
"Retry-After",
|
||||
`${Math.max(0, Math.ceil(resetAfterSec))}`,
|
||||
)
|
||||
.set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`)
|
||||
// TODO: error rate limit message translation
|
||||
.send({
|
||||
message: "You are being rate limited.",
|
||||
@@ -156,11 +140,7 @@ export default function rateLimit(opts: {
|
||||
// check if error and increment error rate limit
|
||||
if (res.statusCode >= 400 && opts.error) {
|
||||
return hitRoute(hitRouteOpts);
|
||||
} else if (
|
||||
res.statusCode >= 200 &&
|
||||
res.statusCode < 300 &&
|
||||
opts.success
|
||||
) {
|
||||
} else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) {
|
||||
return hitRoute(hitRouteOpts);
|
||||
}
|
||||
});
|
||||
@@ -213,18 +193,10 @@ export async function initRateLimits(app: Router) {
|
||||
app.use("/webhooks/:webhook_id", rateLimit(routes.webhook));
|
||||
app.use("/channels/:channel_id", rateLimit(routes.channel));
|
||||
app.use("/auth/login", rateLimit(routes.auth.login));
|
||||
app.use(
|
||||
"/auth/register",
|
||||
rateLimit({ onlyIp: true, success: true, ...routes.auth.register }),
|
||||
);
|
||||
app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register }));
|
||||
}
|
||||
|
||||
async function hitRoute(opts: {
|
||||
executor_id: string;
|
||||
bucket_id: string;
|
||||
max_hits: number;
|
||||
window: number;
|
||||
}) {
|
||||
async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) {
|
||||
const id = opts.executor_id + opts.bucket_id;
|
||||
let limit = Cache.get(id);
|
||||
if (!limit) {
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import { route, verifyCaptcha } from "@spacebar/api";
|
||||
import { Config, Email, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { ForgotPasswordSchema } from "@spacebar/schemas"
|
||||
import { ForgotPasswordSchema } from "@spacebar/schemas";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
router.post(
|
||||
@@ -38,10 +38,7 @@ router.post(
|
||||
|
||||
const config = Config.get();
|
||||
|
||||
if (
|
||||
config.passwordReset.requireCaptcha &&
|
||||
config.security.captcha.enabled
|
||||
) {
|
||||
if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) {
|
||||
const { sitekey, service } = config.security.captcha;
|
||||
if (!captcha_key) {
|
||||
return res.status(400).json({
|
||||
@@ -51,7 +48,7 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
const ip = getIpAdress(req);
|
||||
const ip = req.ip;
|
||||
const verify = await verifyCaptcha(captcha_key, ip);
|
||||
if (!verify.success) {
|
||||
return res.status(400).json({
|
||||
@@ -71,9 +68,7 @@ router.post(
|
||||
|
||||
if (user && user.email) {
|
||||
Email.sendResetPassword(user, user.email).catch((e) => {
|
||||
console.error(
|
||||
`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`,
|
||||
);
|
||||
console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route } from "@spacebar/api";
|
||||
import { route } from "@spacebar/api";
|
||||
import { IpDataClient } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
@@ -33,7 +33,7 @@ router.get(
|
||||
async (req: Request, res: Response) => {
|
||||
//TODO
|
||||
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too
|
||||
const country_code = (await IpDataClient.getIpInfo(getIpAdress(req)))?.country_code;
|
||||
const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code;
|
||||
res.json({
|
||||
consent_required: false,
|
||||
country_code: country_code,
|
||||
|
||||
@@ -16,19 +16,12 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import {
|
||||
Config,
|
||||
FieldErrors,
|
||||
User,
|
||||
WebAuthn,
|
||||
generateToken,
|
||||
generateWebAuthnTicket,
|
||||
} from "@spacebar/util";
|
||||
import { route, verifyCaptcha } from "@spacebar/api";
|
||||
import { Config, FieldErrors, User, WebAuthn, generateToken, generateWebAuthnTicket } from "@spacebar/util";
|
||||
import bcrypt from "bcrypt";
|
||||
import crypto from "crypto";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { LoginSchema } from "@spacebar/schemas"
|
||||
import { LoginSchema } from "@spacebar/schemas";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
export default router;
|
||||
@@ -61,7 +54,7 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
const ip = getIpAdress(req);
|
||||
const ip = req.ip;
|
||||
const verify = await verifyCaptcha(captcha_key, ip);
|
||||
if (!verify.success) {
|
||||
return res.status(400).json({
|
||||
@@ -74,17 +67,7 @@ router.post(
|
||||
|
||||
const user = await User.findOneOrFail({
|
||||
where: [{ phone: login }, { email: login }],
|
||||
select: [
|
||||
"data",
|
||||
"id",
|
||||
"disabled",
|
||||
"deleted",
|
||||
"totp_secret",
|
||||
"mfa_enabled",
|
||||
"webauthn_enabled",
|
||||
"security_keys",
|
||||
"verified",
|
||||
],
|
||||
select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"],
|
||||
relations: ["security_keys", "settings"],
|
||||
}).catch(() => {
|
||||
throw FieldErrors({
|
||||
@@ -100,10 +83,7 @@ router.post(
|
||||
});
|
||||
|
||||
// the salt is saved in the password refer to bcrypt docs
|
||||
const same_password = await bcrypt.compare(
|
||||
password,
|
||||
user.data.hash || "",
|
||||
);
|
||||
const same_password = await bcrypt.compare(password, user.data.hash || "");
|
||||
if (!same_password) {
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
@@ -122,8 +102,7 @@ router.post(
|
||||
throw FieldErrors({
|
||||
login: {
|
||||
code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL",
|
||||
message:
|
||||
"Email verification is required, please check your email.",
|
||||
message: "Email verification is required, please check your email.",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -152,9 +131,7 @@ router.post(
|
||||
const challenge = JSON.stringify({
|
||||
publicKey: {
|
||||
...options,
|
||||
challenge: Buffer.from(options.challenge).toString(
|
||||
"base64",
|
||||
),
|
||||
challenge: Buffer.from(options.challenge).toString("base64"),
|
||||
allowCredentials: user.security_keys.map((x) => ({
|
||||
id: x.key_id,
|
||||
type: "public-key",
|
||||
@@ -178,10 +155,8 @@ router.post(
|
||||
|
||||
if (undelete) {
|
||||
// undelete refers to un'disable' here
|
||||
if (user.disabled)
|
||||
await User.update({ id: user.id }, { disabled: false });
|
||||
if (user.deleted)
|
||||
await User.update({ id: user.id }, { deleted: false });
|
||||
if (user.disabled) await User.update({ id: user.id }, { disabled: false });
|
||||
if (user.deleted) await User.update({ id: user.id }, { deleted: false });
|
||||
} else {
|
||||
if (user.deleted)
|
||||
return res.status(400).json({
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import { route, verifyCaptcha } from "@spacebar/api";
|
||||
import { Config, FieldErrors, Invite, User, ValidRegistrationToken, generateToken, IpDataClient, AbuseIpDbClient } from "@spacebar/util";
|
||||
import bcrypt from "bcrypt";
|
||||
import { Request, Response, Router } from "express";
|
||||
@@ -38,7 +38,7 @@ router.post(
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as RegisterSchema;
|
||||
const { register, security, limits } = Config.get();
|
||||
const ip = getIpAdress(req);
|
||||
const ip = req.ip!;
|
||||
|
||||
// Reg tokens
|
||||
// They're a one time use token that bypasses registration limits ( rates, disabled reg, etc )
|
||||
@@ -143,7 +143,7 @@ router.post(
|
||||
|
||||
const ipData = await IpDataClient.getIpInfo(ip);
|
||||
if (ipData) {
|
||||
if(!ipData.threat) {
|
||||
if (!ipData.threat) {
|
||||
console.log("Invalid IPData.co response, missing threat field", ipData);
|
||||
}
|
||||
const categories = Object.entries(ipData.threat)
|
||||
@@ -287,7 +287,7 @@ router.post(
|
||||
},
|
||||
})) >= limits.absoluteRate.register.limit
|
||||
) {
|
||||
console.log(`Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
|
||||
console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`);
|
||||
throw FieldErrors({
|
||||
email: {
|
||||
code: "TOO_MANY_REGISTRATIONS",
|
||||
|
||||
@@ -51,6 +51,8 @@ router.post(
|
||||
try {
|
||||
const userTokenData = await checkToken(token, {
|
||||
select: ["email"],
|
||||
fingerprint: req.fingerprint,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
user = userTokenData.user;
|
||||
} catch {
|
||||
|
||||
@@ -16,14 +16,8 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route, verifyCaptcha } from "@spacebar/api";
|
||||
import {
|
||||
checkToken,
|
||||
Config,
|
||||
FieldErrors,
|
||||
generateToken,
|
||||
User,
|
||||
} from "@spacebar/util";
|
||||
import { route, verifyCaptcha } from "@spacebar/api";
|
||||
import { checkToken, Config, FieldErrors, generateToken, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -67,7 +61,7 @@ router.post(
|
||||
});
|
||||
}
|
||||
|
||||
const ip = getIpAdress(req);
|
||||
const ip = req.ip;
|
||||
const verify = await verifyCaptcha(captcha_key, ip);
|
||||
if (!verify.success) {
|
||||
return res.status(400).json({
|
||||
@@ -81,7 +75,10 @@ router.post(
|
||||
let user;
|
||||
|
||||
try {
|
||||
const userTokenData = await checkToken(token);
|
||||
const userTokenData = await checkToken(token, {
|
||||
fingerprint: req.fingerprint,
|
||||
ipAddress: req.ip
|
||||
});
|
||||
user = userTokenData.user;
|
||||
} catch {
|
||||
throw FieldErrors({
|
||||
|
||||
@@ -548,7 +548,7 @@ router.delete(
|
||||
// TODO: handle other read state types
|
||||
if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send();
|
||||
|
||||
const readState = await ReadState.findOne({where: {channel_id}});
|
||||
const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } });
|
||||
if (readState) {
|
||||
await readState.remove();
|
||||
}
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route } from "@spacebar/api";
|
||||
import { route } from "@spacebar/api";
|
||||
import { Ban, DiscordApiErrors, GuildBanAddEvent, GuildBanRemoveEvent, Member, User, emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { APIBansArray, BanRegistrySchema, GuildBansResponse } from "@spacebar/schemas";
|
||||
import { APIBansArray, BanCreateSchema, BanRegistrySchema, GuildBansResponse } from "@spacebar/schemas";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
|
||||
@@ -198,6 +198,15 @@ router.put(
|
||||
async (req: Request, res: Response) => {
|
||||
const { guild_id } = req.params;
|
||||
const banned_user_id = req.params.user_id;
|
||||
const opts = req.body as BanCreateSchema;
|
||||
|
||||
let deleteMessagesMs = opts.delete_message_days
|
||||
? (opts.delete_message_days as number) * 86400000
|
||||
: opts.delete_message_seconds
|
||||
? (opts.delete_message_seconds as number) * 1000
|
||||
: 0;
|
||||
|
||||
if (deleteMessagesMs < 0) deleteMessagesMs = 0;
|
||||
|
||||
if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id)
|
||||
throw new HTTPError("You are the guild owner, hence can't ban yourself", 403);
|
||||
@@ -207,6 +216,7 @@ router.put(
|
||||
const existingBan = await Ban.findOne({
|
||||
where: { guild_id: guild_id, user_id: banned_user_id },
|
||||
});
|
||||
|
||||
// Bans on already banned users are silently ignored
|
||||
if (existingBan) return res.status(204).send();
|
||||
|
||||
@@ -227,6 +237,7 @@ router.put(
|
||||
data: {
|
||||
guild_id: guild_id,
|
||||
user: banned_user.toPublicUser(),
|
||||
delete_message_secs: Math.floor(deleteMessagesMs / 1000),
|
||||
},
|
||||
guild_id: guild_id,
|
||||
} as GuildBanAddEvent),
|
||||
@@ -279,4 +290,4 @@ router.delete(
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
export default router;
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route } from "@spacebar/api";
|
||||
import { route } from "@spacebar/api";
|
||||
import { Ban, DiscordApiErrors, GuildBanAddEvent, Member, User, emitEvent } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
@@ -81,7 +81,7 @@ router.post(
|
||||
const ban = Ban.create({
|
||||
user_id: banned_user_id,
|
||||
guild_id: guild_id,
|
||||
ip: getIpAdress(req),
|
||||
ip: req.ip,
|
||||
executor_id: req.user_id,
|
||||
reason: req.body.reason, // || otherwise empty
|
||||
});
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
|
||||
import { getVoiceRegions, route } from "@spacebar/api";
|
||||
import { Guild } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
@@ -38,12 +38,7 @@ router.get(
|
||||
const { guild_id } = req.params;
|
||||
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
|
||||
//TODO we should use an enum for guild's features and not hardcoded strings
|
||||
return res.json(
|
||||
await getVoiceRegions(
|
||||
getIpAdress(req),
|
||||
guild.features.includes("VIP_REGIONS"),
|
||||
),
|
||||
);
|
||||
return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS")));
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, route } from "@spacebar/api";
|
||||
import { route } from "@spacebar/api";
|
||||
import { Ban, DiscordApiErrors, emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, PublicInviteRelation, User } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { HTTPError } from "lambert-server";
|
||||
@@ -83,7 +83,7 @@ router.post(
|
||||
const ban = await Ban.findOne({
|
||||
where: [
|
||||
{ guild_id: guild_id, user_id: req.user_id },
|
||||
{ guild_id: guild_id, ip: getIpAdress(req) },
|
||||
{ guild_id: guild_id, ip: req.ip },
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
@@ -17,18 +17,9 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import {
|
||||
ApiError,
|
||||
Application,
|
||||
DiscordApiErrors,
|
||||
FieldErrors,
|
||||
Member,
|
||||
Permissions,
|
||||
User,
|
||||
getPermission,
|
||||
} from "@spacebar/util";
|
||||
import { ApiError, Application, DiscordApiErrors, FieldErrors, Member, Permissions, User, getPermission } from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { ApplicationAuthorizeSchema } from "@spacebar/schemas"
|
||||
import { ApplicationAuthorizeSchema } from "@spacebar/schemas";
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
// TODO: scopes, other oauth types
|
||||
@@ -84,26 +75,18 @@ router.get(
|
||||
id: req.user_id,
|
||||
bot: false,
|
||||
},
|
||||
select: [
|
||||
"id",
|
||||
"username",
|
||||
"avatar",
|
||||
"discriminator",
|
||||
"public_flags",
|
||||
],
|
||||
select: ["id", "username", "avatar", "discriminator", "public_flags"],
|
||||
});
|
||||
|
||||
const guilds = await Member.find({
|
||||
where: {
|
||||
user: {
|
||||
id: req.user_id,
|
||||
},
|
||||
id: req.user_id,
|
||||
},
|
||||
relations: ["guild", "roles", "user"],
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
//@ts-ignore
|
||||
// prettier-ignore
|
||||
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "communication_disabled_until", "user.flags"],
|
||||
select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"],
|
||||
});
|
||||
|
||||
const guildsWithPermissions = guilds.map((x) => {
|
||||
@@ -112,7 +95,7 @@ router.get(
|
||||
id: user.id,
|
||||
roles: x.roles?.map((x) => x.id) || [],
|
||||
communication_disabled_until: x.communication_disabled_until,
|
||||
flags: x.user.flags
|
||||
flags: x.user.flags,
|
||||
},
|
||||
guild: {
|
||||
roles: x?.roles || [],
|
||||
@@ -211,18 +194,9 @@ router.post(
|
||||
// TODO: captcha verification
|
||||
// TODO: MFA verification
|
||||
|
||||
const perms = await getPermission(
|
||||
req.user_id,
|
||||
body.guild_id,
|
||||
undefined,
|
||||
{ member_relations: ["user"] },
|
||||
);
|
||||
const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] });
|
||||
// getPermission cache won't exist if we're owner
|
||||
if (
|
||||
Object.keys(perms.cache || {}).length > 0 &&
|
||||
perms.cache.member?.user.bot
|
||||
)
|
||||
throw DiscordApiErrors.UNAUTHORIZED;
|
||||
if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED;
|
||||
perms.hasThrow("MANAGE_GUILD");
|
||||
|
||||
const app = await Application.findOne({
|
||||
@@ -235,12 +209,7 @@ router.post(
|
||||
// TODO: use DiscordApiErrors
|
||||
// findOneOrFail throws code 404
|
||||
if (!app) throw new ApiError("Unknown Application", 10002, 404);
|
||||
if (!app.bot)
|
||||
throw new ApiError(
|
||||
"OAuth2 application does not have a bot",
|
||||
50010,
|
||||
400,
|
||||
);
|
||||
if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400);
|
||||
|
||||
await Member.addToGuild(app.id, body.guild_id);
|
||||
|
||||
|
||||
@@ -17,9 +17,25 @@
|
||||
*/
|
||||
|
||||
import { route } from "@spacebar/api";
|
||||
import { emitEvent, Member, User, UserDeleteEvent } from "@spacebar/util";
|
||||
import {
|
||||
Channel,
|
||||
ChannelDeleteEvent,
|
||||
ChannelRecipientRemoveEvent,
|
||||
emitEvent,
|
||||
Emoji,
|
||||
Guild,
|
||||
InstanceBan,
|
||||
Member,
|
||||
Recipient,
|
||||
Sticker,
|
||||
Stopwatch,
|
||||
User,
|
||||
UserDeleteEvent,
|
||||
UserSettingsProtos,
|
||||
} from "@spacebar/util";
|
||||
import { Request, Response, Router } from "express";
|
||||
import { PrivateUserProjection } from "@spacebar/schemas";
|
||||
import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas";
|
||||
import { Not } from "typeorm";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
@@ -27,6 +43,7 @@ router.post(
|
||||
"/",
|
||||
route({
|
||||
right: "MANAGE_USERS",
|
||||
requestBody: "InstanceUserDeleteSchema",
|
||||
responses: {
|
||||
204: {},
|
||||
403: {
|
||||
@@ -38,12 +55,132 @@ router.post(
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
await User.findOneOrFail({
|
||||
const sw = Stopwatch.startNew();
|
||||
const body = req.body as InstanceUserDeleteSchema | undefined;
|
||||
const user = await User.findOneOrFail({
|
||||
where: { id: req.params.user_id },
|
||||
select: [...PrivateUserProjection, "data"],
|
||||
});
|
||||
|
||||
if (!(await InstanceBan.findOne({ where: { user_id: user.id } })))
|
||||
await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "<legacy instance ban API - no reason specified>" }).save();
|
||||
|
||||
// prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow
|
||||
const dmChannels = await user.getDmChannels();
|
||||
for (const channel of dmChannels) {
|
||||
console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`);
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: channel.toJSON(),
|
||||
channel_id: channel.id,
|
||||
} as ChannelDeleteEvent);
|
||||
await Recipient.delete({ channel_id: channel.id });
|
||||
await Channel.deleteChannel(channel);
|
||||
}
|
||||
|
||||
//leave all group channels
|
||||
const groupChannels = await Channel.find({
|
||||
where: { type: ChannelType.GROUP_DM },
|
||||
relations: ["recipients"],
|
||||
select: {
|
||||
id: true,
|
||||
owner_id: true,
|
||||
recipients: {
|
||||
id: true,
|
||||
user_id: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
groupChannels.map(async (channel) => {
|
||||
const recipient = channel.recipients!.find((r) => r.user_id === user.id);
|
||||
if (recipient) {
|
||||
await Recipient.delete({ id: recipient.id });
|
||||
await emitEvent({
|
||||
event: "CHANNEL_RECIPIENT_REMOVE",
|
||||
data: {
|
||||
user: user.toPublicUser(),
|
||||
channel_id: channel.id,
|
||||
},
|
||||
channel_id: channel.id,
|
||||
} as ChannelRecipientRemoveEvent);
|
||||
console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`);
|
||||
}
|
||||
|
||||
// if no recipients remain, delete the channel
|
||||
const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } });
|
||||
if (remainingRecipients.length === 0) {
|
||||
await emitEvent({
|
||||
event: "CHANNEL_DELETE",
|
||||
data: channel.toJSON(),
|
||||
channel_id: channel.id,
|
||||
} as ChannelDeleteEvent);
|
||||
await Channel.deleteChannel(channel);
|
||||
console.log(`[Instance ban] Deleted empty group channel ${channel.id}`);
|
||||
} else {
|
||||
// otherwise, if the banned user was the owner, reassign ownership
|
||||
if (channel.owner_id === user.id) {
|
||||
channel.owner_id = remainingRecipients[0].user_id;
|
||||
await channel.save();
|
||||
console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`);
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// change ownership on guilds
|
||||
const guilds = await Guild.find({ where: { owner_id: req.params.user_id } });
|
||||
await Promise.all(
|
||||
guilds.map(async (guild) => {
|
||||
const members = await Member.find({
|
||||
where: { guild_id: guild.id, id: Not(req.params.user_id) },
|
||||
relations: { roles: true },
|
||||
select: { id: true, roles: { id: true, position: true } },
|
||||
});
|
||||
const sortedMembers = members
|
||||
.filter((m) => m.id !== req.params.user_id)
|
||||
.sort((a, b) => {
|
||||
const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
|
||||
const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number });
|
||||
return bHighestRole.position - aHighestRole.position;
|
||||
});
|
||||
if (sortedMembers.length === 0) {
|
||||
// no members left, delete guild
|
||||
await guild.remove();
|
||||
console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`);
|
||||
} else {
|
||||
// assign new owner
|
||||
guild.owner_id = sortedMembers[0].id;
|
||||
await guild.save();
|
||||
console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`);
|
||||
|
||||
// safety - reassign emojis/stickers owned by the old owner
|
||||
const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
|
||||
await Promise.all(
|
||||
stickers.map(async (sticker) => {
|
||||
sticker.user_id = guild.owner_id;
|
||||
await sticker.save();
|
||||
console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`);
|
||||
}),
|
||||
);
|
||||
|
||||
const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } });
|
||||
await Promise.all(
|
||||
emojis.map(async (emoji) => {
|
||||
emoji.user_id = guild.owner_id!;
|
||||
await emoji.save();
|
||||
console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
const members = await Member.find({ where: { id: req.params.user_id } });
|
||||
await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id)), User.delete({ id: req.params.user_id })]);
|
||||
await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]);
|
||||
await UserSettingsProtos.delete({ user_id: req.params.user_id });
|
||||
await User.delete({ id: req.params.user_id });
|
||||
|
||||
// TODO: respect intents as USER_DELETE has potential to cause privacy issues
|
||||
await emitEvent({
|
||||
@@ -52,6 +189,7 @@ router.post(
|
||||
data: { user_id: req.params.user_id },
|
||||
} as UserDeleteEvent);
|
||||
|
||||
console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`);
|
||||
res.sendStatus(204);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
|
||||
import { getVoiceRegions, route } from "@spacebar/api";
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
const router: Router = Router({ mergeParams: true });
|
||||
@@ -31,7 +31,7 @@ router.get(
|
||||
},
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true?
|
||||
res.json(await getVoiceRegions(req.ip!, true)); //vip true?
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -16,41 +16,16 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Config } from "@spacebar/util";
|
||||
import { Request } from "express";
|
||||
|
||||
export function getIpAdress(req: Request): string {
|
||||
// TODO: express can do this (trustProxies: true)?
|
||||
|
||||
return req.ip!;
|
||||
}
|
||||
|
||||
type Location = { latitude: number; longitude: number };
|
||||
export function distanceBetweenLocations(
|
||||
loc1: Location,
|
||||
loc2: Location,
|
||||
): number {
|
||||
return distanceBetweenCoords(
|
||||
loc1.latitude,
|
||||
loc1.longitude,
|
||||
loc2.latitude,
|
||||
loc2.longitude,
|
||||
);
|
||||
export function distanceBetweenLocations(loc1: Location, loc2: Location): number {
|
||||
return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude);
|
||||
}
|
||||
|
||||
//Haversine function
|
||||
function distanceBetweenCoords(
|
||||
lat1: number,
|
||||
lon1: number,
|
||||
lat2: number,
|
||||
lon2: number,
|
||||
) {
|
||||
function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const p = 0.017453292519943295; // Math.PI / 180
|
||||
const c = Math.cos;
|
||||
const a =
|
||||
0.5 -
|
||||
c((lat2 - lat1) * p) / 2 +
|
||||
(c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
|
||||
const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2;
|
||||
|
||||
return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
|
||||
}
|
||||
@@ -28,6 +28,7 @@ import ws from "ws";
|
||||
import { Connection } from "./events/Connection";
|
||||
import http from "http";
|
||||
import { cleanupOnStartup } from "./util/Utils";
|
||||
import { randomString } from "@spacebar/api";
|
||||
|
||||
export class Server {
|
||||
public ws: ws.Server;
|
||||
@@ -50,6 +51,10 @@ export class Server {
|
||||
if (server) this.server = server;
|
||||
else {
|
||||
this.server = http.createServer(function (req, res) {
|
||||
if(!req.headers.cookie?.split("; ").find(x => x.startsWith("__sb_sessid="))) {
|
||||
res.setHeader("Set-Cookie", `__sb_sessid=${randomString(32)}; Secure; HttpOnly; SameSite=None; Path=/`);
|
||||
}
|
||||
|
||||
res.writeHead(200).end("Online");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -72,6 +72,13 @@ export async function Connection(
|
||||
);
|
||||
}
|
||||
|
||||
if (request.headers.cookie?.split("; ").find(x => x.startsWith("__sb_sessid="))) {
|
||||
socket.fingerprint = request.headers.cookie
|
||||
.split("; ")
|
||||
.find((x) => x.startsWith("__sb_sessid="))
|
||||
?.split("=")[1];
|
||||
}
|
||||
|
||||
//Create session ID when the connection is opened. This allows gateway dump to group the initial websocket messages with the rest of the conversation.
|
||||
const session_id = genSessionId();
|
||||
socket.session_id = session_id; //Set the session of the WebSocket object
|
||||
|
||||
@@ -29,6 +29,7 @@ import {
|
||||
Message,
|
||||
NewUrlUserSignatureData,
|
||||
GuildMemberAddEvent,
|
||||
Ban,
|
||||
} from "@spacebar/util";
|
||||
import { OPCODES } from "../util/Constants";
|
||||
import { Send } from "../util/Send";
|
||||
@@ -36,7 +37,7 @@ import { WebSocket } from "@spacebar/gateway";
|
||||
import { Channel as AMQChannel } from "amqplib";
|
||||
import { Recipient } from "@spacebar/util";
|
||||
import * as console from "node:console";
|
||||
import { PublicMember, RelationshipType } from "@spacebar/schemas"
|
||||
import { PublicMember, RelationshipType } from "@spacebar/schemas";
|
||||
import { bgRedBright } from "picocolors";
|
||||
|
||||
// TODO: close connection on Invalidated Token
|
||||
@@ -46,10 +47,7 @@ import { bgRedBright } from "picocolors";
|
||||
// Sharding: calculate if the current shard id matches the formula: shard_id = (guild_id >> 22) % num_shards
|
||||
// https://discord.com/developers/docs/topics/gateway#sharding
|
||||
|
||||
export function handlePresenceUpdate(
|
||||
this: WebSocket,
|
||||
{ event, acknowledge, data }: EventOpts,
|
||||
) {
|
||||
export function handlePresenceUpdate(this: WebSocket, { event, acknowledge, data }: EventOpts) {
|
||||
acknowledge?.();
|
||||
if (event === EVENTEnum.PresenceUpdate) {
|
||||
return Send(this, {
|
||||
@@ -92,32 +90,24 @@ export async function setupListener(this: WebSocket) {
|
||||
this.listen_options = opts;
|
||||
const consumer = consume.bind(this);
|
||||
|
||||
const handleChannelError = (err: unknown) => {
|
||||
console.error(`[RabbitMQ] [user-${this.user_id}] Channel Error (Handled):`, err);
|
||||
};
|
||||
|
||||
console.log("[RabbitMQ] setupListener: open for ", this.user_id);
|
||||
if (RabbitMQ.connection) {
|
||||
console.log(
|
||||
"[RabbitMQ] setupListener: opts.channel = ",
|
||||
typeof opts.channel,
|
||||
"with channel id",
|
||||
opts.channel?.ch,
|
||||
);
|
||||
console.log("[RabbitMQ] setupListener: opts.channel = ", typeof opts.channel, "with channel id", opts.channel?.ch);
|
||||
opts.channel = await RabbitMQ.connection.createChannel();
|
||||
|
||||
opts.channel.on("error", handleChannelError);
|
||||
opts.channel.queues = {};
|
||||
console.log(
|
||||
"[RabbitMQ] channel created: ",
|
||||
typeof opts.channel,
|
||||
"with channel id",
|
||||
opts.channel?.ch,
|
||||
);
|
||||
console.log("[RabbitMQ] channel created: ", typeof opts.channel, "with channel id", opts.channel?.ch);
|
||||
}
|
||||
|
||||
this.events[this.user_id] = await listenEvent(this.user_id, consumer, opts);
|
||||
|
||||
relationships.forEach(async (relationship) => {
|
||||
this.events[relationship.to_id] = await listenEvent(
|
||||
relationship.to_id,
|
||||
handlePresenceUpdate.bind(this),
|
||||
opts,
|
||||
);
|
||||
this.events[relationship.to_id] = await listenEvent(relationship.to_id, handlePresenceUpdate.bind(this), opts);
|
||||
});
|
||||
|
||||
dm_channels.forEach(async (channel) => {
|
||||
@@ -130,33 +120,27 @@ export async function setupListener(this: WebSocket) {
|
||||
this.events[guild.id] = await listenEvent(guild.id, consumer, opts);
|
||||
|
||||
guild.channels.forEach(async (channel) => {
|
||||
if (
|
||||
permission
|
||||
.overwriteChannel(channel.permission_overwrites ?? [])
|
||||
.has("VIEW_CHANNEL")
|
||||
) {
|
||||
this.events[channel.id] = await listenEvent(
|
||||
channel.id,
|
||||
consumer,
|
||||
opts,
|
||||
);
|
||||
if (permission.overwriteChannel(channel.permission_overwrites ?? []).has("VIEW_CHANNEL")) {
|
||||
this.events[channel.id] = await listenEvent(channel.id, consumer, opts);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.once("close", () => {
|
||||
console.log(
|
||||
"[RabbitMQ] setupListener: close for",
|
||||
this.user_id,
|
||||
"=",
|
||||
typeof opts.channel,
|
||||
"with channel id",
|
||||
opts.channel?.ch,
|
||||
this.once("close", async () => {
|
||||
console.log("[RabbitMQ] setupListener: close for", this.user_id, "=", typeof opts.channel, "with channel id", opts.channel?.ch);
|
||||
|
||||
// wait for event consumer cancellation
|
||||
await Promise.all(
|
||||
Object.values(this.events).map((x) => {
|
||||
if (x) return x();
|
||||
else return Promise.resolve();
|
||||
}),
|
||||
);
|
||||
if (opts.channel) opts.channel.close();
|
||||
else {
|
||||
Object.values(this.events).forEach((x) => x?.());
|
||||
Object.values(this.member_events).forEach((x) => x());
|
||||
await Promise.all(Object.values(this.member_events).map((x) => x()));
|
||||
|
||||
if (opts.channel) {
|
||||
await opts.channel.close();
|
||||
opts.channel.off("error", handleChannelError);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,11 +164,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
|
||||
break;
|
||||
case "GUILD_MEMBER_ADD":
|
||||
if (this.member_events[data.user.id]) break; // already subscribed
|
||||
this.member_events[data.user.id] = await listenEvent(
|
||||
data.user.id,
|
||||
handlePresenceUpdate.bind(this),
|
||||
this.listen_options,
|
||||
);
|
||||
this.member_events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options);
|
||||
break;
|
||||
case "GUILD_MEMBER_UPDATE":
|
||||
if (!this.member_events[data.user.id]) break;
|
||||
@@ -195,34 +175,32 @@ async function consume(this: WebSocket, opts: EventOpts) {
|
||||
case "GUILD_DELETE":
|
||||
this.events[id]?.();
|
||||
delete this.events[id];
|
||||
if (event === "GUILD_DELETE" && this.ipAddress) {
|
||||
const ban = await Ban.findOne({
|
||||
where: { guild_id: id, user_id: this.user_id },
|
||||
});
|
||||
|
||||
if (ban) {
|
||||
ban.ip = this.ipAddress || undefined;
|
||||
await ban.save();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "CHANNEL_CREATE":
|
||||
if (
|
||||
!permission
|
||||
.overwriteChannel(data.permission_overwrites)
|
||||
.has("VIEW_CHANNEL")
|
||||
) {
|
||||
if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) {
|
||||
return;
|
||||
}
|
||||
this.events[id] = await listenEvent(id, consumer, listenOpts);
|
||||
break;
|
||||
case "RELATIONSHIP_ADD":
|
||||
this.events[data.user.id] = await listenEvent(
|
||||
data.user.id,
|
||||
handlePresenceUpdate.bind(this),
|
||||
this.listen_options,
|
||||
);
|
||||
this.events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options);
|
||||
break;
|
||||
case "GUILD_CREATE":
|
||||
this.events[id] = await listenEvent(id, consumer, listenOpts);
|
||||
break;
|
||||
case "CHANNEL_UPDATE": {
|
||||
const exists = this.events[id];
|
||||
if (
|
||||
permission
|
||||
.overwriteChannel(data.permission_overwrites)
|
||||
.has("VIEW_CHANNEL")
|
||||
) {
|
||||
if (permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) {
|
||||
if (exists) break;
|
||||
this.events[id] = await listenEvent(id, consumer, listenOpts);
|
||||
} else {
|
||||
@@ -294,20 +272,19 @@ async function consume(this: WebSocket, opts: EventOpts) {
|
||||
case "MESSAGE_UPDATE":
|
||||
// console.log(this.request)
|
||||
if (data["attachments"])
|
||||
data["attachments"] =
|
||||
Message.prototype.withSignedAttachments.call(
|
||||
data,
|
||||
new NewUrlUserSignatureData({
|
||||
ip: this.ipAddress,
|
||||
userAgent: this.userAgent,
|
||||
}),
|
||||
).attachments;
|
||||
data["attachments"] = Message.prototype.withSignedAttachments.call(
|
||||
data,
|
||||
new NewUrlUserSignatureData({
|
||||
ip: this.ipAddress,
|
||||
userAgent: this.userAgent,
|
||||
}),
|
||||
).attachments;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(event === "GUILD_MEMBER_ADD") {
|
||||
if (event === "GUILD_MEMBER_ADD") {
|
||||
if ((data as PublicMember).roles === undefined || (data as PublicMember).roles === null) {
|
||||
console.log(bgRedBright("[Gateway]"), "[GUILD_MEMBER_ADD] roles is undefined, setting to empty array!", opts.origin ?? "(Event origin not defined)", data);
|
||||
(data as PublicMember).roles = [];
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface WebSocket extends WS {
|
||||
compress?: "zlib-stream" | "zstd-stream";
|
||||
ipAddress?: string;
|
||||
userAgent?: string; // for cdn request signing
|
||||
fingerprint?: string;
|
||||
shard_count?: bigint;
|
||||
shard_id?: bigint;
|
||||
deflate?: Deflate;
|
||||
@@ -44,8 +45,8 @@ export interface WebSocket extends WS {
|
||||
intents: Intents;
|
||||
sequence: number;
|
||||
permissions: Record<string, Permissions>;
|
||||
events: Record<string, undefined | (() => unknown)>;
|
||||
member_events: Record<string, () => unknown>;
|
||||
events: Record<string, undefined | (() => Promise<unknown>)>;
|
||||
member_events: Record<string, () => Promise<unknown>>;
|
||||
listen_options: ListenEventOpts;
|
||||
capabilities?: Capabilities;
|
||||
large_threshold: number;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { ConnectedAccount } from "@spacebar/util";
|
||||
|
||||
export type InstanceUserDeleteSchema = InstanceUserDeleteSchemaContent | undefined; //unsure if this a correct way to make the body optional
|
||||
export interface InstanceUserDeleteSchemaContent {
|
||||
reason?: string;
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
export * from "./ConnectedAccount";
|
||||
export * from "./InstanceUserDeleteSchema";
|
||||
export * from "./Member";
|
||||
export * from "./User";
|
||||
export * from "./UserSettings";
|
||||
@@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
export interface BanCreateSchema {
|
||||
delete_message_seconds?: string;
|
||||
delete_message_days?: string;
|
||||
delete_message_seconds?: number;
|
||||
delete_message_days?: number;
|
||||
reason?: string;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export class SecurityConfiguration {
|
||||
twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration();
|
||||
autoUpdate: boolean | number = true;
|
||||
requestSignature: string = crypto.randomBytes(32).toString("base64");
|
||||
jwtSecret: string = crypto.randomBytes(256).toString("base64");
|
||||
jwtSecret: string | null = null;
|
||||
// header to get the real user ip address
|
||||
// X-Forwarded-For for nginx/reverse proxies
|
||||
// CF-Connecting-IP for cloudflare
|
||||
@@ -36,7 +36,7 @@ export class SecurityConfiguration {
|
||||
// https://docs.abuseipdb.com/#api-daily-rate-limits
|
||||
abuseipdbBlacklistRatelimit: number = 5;
|
||||
abuseipdbConfidenceScoreTreshold: number = 50;
|
||||
ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"; // isnt even valid anymore it seems?
|
||||
ipdataApiKey: string | null = null;
|
||||
mfaBackupCodeCount: number = 10;
|
||||
statsWorldReadable: boolean = true;
|
||||
defaultRegistrationTokenExpiration: number = 1000 * 60 * 60 * 24 * 7; //1 week
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { User } from "./User";
|
||||
import crypto from "crypto";
|
||||
import { Config } from "../util";
|
||||
|
||||
@Entity({
|
||||
name: "backup_codes",
|
||||
@@ -41,7 +42,7 @@ export class BackupCode extends BaseClass {
|
||||
|
||||
export function generateMfaBackupCodes(user_id: string) {
|
||||
const backup_codes: BackupCode[] = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
for (let i = 0; i < Config.get().security.mfaBackupCodeCount; i++) {
|
||||
const code = BackupCode.create({
|
||||
user: { id: user_id },
|
||||
code: crypto.randomBytes(4).toString("hex"), // 8 characters
|
||||
|
||||
@@ -379,13 +379,15 @@ export class Channel extends BaseClass {
|
||||
// TODO Delete attachments from the CDN for messages in the channel
|
||||
await Channel.delete({ id: channel.id });
|
||||
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: { id: channel.guild_id },
|
||||
select: { channel_ordering: true },
|
||||
});
|
||||
if (channel.guild_id) {
|
||||
const guild = await Guild.findOneOrFail({
|
||||
where: { id: channel.guild_id },
|
||||
select: { channel_ordering: true },
|
||||
});
|
||||
|
||||
const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id);
|
||||
await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering });
|
||||
const updatedOrdering = guild.channel_ordering.filter((id) => id != channel.id);
|
||||
await Guild.update({ id: channel.guild_id }, { channel_ordering: updatedOrdering });
|
||||
}
|
||||
}
|
||||
|
||||
static async calculatePosition(channel_id: string, guild_id: string, guild?: Guild) {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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 { Column, CreateDateColumn, Entity, FindOptionsWhere, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
|
||||
import { BaseClass } from "./BaseClass";
|
||||
import { Team } from "./Team";
|
||||
import { User } from "./User";
|
||||
import { Guild } from "./Guild";
|
||||
|
||||
@Entity({
|
||||
name: "instance_bans",
|
||||
})
|
||||
export class InstanceBan extends BaseClass {
|
||||
@Column({ type: "bigint" })
|
||||
@CreateDateColumn()
|
||||
created_at: Date = new Date();
|
||||
|
||||
@Column()
|
||||
reason: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
user_id?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
fingerprint?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
ip_address?: string;
|
||||
|
||||
// chain of trust type tracking
|
||||
|
||||
@Column({ default: false })
|
||||
is_allowlisted: boolean = false;
|
||||
|
||||
@Column({ default: false })
|
||||
is_from_other_instance_ban: boolean = false;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@RelationId((instance_ban: InstanceBan) => instance_ban.origin_instance_ban)
|
||||
origin_instance_ban_id?: string;
|
||||
|
||||
@JoinColumn({ name: "origin_instance_ban_id" })
|
||||
@OneToOne(() => InstanceBan, { nullable: true, onDelete: "SET NULL" })
|
||||
origin_instance_ban?: InstanceBan;
|
||||
|
||||
static async findInstanceBans(opts: { userId?: string; ipAddress?: string; fingerprint?: string; propagateBan?: boolean }) {
|
||||
const optionalChecks: FindOptionsWhere<InstanceBan>[] = [{ user_id: opts.userId }];
|
||||
if (opts?.ipAddress) optionalChecks.push({ ip_address: opts.ipAddress });
|
||||
if (opts?.fingerprint) optionalChecks.push({ fingerprint: opts.fingerprint });
|
||||
const instanceBans = await InstanceBan.find({ where: optionalChecks });
|
||||
|
||||
const banReasons = [];
|
||||
for (const ban of instanceBans) {
|
||||
if (ban.is_allowlisted) continue;
|
||||
if (opts?.fingerprint && ban.fingerprint === opts.fingerprint) banReasons.push("fingerprint");
|
||||
if (opts?.ipAddress && ban.ip_address === opts.ipAddress) banReasons.push("ipAddress");
|
||||
if (opts?.userId && ban.user_id === opts?.userId) banReasons.push("userId");
|
||||
}
|
||||
|
||||
const banViralityPromises: Promise<InstanceBan>[] = [];
|
||||
if (opts.propagateBan && banReasons.length > 0) {
|
||||
if (opts?.ipAddress && !instanceBans.find((b) => b.ip_address === opts.ipAddress))
|
||||
banViralityPromises.push(
|
||||
InstanceBan.create({
|
||||
user_id: opts.userId,
|
||||
ip_address: opts.ipAddress,
|
||||
reason: "Propagated from other instance ban",
|
||||
is_from_other_instance_ban: true,
|
||||
origin_instance_ban: instanceBans[0],
|
||||
}).save(),
|
||||
);
|
||||
if (opts?.fingerprint && !instanceBans.find((b) => b.fingerprint === opts.fingerprint))
|
||||
banViralityPromises.push(
|
||||
InstanceBan.create({
|
||||
user_id: opts.userId,
|
||||
fingerprint: opts.fingerprint,
|
||||
reason: "Propagated from other instance ban",
|
||||
is_from_other_instance_ban: true,
|
||||
origin_instance_ban: instanceBans[0],
|
||||
}).save(),
|
||||
);
|
||||
if (opts?.userId && !instanceBans.find((b) => b.user_id === opts.userId))
|
||||
banViralityPromises.push(
|
||||
InstanceBan.create({
|
||||
user_id: opts.userId,
|
||||
reason: "Propagated from other instance ban",
|
||||
is_from_other_instance_ban: true,
|
||||
origin_instance_ban: instanceBans[0],
|
||||
}).save(),
|
||||
);
|
||||
}
|
||||
|
||||
await Promise.all(banViralityPromises);
|
||||
return banReasons;
|
||||
}
|
||||
}
|
||||
@@ -359,4 +359,18 @@ export class User extends BaseClass {
|
||||
|
||||
return qry[0];
|
||||
}
|
||||
|
||||
async getDmChannels() {
|
||||
const qry = await Channel.getRepository()
|
||||
.createQueryBuilder("channel")
|
||||
.leftJoinAndSelect("channel.recipients", "rcp")
|
||||
.where("channel.type = :type", { type: ChannelType.DM })
|
||||
.andWhere("rcp.user_id = :user_id", { user_id: this.id })
|
||||
.groupBy("channel.id")
|
||||
.addGroupBy("rcp.id")
|
||||
.having("COUNT(rcp.id) = 2")
|
||||
.getMany();
|
||||
|
||||
return qry;
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ export * from "./EmbedCache";
|
||||
export * from "./Emoji";
|
||||
export * from "./Encryption";
|
||||
export * from "./Guild";
|
||||
export * from "./InstanceBan";
|
||||
export * from "./Invite";
|
||||
export * from "./Member";
|
||||
export * from "./Message";
|
||||
|
||||
@@ -219,6 +219,7 @@ export interface GuildBanAddEvent extends Event {
|
||||
data: {
|
||||
guild_id: string;
|
||||
user: User;
|
||||
delete_message_secs?: number
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,727 @@
|
||||
// @prettier-ignore
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2025 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 { MigrationInterface, QueryRunner } from "typeorm";
|
||||
export class initial0 implements MigrationInterface {
|
||||
name = "initial0";
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE public.applications (
|
||||
id character varying NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
icon character varying,
|
||||
description character varying,
|
||||
summary character varying,
|
||||
type text,
|
||||
hook boolean NOT NULL,
|
||||
bot_public boolean NOT NULL,
|
||||
bot_require_code_grant boolean NOT NULL,
|
||||
verify_key character varying NOT NULL,
|
||||
flags integer NOT NULL,
|
||||
redirect_uris text,
|
||||
rpc_application_state integer,
|
||||
store_application_state integer,
|
||||
verification_state integer,
|
||||
interactions_endpoint_url character varying,
|
||||
integration_public boolean,
|
||||
integration_require_code_grant boolean,
|
||||
discoverability_state integer,
|
||||
discovery_eligibility_flags integer,
|
||||
tags text,
|
||||
cover_image character varying,
|
||||
install_params text,
|
||||
terms_of_service_url character varying,
|
||||
privacy_policy_url character varying,
|
||||
owner_id character varying,
|
||||
bot_user_id character varying,
|
||||
team_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.attachments (
|
||||
id character varying NOT NULL,
|
||||
filename character varying NOT NULL,
|
||||
size integer NOT NULL,
|
||||
url character varying NOT NULL,
|
||||
proxy_url character varying NOT NULL,
|
||||
height integer,
|
||||
width integer,
|
||||
content_type character varying,
|
||||
message_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.audit_logs (
|
||||
id character varying NOT NULL,
|
||||
user_id character varying,
|
||||
action_type integer NOT NULL,
|
||||
options text,
|
||||
changes text NOT NULL,
|
||||
reason character varying,
|
||||
target_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.backup_codes (
|
||||
id character varying NOT NULL,
|
||||
code character varying NOT NULL,
|
||||
consumed boolean NOT NULL,
|
||||
expired boolean NOT NULL,
|
||||
user_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.bans (
|
||||
id character varying NOT NULL,
|
||||
user_id character varying,
|
||||
guild_id character varying,
|
||||
executor_id character varying,
|
||||
ip character varying NOT NULL,
|
||||
reason character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.categories (
|
||||
id integer NOT NULL,
|
||||
name character varying,
|
||||
localizations text NOT NULL,
|
||||
is_primary boolean
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.channels (
|
||||
id character varying NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
name character varying,
|
||||
icon text,
|
||||
type integer NOT NULL,
|
||||
last_message_id character varying,
|
||||
guild_id character varying,
|
||||
parent_id character varying,
|
||||
owner_id character varying,
|
||||
last_pin_timestamp integer,
|
||||
default_auto_archive_duration integer,
|
||||
permission_overwrites text,
|
||||
video_quality_mode integer,
|
||||
bitrate integer,
|
||||
user_limit integer,
|
||||
nsfw boolean NOT NULL,
|
||||
rate_limit_per_user integer,
|
||||
topic character varying,
|
||||
retention_policy_id character varying,
|
||||
flags integer NOT NULL,
|
||||
default_thread_rate_limit_per_user integer NOT NULL,
|
||||
position integer NOT NULL DEFAULT 0
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.client_release (
|
||||
id character varying NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
pub_date timestamp without time zone NOT NULL,
|
||||
url character varying NOT NULL,
|
||||
platform character varying NOT NULL,
|
||||
enabled boolean NOT NULL,
|
||||
notes character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.config (
|
||||
key character varying NOT NULL,
|
||||
value text
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.connected_accounts (
|
||||
id character varying NOT NULL,
|
||||
external_id character varying NOT NULL,
|
||||
user_id character varying,
|
||||
friend_sync boolean NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
revoked boolean NOT NULL,
|
||||
show_activity integer NOT NULL,
|
||||
type character varying NOT NULL,
|
||||
verified boolean NOT NULL,
|
||||
visibility integer NOT NULL,
|
||||
integrations text NOT NULL,
|
||||
metadata text,
|
||||
metadata_visibility integer NOT NULL,
|
||||
two_way_link boolean NOT NULL,
|
||||
token_data text
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.connection_config (
|
||||
key character varying NOT NULL,
|
||||
value text
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.embed_cache (
|
||||
id character varying NOT NULL,
|
||||
url character varying NOT NULL,
|
||||
embed text NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.emojis (
|
||||
id character varying NOT NULL,
|
||||
animated boolean NOT NULL,
|
||||
available boolean NOT NULL,
|
||||
guild_id character varying NOT NULL,
|
||||
user_id character varying,
|
||||
managed boolean NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
require_colons boolean NOT NULL,
|
||||
roles text NOT NULL,
|
||||
groups text
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.guilds (
|
||||
id character varying NOT NULL,
|
||||
afk_channel_id character varying,
|
||||
afk_timeout integer,
|
||||
banner character varying,
|
||||
default_message_notifications integer,
|
||||
description character varying,
|
||||
discovery_splash character varying,
|
||||
explicit_content_filter integer,
|
||||
features text NOT NULL,
|
||||
primary_category_id character varying,
|
||||
icon character varying,
|
||||
large boolean NOT NULL,
|
||||
max_members integer,
|
||||
max_presences integer,
|
||||
max_video_channel_users integer,
|
||||
member_count integer,
|
||||
presence_count integer,
|
||||
template_id character varying,
|
||||
mfa_level integer,
|
||||
name character varying NOT NULL,
|
||||
owner_id character varying,
|
||||
preferred_locale character varying,
|
||||
premium_subscription_count integer,
|
||||
premium_tier integer NOT NULL,
|
||||
public_updates_channel_id character varying,
|
||||
rules_channel_id character varying,
|
||||
region character varying,
|
||||
splash character varying,
|
||||
system_channel_id character varying,
|
||||
system_channel_flags integer,
|
||||
unavailable boolean NOT NULL,
|
||||
verification_level integer,
|
||||
welcome_screen text NOT NULL,
|
||||
widget_channel_id character varying,
|
||||
widget_enabled boolean NOT NULL,
|
||||
nsfw_level integer,
|
||||
nsfw boolean NOT NULL,
|
||||
parent character varying,
|
||||
premium_progress_bar_enabled boolean
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.invites (
|
||||
code character varying NOT NULL,
|
||||
temporary boolean NOT NULL,
|
||||
uses integer NOT NULL,
|
||||
max_uses integer NOT NULL,
|
||||
max_age integer NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
expires_at timestamp without time zone,
|
||||
guild_id character varying,
|
||||
channel_id character varying,
|
||||
inviter_id character varying,
|
||||
target_user_id character varying,
|
||||
target_user_type integer,
|
||||
vanity_url boolean,
|
||||
flags integer NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.member_roles (
|
||||
index integer NOT NULL,
|
||||
role_id character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.members (
|
||||
index integer NOT NULL,
|
||||
id character varying NOT NULL,
|
||||
guild_id character varying NOT NULL,
|
||||
nick character varying,
|
||||
joined_at timestamp without time zone NOT NULL,
|
||||
premium_since bigint,
|
||||
deaf boolean NOT NULL,
|
||||
mute boolean NOT NULL,
|
||||
pending boolean NOT NULL,
|
||||
settings text NOT NULL,
|
||||
last_message_id character varying,
|
||||
joined_by character varying,
|
||||
avatar character varying,
|
||||
banner character varying,
|
||||
bio character varying NOT NULL,
|
||||
theme_colors text,
|
||||
pronouns character varying,
|
||||
communication_disabled_until timestamp without time zone
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE SEQUENCE public.members_index_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;`);
|
||||
|
||||
await queryRunner.query(`ALTER SEQUENCE public.members_index_seq OWNED BY public.members.index;`);
|
||||
await queryRunner.query(`CREATE TABLE public.message_channel_mentions (
|
||||
"messagesId" character varying NOT NULL,
|
||||
"channelsId" character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.message_role_mentions (
|
||||
"messagesId" character varying NOT NULL,
|
||||
"rolesId" character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.message_stickers (
|
||||
"messagesId" character varying NOT NULL,
|
||||
"stickersId" character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.message_user_mentions (
|
||||
"messagesId" character varying NOT NULL,
|
||||
"usersId" character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.messages (
|
||||
id character varying NOT NULL,
|
||||
channel_id character varying,
|
||||
guild_id character varying,
|
||||
author_id character varying,
|
||||
member_id character varying,
|
||||
webhook_id character varying,
|
||||
application_id character varying,
|
||||
content character varying,
|
||||
"timestamp" timestamp without time zone DEFAULT now() NOT NULL,
|
||||
edited_timestamp timestamp without time zone,
|
||||
tts boolean,
|
||||
mention_everyone boolean,
|
||||
embeds text NOT NULL,
|
||||
reactions text NOT NULL,
|
||||
nonce text,
|
||||
type integer NOT NULL,
|
||||
activity text,
|
||||
message_reference text,
|
||||
interaction text,
|
||||
components text,
|
||||
message_reference_id character varying,
|
||||
pinned boolean,
|
||||
flags integer
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.migrations (
|
||||
id integer NOT NULL,
|
||||
"timestamp" bigint NOT NULL,
|
||||
name character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE SEQUENCE public.migrations_id_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;`);
|
||||
|
||||
await queryRunner.query(`ALTER SEQUENCE public.migrations_id_seq OWNED BY public.migrations.id;`);
|
||||
await queryRunner.query(`CREATE TABLE public.notes (
|
||||
id character varying NOT NULL,
|
||||
content character varying NOT NULL,
|
||||
owner_id character varying,
|
||||
target_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.rate_limits (
|
||||
id character varying NOT NULL,
|
||||
executor_id character varying NOT NULL,
|
||||
hits integer NOT NULL,
|
||||
blocked boolean NOT NULL,
|
||||
expires_at timestamp without time zone NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.read_states (
|
||||
id character varying NOT NULL,
|
||||
channel_id character varying NOT NULL,
|
||||
user_id character varying NOT NULL,
|
||||
last_message_id character varying,
|
||||
public_ack character varying,
|
||||
notifications_cursor character varying,
|
||||
last_pin_timestamp timestamp without time zone,
|
||||
mention_count integer
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.recipients (
|
||||
id character varying NOT NULL,
|
||||
channel_id character varying NOT NULL,
|
||||
user_id character varying NOT NULL,
|
||||
closed boolean DEFAULT false NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.relationships (
|
||||
id character varying NOT NULL,
|
||||
from_id character varying NOT NULL,
|
||||
to_id character varying NOT NULL,
|
||||
nickname character varying,
|
||||
type integer NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.roles (
|
||||
id character varying NOT NULL,
|
||||
guild_id character varying NOT NULL,
|
||||
color integer NOT NULL,
|
||||
hoist boolean NOT NULL,
|
||||
managed boolean NOT NULL,
|
||||
mentionable boolean NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
permissions character varying NOT NULL,
|
||||
"position" integer NOT NULL,
|
||||
icon character varying,
|
||||
unicode_emoji character varying,
|
||||
tags text,
|
||||
flags integer DEFAULT 0 NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.security_settings (
|
||||
id character varying NOT NULL,
|
||||
guild_id character varying,
|
||||
channel_id character varying,
|
||||
encryption_permission_mask integer NOT NULL,
|
||||
allowed_algorithms text NOT NULL,
|
||||
current_algorithm character varying NOT NULL,
|
||||
used_since_message character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.sessions (
|
||||
id character varying NOT NULL,
|
||||
user_id character varying,
|
||||
session_id character varying NOT NULL,
|
||||
activities text,
|
||||
client_info text NOT NULL,
|
||||
status character varying NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.sticker_packs (
|
||||
id character varying NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying,
|
||||
banner_asset_id character varying,
|
||||
cover_sticker_id character varying,
|
||||
"coverStickerId" character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.stickers (
|
||||
id character varying NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying,
|
||||
available boolean,
|
||||
tags character varying,
|
||||
pack_id character varying,
|
||||
guild_id character varying,
|
||||
user_id character varying,
|
||||
type integer NOT NULL,
|
||||
format_type integer NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.team_members (
|
||||
id character varying NOT NULL,
|
||||
membership_state integer NOT NULL,
|
||||
permissions text NOT NULL,
|
||||
team_id character varying,
|
||||
user_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.teams (
|
||||
id character varying NOT NULL,
|
||||
icon character varying,
|
||||
name character varying NOT NULL,
|
||||
owner_user_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.templates (
|
||||
id character varying NOT NULL,
|
||||
code character varying NOT NULL,
|
||||
name character varying NOT NULL,
|
||||
description character varying,
|
||||
usage_count integer,
|
||||
creator_id character varying,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
source_guild_id character varying,
|
||||
serialized_source_guild text NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.user_settings (
|
||||
index integer NOT NULL,
|
||||
afk_timeout integer,
|
||||
allow_accessibility_detection boolean,
|
||||
animate_emoji boolean,
|
||||
animate_stickers integer,
|
||||
contact_sync_enabled boolean,
|
||||
convert_emoticons boolean,
|
||||
custom_status text,
|
||||
default_guilds_restricted boolean,
|
||||
detect_platform_accounts boolean,
|
||||
developer_mode boolean,
|
||||
disable_games_tab boolean,
|
||||
enable_tts_command boolean,
|
||||
explicit_content_filter integer,
|
||||
friend_source_flags text,
|
||||
gateway_connected boolean,
|
||||
gif_auto_play boolean,
|
||||
guild_folders text,
|
||||
guild_positions text,
|
||||
inline_attachment_media boolean,
|
||||
inline_embed_media boolean,
|
||||
locale character varying,
|
||||
message_display_compact boolean,
|
||||
native_phone_integration_enabled boolean,
|
||||
render_embeds boolean,
|
||||
render_reactions boolean,
|
||||
restricted_guilds text,
|
||||
show_current_game boolean,
|
||||
status character varying,
|
||||
stream_notifications_enabled boolean,
|
||||
theme character varying,
|
||||
timezone_offset integer
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE SEQUENCE public.user_settings_index_seq
|
||||
AS integer
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;`
|
||||
);
|
||||
|
||||
await queryRunner.query(`ALTER SEQUENCE public.user_settings_index_seq OWNED BY public.user_settings.index;`);
|
||||
await queryRunner.query(`CREATE TABLE public.users (
|
||||
id character varying NOT NULL,
|
||||
username character varying NOT NULL,
|
||||
discriminator character varying NOT NULL,
|
||||
avatar character varying,
|
||||
accent_color integer,
|
||||
banner character varying,
|
||||
theme_colors text,
|
||||
pronouns character varying,
|
||||
phone character varying,
|
||||
desktop boolean NOT NULL,
|
||||
mobile boolean NOT NULL,
|
||||
premium boolean NOT NULL,
|
||||
premium_type integer NOT NULL,
|
||||
bot boolean NOT NULL,
|
||||
bio character varying NOT NULL,
|
||||
system boolean NOT NULL,
|
||||
nsfw_allowed boolean NOT NULL,
|
||||
mfa_enabled boolean NOT NULL,
|
||||
totp_secret character varying,
|
||||
totp_last_ticket character varying,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
premium_since timestamp without time zone,
|
||||
verified boolean NOT NULL,
|
||||
disabled boolean NOT NULL,
|
||||
deleted boolean NOT NULL,
|
||||
email character varying,
|
||||
flags bigint NOT NULL,
|
||||
public_flags bigint NOT NULL,
|
||||
purchased_flags bigint NOT NULL,
|
||||
premium_usage_flags integer NOT NULL,
|
||||
rights bigint NOT NULL,
|
||||
data text NOT NULL,
|
||||
fingerprints text NOT NULL,
|
||||
extended_settings text NOT NULL,
|
||||
"settingsIndex" integer
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.valid_registration_tokens (
|
||||
token character varying NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
expires_at timestamp without time zone NOT NULL
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.voice_states (
|
||||
id character varying NOT NULL,
|
||||
guild_id character varying,
|
||||
channel_id character varying,
|
||||
user_id character varying,
|
||||
session_id character varying NOT NULL,
|
||||
token character varying,
|
||||
deaf boolean NOT NULL,
|
||||
mute boolean NOT NULL,
|
||||
self_deaf boolean NOT NULL,
|
||||
self_mute boolean NOT NULL,
|
||||
self_stream boolean,
|
||||
self_video boolean NOT NULL,
|
||||
suppress boolean NOT NULL,
|
||||
request_to_speak_timestamp timestamp without time zone
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`CREATE TABLE public.webhooks (
|
||||
id character varying NOT NULL,
|
||||
type integer NOT NULL,
|
||||
name character varying,
|
||||
avatar character varying,
|
||||
token character varying,
|
||||
guild_id character varying,
|
||||
channel_id character varying,
|
||||
application_id character varying,
|
||||
user_id character varying,
|
||||
source_guild_id character varying
|
||||
);`);
|
||||
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.members ALTER COLUMN index SET DEFAULT nextval('public.members_index_seq'::regclass);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.migrations ALTER COLUMN id SET DEFAULT nextval('public.migrations_id_seq'::regclass);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.user_settings ALTER COLUMN index SET DEFAULT nextval('public.user_settings_index_seq'::regclass);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.embed_cache ADD CONSTRAINT "PK_0abb7581d4efc5a8b1361389c5e" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "PK_18325f38ae6de43878487eff986" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.audit_logs ADD CONSTRAINT "PK_1bb179d048bbc581caa3b013439" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.categories ADD CONSTRAINT "PK_24dbc6126a28ff948da33e97d3b" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.config ADD CONSTRAINT "PK_26489c99ddbb4c91631ef5cc791" PRIMARY KEY (key);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.sessions ADD CONSTRAINT "PK_3238ef96f18b355b671619111bc" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.invites ADD CONSTRAINT "PK_33fd8a248db1cd832baa8aa25bf" PRIMARY KEY (code);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.backup_codes ADD CONSTRAINT "PK_34ab957382dbc57e8fb53f1638f" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.rate_limits ADD CONSTRAINT "PK_3b4449f1f5fc167d921ee619f65" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.security_settings ADD CONSTRAINT "PK_4aec436cf81177ae97a1bcec3c7" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.client_release ADD CONSTRAINT "PK_4c4ea258342d2d6ba1be0a71a43" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.templates ADD CONSTRAINT "PK_515948649ce0bbbe391de702ae5" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.attachments ADD CONSTRAINT "PK_5e1f050bcff31e3084a1d662412" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.connected_accounts ADD CONSTRAINT "PK_70416f1da0be645bb31da01c774" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_role_mentions ADD CONSTRAINT "PK_74dba92cc300452a6e14b83ed44" PRIMARY KEY ("messagesId", "rolesId");`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.teams ADD CONSTRAINT "PK_7e5523774a38b08a6236d322403" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_channel_mentions ADD CONSTRAINT "PK_85cb45351497cd9d06a79ced65e" PRIMARY KEY ("messagesId", "channelsId");`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.migrations ADD CONSTRAINT "PK_8c82d7f526340ab734260ea46be" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.applications ADD CONSTRAINT "PK_938c0a27255637bde919591888f" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.member_roles ADD CONSTRAINT "PK_951c1d72a0fd1da8760b4a1fd66" PRIMARY KEY (index, role_id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.emojis ADD CONSTRAINT "PK_9adb96a675f555c6169bad7ba62" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_user_mentions ADD CONSTRAINT "PK_9b9b6e245ad47a48dbd7605d4fb" PRIMARY KEY ("messagesId", "usersId");`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "PK_9e8795cfc899ab7bdaa831e8527" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.sticker_packs ADD CONSTRAINT "PK_a27381efea0f876f5d3233af655" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.users ADD CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.bans ADD CONSTRAINT "PK_a4d6f261bffa4615c62d756566a" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.voice_states ADD CONSTRAINT "PK_ada09a50c134fad1369b510e3ce" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.notes ADD CONSTRAINT "PK_af6206538ea96c4e77e9f400c3d" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.members ADD CONSTRAINT "PK_b4a6b8c2478e5df990909c6cf6a" PRIMARY KEY (index);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.relationships ADD CONSTRAINT "PK_ba20e2f5cf487408e08e4dcecaf" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.connection_config ADD CONSTRAINT "PK_bc0554f736ad71dde346549488a" PRIMARY KEY (key);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.channels ADD CONSTRAINT "PK_bc603823f3f741359c2339389f9" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.roles ADD CONSTRAINT "PK_c1433d71a4838793a49dcad46ab" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.team_members ADD CONSTRAINT "PK_ca3eae89dcf20c9fd95bf7460aa" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.recipients ADD CONSTRAINT "PK_de8fc5a9c364568f294798fe1e9" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.valid_registration_tokens ADD CONSTRAINT "PK_e0f5c8e3fcefe3134a092c50485" PRIMARY KEY (token);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.stickers ADD CONSTRAINT "PK_e1dafa4063a5532645cc2810374" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.read_states ADD CONSTRAINT "PK_e6956a804978f01b713b1ed58e2" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "PK_e7e7f2a51bd6d96a9ac2aa560f9" PRIMARY KEY (id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.user_settings ADD CONSTRAINT "PK_e81f8bb92802737337d35c00981" PRIMARY KEY (index);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_stickers ADD CONSTRAINT "PK_ed820c4093d0b8cd1d2bcf66087" PRIMARY KEY ("messagesId", "stickersId");`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.users ADD CONSTRAINT "REL_0c14beb78d8c5ccba66072adbc" UNIQUE ("settingsIndex");`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.applications ADD CONSTRAINT "REL_2ce5a55796fe4c2f77ece57a64" UNIQUE (bot_user_id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.notes ADD CONSTRAINT "UQ_74e6689b9568cc965b8bfc9150b" UNIQUE (owner_id, target_id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.templates ADD CONSTRAINT "UQ_be38737bf339baf63b1daeffb55" UNIQUE (code);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_05535bc695e9f7ee104616459d" ON public.messages USING btree (author_id);`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_0abf8b443321bd3cf7f81ee17a" ON public.read_states USING btree (channel_id, user_id);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_29d63eb1a458200851bc37d074" ON public.message_role_mentions USING btree ("rolesId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_2a27102ecd1d81b4582a436092" ON public.message_channel_mentions USING btree ("messagesId");`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" ON public.messages USING btree (channel_id, id);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_40bb6f23e7cc133292e92829d2" ON public.message_stickers USING btree ("messagesId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_5d7ddc8a5f9c167f548625e772" ON public.member_roles USING btree (index);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" ON public.messages USING btree (channel_id);`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_a0b2ff0a598df0b0d055934a17" ON public.relationships USING btree (from_id, to_id);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_a343387fc560ef378760681c23" ON public.message_user_mentions USING btree ("messagesId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_a8242cf535337a490b0feaea0b" ON public.message_role_mentions USING btree ("messagesId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_b831eb18ceebd28976239b1e2f" ON public.message_user_mentions USING btree ("usersId");`);
|
||||
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON public.members USING btree (id, guild_id);`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_bdb8c09e1464cabf62105bf4b9" ON public.message_channel_mentions USING btree ("channelsId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e22a70819d07659c7a71c112a1" ON public.message_stickers USING btree ("stickersId");`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_e9080e7a7997a0170026d5139c" ON public.member_roles USING btree (role_id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.voice_states ADD CONSTRAINT "FK_03779ef216d4b0358470d9cb748" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_05535bc695e9f7ee104616459d3" FOREIGN KEY (author_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.bans ADD CONSTRAINT "FK_07ad88c86d1f290d46748410d58" FOREIGN KEY (executor_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.sessions ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.users ADD CONSTRAINT "FK_0c14beb78d8c5ccba66072adbc7" FOREIGN KEY ("settingsIndex") REFERENCES public.user_settings(index);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "FK_0d523f6f997c86e052c49b1455f" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.invites ADD CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY (target_user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.teams ADD CONSTRAINT "FK_13f00abf7cb6096c43ecaf8c108" FOREIGN KEY (owner_user_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.invites ADD CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY (inviter_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.members ADD CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.stickers ADD CONSTRAINT "FK_193d551d852aca5347ef5c9f205" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.read_states ADD CONSTRAINT "FK_195f92e4dd1254a4e348c043763" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.notes ADD CONSTRAINT "FK_23e08e5b4481711d573e1abecdc" FOREIGN KEY (target_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.members ADD CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY (id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_role_mentions ADD CONSTRAINT "FK_29d63eb1a458200851bc37d074b" FOREIGN KEY ("rolesId") REFERENCES public.roles(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_channel_mentions ADD CONSTRAINT "FK_2a27102ecd1d81b4582a4360921" FOREIGN KEY ("messagesId") REFERENCES public.messages(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.applications ADD CONSTRAINT "FK_2ce5a55796fe4c2f77ece57a647" FOREIGN KEY (bot_user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.recipients ADD CONSTRAINT "FK_2f18ee1ba667f233ae86c0ea60e" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.channels ADD CONSTRAINT "FK_3274522d14af40540b1a883fc80" FOREIGN KEY (parent_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.channels ADD CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" FOREIGN KEY (owner_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "FK_3a285f4f49c40e0706d3018bc9f" FOREIGN KEY (source_guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.audit_logs ADD CONSTRAINT "FK_3cd01cd3ae7aab010310d96ac8e" FOREIGN KEY (target_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.invites ADD CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_stickers ADD CONSTRAINT "FK_40bb6f23e7cc133292e92829d28" FOREIGN KEY ("messagesId") REFERENCES public.messages(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.read_states ADD CONSTRAINT "FK_40da2fca4e0eaf7a23b5bfc5d34" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.templates ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY (source_guild_id) REFERENCES public.guilds(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.sticker_packs ADD CONSTRAINT "FK_448fafba4355ee1c837bbc865f1" FOREIGN KEY ("coverStickerId") REFERENCES public.stickers(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "FK_487a7af59d189f744fe394368fc" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.emojis ADD CONSTRAINT "FK_4b988e0db89d94cebcf07f598cc" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.bans ADD CONSTRAINT "FK_5999e8e449f80a236ff72023559" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_5d3ec1cb962de6488637fd779d6" FOREIGN KEY (application_id) REFERENCES public.applications(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.member_roles ADD CONSTRAINT "FK_5d7ddc8a5f9c167f548625e772e" FOREIGN KEY (index) REFERENCES public.members(index) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.voice_states ADD CONSTRAINT "FK_5fe1d5f931a67e85039c640001b" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.recipients ADD CONSTRAINT "FK_6157e8b6ba4e6e3089616481fe2" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY (message_reference_id) REFERENCES public.messages(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.attachments ADD CONSTRAINT "FK_623e10eec51ada466c5038979e3" FOREIGN KEY (message_id) REFERENCES public.messages(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.invites ADD CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.backup_codes ADD CONSTRAINT "FK_70066ea80d2f4b871beda32633b" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_86b9109b155eb70c0a2ca3b4b6d" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY (public_updates_channel_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.stickers ADD CONSTRAINT "FK_8f4ee73f2bb2325ff980502e158" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY (rules_channel_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.relationships ADD CONSTRAINT "FK_9af4194bab1250b1c584ae4f1d7" FOREIGN KEY (from_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.relationships ADD CONSTRAINT "FK_9c7f6b98a9843b76dce1b0c878b" FOREIGN KEY (to_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY (widget_channel_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.bans ADD CONSTRAINT "FK_9d3ab7dd180ebdd245cdb66ecad" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.voice_states ADD CONSTRAINT "FK_9f8d389866b40b6657edd026dd4" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_user_mentions ADD CONSTRAINT "FK_a343387fc560ef378760681c236" FOREIGN KEY ("messagesId") REFERENCES public.messages(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.applications ADD CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY (team_id) REFERENCES public.teams(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_role_mentions ADD CONSTRAINT "FK_a8242cf535337a490b0feaea0b4" FOREIGN KEY ("messagesId") REFERENCES public.messages(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_b0525304f2262b7014245351c76" FOREIGN KEY (member_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_b193588441b085352a4c0109423" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_user_mentions ADD CONSTRAINT "FK_b831eb18ceebd28976239b1e2f8" FOREIGN KEY ("usersId") REFERENCES public.users(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.audit_logs ADD CONSTRAINT "FK_bd2726fd31b35443f2245b93ba0" FOREIGN KEY (user_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_channel_mentions ADD CONSTRAINT "FK_bdb8c09e1464cabf62105bf4b9d" FOREIGN KEY ("channelsId") REFERENCES public.channels(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.channels ADD CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.team_members ADD CONSTRAINT "FK_c2bf4967c8c2a6b845dadfbf3d4" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.roles ADD CONSTRAINT "FK_c32c1ab1c4dc7dcb0278c4b1b8b" FOREIGN KEY (guild_id) REFERENCES public.guilds(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "FK_c3e5305461931763b56aa905f1c" FOREIGN KEY (application_id) REFERENCES public.applications(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY (system_channel_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.templates ADD CONSTRAINT "FK_d7374b7f8f5fbfdececa4fb62e1" FOREIGN KEY (creator_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.webhooks ADD CONSTRAINT "FK_df528cf77e82f8032230e7e37d8" FOREIGN KEY (channel_id) REFERENCES public.channels(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.message_stickers ADD CONSTRAINT "FK_e22a70819d07659c7a71c112a1f" FOREIGN KEY ("stickersId") REFERENCES public.stickers(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY (template_id) REFERENCES public.templates(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.applications ADD CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.stickers ADD CONSTRAINT "FK_e7cfa5cefa6661b3fb8fda8ce69" FOREIGN KEY (pack_id) REFERENCES public.sticker_packs(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.member_roles ADD CONSTRAINT "FK_e9080e7a7997a0170026d5139c1" FOREIGN KEY (role_id) REFERENCES public.roles(id) ON UPDATE CASCADE ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.connected_accounts ADD CONSTRAINT "FK_f47244225a6a1eac04a3463dd90" FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY (afk_channel_id) REFERENCES public.channels(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.messages ADD CONSTRAINT "FK_f83c04bcf1df4e5c0e7a52ed348" FOREIGN KEY (webhook_id) REFERENCES public.webhooks(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.notes ADD CONSTRAINT "FK_f9e103f8ae67cb1787063597925" FOREIGN KEY (owner_id) REFERENCES public.users(id) ON DELETE CASCADE;`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.emojis ADD CONSTRAINT "FK_fa7ddd5f9a214e28ce596548421" FOREIGN KEY (user_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.guilds ADD CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY (owner_id) REFERENCES public.users(id);`);
|
||||
await queryRunner.query(`ALTER TABLE ONLY public.team_members ADD CONSTRAINT "FK_fdad7d5768277e60c40e01cdcea" FOREIGN KEY (team_id) REFERENCES public.teams(id) ON DELETE CASCADE;`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
throw new Error("Can't revert this: just throw away your database lol");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,29 @@ export class guildChannelOrdering1696420827239 implements MigrationInterface {
|
||||
await queryRunner.query(`ALTER TABLE channels DROP COLUMN position`);
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// don't care actually, sorry.
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE channels ADD position integer NOT NULL DEFAULT 0`);
|
||||
|
||||
const guilds = await queryRunner.query(
|
||||
`SELECT id, channel_ordering FROM guilds`,
|
||||
undefined,
|
||||
true,
|
||||
);
|
||||
|
||||
for (const guild of guilds.records) {
|
||||
const channel_ordering: string[] = JSON.parse(guild.channel_ordering);
|
||||
|
||||
for (let i = 0; i < channel_ordering.length; i++) {
|
||||
const channel_id = channel_ordering[i];
|
||||
await queryRunner.query(
|
||||
`UPDATE channels SET position = $1 WHERE id = $2`,
|
||||
[i, channel_id],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE guilds DROP COLUMN channel_ordering`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,16 @@ export class MessageFlagsNotNull1713116476900 implements MigrationInterface {
|
||||
await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_old;");
|
||||
}
|
||||
|
||||
public async down(): Promise<void> {
|
||||
// dont care
|
||||
throw new Error("Migration down is not implemented.");
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
"ALTER TABLE messages RENAME COLUMN flags TO flags_new;",
|
||||
);
|
||||
await queryRunner.query(
|
||||
"ALTER TABLE messages ADD COLUMN flags integer;",
|
||||
);
|
||||
await queryRunner.query(
|
||||
"UPDATE messages SET flags = flags_new;",
|
||||
);
|
||||
await queryRunner.query("ALTER TABLE messages DROP COLUMN flags_new;");
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,6 @@ export class Badges1720628601997 implements MigrationInterface {
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "badge_ids"`);
|
||||
await queryRunner.query(`DROP TABLE "badges"`);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ export class RoleColors1752321571508 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "roles" ADD "colors" text`);
|
||||
await queryRunner.query(`UPDATE "roles" SET "colors" = jsonb_build_object('primary_color', "color") WHERE "colors" IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "roles" ALTER COLUMN "colors" SET NOT NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class RoleColorsSolidColor1752342900886 implements MigrationInterface {
|
||||
name = 'RoleColorsSolidColor1752342900886'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`UPDATE "roles" SET "colors" = jsonb_build_object('primary_color', "color") WHERE "colors" IS NULL`);
|
||||
await queryRunner.query(`ALTER TABLE "roles" ALTER COLUMN "colors" SET NOT NULL`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "roles" ALTER COLUMN "colors" DROP NOT NULL`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,12 +14,12 @@ export class MessagePinnedAt1752383879533 implements MigrationInterface {
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "messages" DROP COLUMN "pinned_at"`,
|
||||
);
|
||||
await queryRunner.query(`ALTER TABLE "messages" ADD "pinned" boolean`);
|
||||
await queryRunner.query(
|
||||
`UPDATE "messages" SET "pinned" = true WHERE "pinned_at" IS NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "messages" DROP COLUMN "pinned_at"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class DeleteBotUsersWithoutAnApplication1761113394664 implements MigrationInterface {
|
||||
name = "DeleteBotUsersWithoutAnApplication1761113394664";
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DELETE FROM users WHERE bot = true AND id NOT IN (SELECT bot_user_id FROM applications);`);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class FixGifStickersFormatType1762611552514 implements MigrationInterface {
|
||||
name = "FixGifStickersFormatType1762611552514";
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`UPDATE "stickers" SET "format_type" = 4 WHERE "format_type" = 0;`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InstanceBanTable1765578570423 implements MigrationInterface {
|
||||
name = 'InstanceBanTable1765578570423'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "instance_bans" ("id" character varying NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "reason" character varying NOT NULL, "user_id" character varying, "fingerprint" character varying, "ip_address" character varying, "is_from_other_instance_ban" boolean NOT NULL DEFAULT false, "origin_instance_ban_id" character varying, CONSTRAINT "REL_0b02d18d0d830f160c921192a3" UNIQUE ("origin_instance_ban_id"), CONSTRAINT "PK_3aa6e80a6d325601054892b1340" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`ALTER TABLE "instance_bans" ADD CONSTRAINT "FK_0b02d18d0d830f160c921192a30" FOREIGN KEY ("origin_instance_ban_id") REFERENCES "instance_bans"("id") ON DELETE SET NULL ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "instance_bans" DROP CONSTRAINT "FK_0b02d18d0d830f160c921192a30"`);
|
||||
await queryRunner.query(`DROP TABLE "instance_bans"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class InstanceBanAllowlist1765587835846 implements MigrationInterface {
|
||||
name = 'InstanceBanAllowlist1765587835846'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "instance_bans" ADD "is_allowlisted" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "instance_bans" DROP COLUMN "is_allowlisted"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class DropDefaultIPDataKey1765665440000 implements MigrationInterface {
|
||||
name = 'DropDefaultIPDataKey1765665440000'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`UPDATE "config" SET "value" = NULL WHERE "key" = 'security_ipdataApiKey' AND "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`UPDATE "config" SET "value" = '"eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"' WHERE "key" = 'security_ipdataApiKey' AND "value" IS NULL`);
|
||||
}
|
||||
|
||||
}
|
||||
+19
-28
@@ -22,6 +22,7 @@ import { green, red, yellow } from "picocolors";
|
||||
import { DataSource } from "typeorm";
|
||||
import { ConfigEntity } from "../entities/Config";
|
||||
import { Migration } from "../entities/Migration";
|
||||
import fs from "fs";
|
||||
|
||||
// UUID extension option is only supported with postgres
|
||||
// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
|
||||
@@ -33,12 +34,9 @@ if (!process.env) {
|
||||
config({ quiet: true });
|
||||
}
|
||||
|
||||
const dbConnectionString =
|
||||
process.env.DATABASE || path.join(process.cwd(), "database.db");
|
||||
const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
|
||||
|
||||
export const DatabaseType = dbConnectionString.includes("://")
|
||||
? dbConnectionString.split(":")[0]?.replace("+srv", "")
|
||||
: "sqlite";
|
||||
export const DatabaseType = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite";
|
||||
const isSqlite = DatabaseType.includes("sqlite");
|
||||
|
||||
export const DataSourceOptions = new DataSource({
|
||||
@@ -69,11 +67,7 @@ export async function initDatabase(): Promise<DataSource> {
|
||||
if (dbConnection) return dbConnection;
|
||||
|
||||
if (isSqlite) {
|
||||
console.log(
|
||||
`[Database] ${red(
|
||||
`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`,
|
||||
)}`,
|
||||
);
|
||||
console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
|
||||
}
|
||||
|
||||
if (!process.env.DB_SYNC) {
|
||||
@@ -94,6 +88,13 @@ export async function initDatabase(): Promise<DataSource> {
|
||||
|
||||
dbConnection = await DataSourceOptions.initialize();
|
||||
|
||||
if (DatabaseType === "sqlite") {
|
||||
console.log(`[Database] ${yellow("Warning: SQLite is not supported. Forcing sync, this may lead to data loss!")}`);
|
||||
await dbConnection.synchronize();
|
||||
console.log(`[Database] ${green("Connected")}`);
|
||||
return dbConnection;
|
||||
}
|
||||
|
||||
// Crude way of detecting if the migrations table exists.
|
||||
const dbExists = async () => {
|
||||
try {
|
||||
@@ -103,27 +104,17 @@ export async function initDatabase(): Promise<DataSource> {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
if (!(await dbExists())) {
|
||||
console.log(
|
||||
"[Database] This appears to be a fresh database. Synchronising.",
|
||||
);
|
||||
await dbConnection.synchronize();
|
||||
|
||||
// On next start, typeorm will try to run all the migrations again from beginning.
|
||||
// Manually insert every current migration to prevent this:
|
||||
await Promise.all(
|
||||
dbConnection.migrations.map((migration) =>
|
||||
Migration.insert({
|
||||
name: migration.name,
|
||||
timestamp: Date.now(),
|
||||
}),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
console.log("[Database] Applying missing migrations, if any.");
|
||||
await dbConnection.runMigrations();
|
||||
if (!(await dbExists())) {
|
||||
console.log("[Database] This appears to be a fresh database. Running initial DDL.");
|
||||
const qr = dbConnection.createQueryRunner();
|
||||
if (fs.existsSync(path.join(__dirname, "..", "migration", DatabaseType, "initial0.js")))
|
||||
await new (require(`../migration/${DatabaseType}-initial`).initial0)().up(qr);
|
||||
}
|
||||
|
||||
console.log("[Database] Applying missing migrations, if any.");
|
||||
await dbConnection.runMigrations();
|
||||
|
||||
console.log(`[Database] ${green("Connected")}`);
|
||||
|
||||
return dbConnection;
|
||||
|
||||
@@ -68,4 +68,17 @@ export class ElapsedTime {
|
||||
get days(): number {
|
||||
return this.totalDays;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
// Format: "DD.HH:MM:SS.mmmuuuNNN", with days being optional
|
||||
const daysPart = Math.floor(this.days) > 0 ? `${Math.floor(this.days)}.` : "";
|
||||
const hoursPart = Math.floor(this.hours).toString().padStart(2, "0");
|
||||
const minutesPart = Math.floor(this.minutes).toString().padStart(2, "0");
|
||||
const secondsPart = Math.floor(this.seconds).toString().padStart(2, "0");
|
||||
const millisecondsPart = Math.floor(this.milliseconds).toString().padStart(3, "0");
|
||||
const microsecondsPart = Math.floor(this.microseconds).toString().padStart(3, "0");
|
||||
const nanosecondsPart = Math.floor(this.nanoseconds).toString().padStart(3, "0");
|
||||
|
||||
return `${daysPart}${hoursPart}:${minutesPart}:${secondsPart}.${millisecondsPart}${microsecondsPart}${nanosecondsPart}`;
|
||||
}
|
||||
}
|
||||
+25
-38
@@ -20,31 +20,28 @@ import { Channel } from "amqplib";
|
||||
import { RabbitMQ } from "./RabbitMQ";
|
||||
import EventEmitter from "events";
|
||||
import { EVENT, Event } from "../interfaces";
|
||||
import { randomUUID } from "crypto";
|
||||
export const events = new EventEmitter();
|
||||
|
||||
export async function emitEvent(payload: Omit<Event, "created_at">) {
|
||||
const id = (payload.guild_id ||
|
||||
payload.channel_id ||
|
||||
payload.user_id) as string;
|
||||
const id = (payload.guild_id || payload.channel_id || payload.user_id) as string;
|
||||
if (!id) return console.error("event doesn't contain any id", payload);
|
||||
|
||||
if (RabbitMQ.connection) {
|
||||
const data =
|
||||
typeof payload.data === "object"
|
||||
? JSON.stringify(payload.data)
|
||||
: payload.data; // use rabbitmq for event transmission
|
||||
await RabbitMQ.channel?.assertExchange(id, "fanout", {
|
||||
durable: false,
|
||||
});
|
||||
const data = typeof payload.data === "object" ? JSON.stringify(payload.data) : payload.data; // use rabbitmq for event transmission
|
||||
const channel = await RabbitMQ.getSafeChannel();
|
||||
try {
|
||||
await channel.assertExchange(id, "fanout", {
|
||||
durable: false,
|
||||
});
|
||||
|
||||
// assertQueue isn't needed, because a queue will automatically created if it doesn't exist
|
||||
const successful = RabbitMQ.channel?.publish(
|
||||
id,
|
||||
"",
|
||||
Buffer.from(`${data}`),
|
||||
{ type: payload.event },
|
||||
);
|
||||
if (!successful) throw new Error("failed to send event");
|
||||
// assertQueue isn't needed, because a queue will automatically created if it doesn't exist
|
||||
const successful = channel.publish(id, "", Buffer.from(`${data}`), { type: payload.event });
|
||||
if (!successful) throw new Error("failed to send event");
|
||||
} catch (e) {
|
||||
// todo: should we retry publishng the event?
|
||||
console.log("[RabbitMQ] ", e);
|
||||
}
|
||||
} else if (process.env.EVENT_TRANSMISSION === "process") {
|
||||
process.send?.({ type: "event", event: payload, id } as ProcessEvent);
|
||||
} else {
|
||||
@@ -79,17 +76,11 @@ export interface ProcessEvent {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export async function listenEvent(
|
||||
event: string,
|
||||
callback: (event: EventOpts) => unknown,
|
||||
opts?: ListenEventOpts,
|
||||
) {
|
||||
export async function listenEvent(event: string, callback: (event: EventOpts) => unknown, opts?: ListenEventOpts): Promise<() => Promise<void>> {
|
||||
if (RabbitMQ.connection) {
|
||||
const channel = opts?.channel || RabbitMQ.channel;
|
||||
if (!channel)
|
||||
throw new Error(
|
||||
"[Events] An event was sent without an associated channel",
|
||||
);
|
||||
const rabbitMQChannel = await RabbitMQ.getSafeChannel();
|
||||
const channel = opts?.channel || rabbitMQChannel;
|
||||
if (!channel) throw new Error("[Events] An event was sent without an associated channel");
|
||||
return await rabbitListen(channel, event, callback, {
|
||||
acknowledge: opts?.acknowledge,
|
||||
});
|
||||
@@ -101,9 +92,7 @@ export async function listenEvent(
|
||||
|
||||
const listener = (msg: ProcessEvent) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
||||
msg.type === "event" &&
|
||||
msg.id === event &&
|
||||
callback({ ...msg.event, cancel });
|
||||
msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel });
|
||||
};
|
||||
|
||||
// TODO: assert the type is correct?
|
||||
@@ -124,20 +113,17 @@ export async function listenEvent(
|
||||
}
|
||||
}
|
||||
|
||||
async function rabbitListen(
|
||||
channel: Channel,
|
||||
id: string,
|
||||
callback: (event: EventOpts) => unknown,
|
||||
opts?: { acknowledge?: boolean },
|
||||
) {
|
||||
async function rabbitListen(channel: Channel, id: string, callback: (event: EventOpts) => unknown, opts?: { acknowledge?: boolean }): Promise<() => Promise<void>> {
|
||||
await channel.assertExchange(id, "fanout", { durable: false });
|
||||
const q = await channel.assertQueue("", {
|
||||
exclusive: true,
|
||||
autoDelete: true,
|
||||
});
|
||||
|
||||
const consumerTag = randomUUID();
|
||||
|
||||
const cancel = async () => {
|
||||
await channel.cancel(q.queue);
|
||||
await channel.cancel(consumerTag);
|
||||
await channel.unbindQueue(q.queue, id, "");
|
||||
};
|
||||
|
||||
@@ -163,6 +149,7 @@ async function rabbitListen(
|
||||
},
|
||||
{
|
||||
noAck: !opts?.acknowledge,
|
||||
consumerTag: consumerTag,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ export const RabbitMQ: {
|
||||
connection: ChannelModel | null;
|
||||
channel: Channel | null;
|
||||
init: () => Promise<void>;
|
||||
getSafeChannel: () => Promise<Channel>;
|
||||
} = {
|
||||
connection: null,
|
||||
channel: null,
|
||||
@@ -34,7 +35,38 @@ export const RabbitMQ: {
|
||||
timeout: 1000 * 60,
|
||||
});
|
||||
console.log(`[RabbitMQ] connected`);
|
||||
|
||||
// log connection errors
|
||||
this.connection.on("error", (err) => {
|
||||
console.error("[RabbitMQ] Connection Error:", err);
|
||||
});
|
||||
|
||||
this.connection.on("close", () => {
|
||||
console.error("[RabbitMQ] connection closed");
|
||||
// TODO: Add reconnection logic here if the connection crashes??
|
||||
// will be a pain since we will have to reconstruct entire state
|
||||
});
|
||||
|
||||
await this.getSafeChannel();
|
||||
},
|
||||
getSafeChannel: async function () {
|
||||
if (!this.connection) return Promise.reject();
|
||||
|
||||
if (this.channel) return this.channel;
|
||||
|
||||
this.channel = await this.connection.createChannel();
|
||||
console.log(`[RabbitMQ] channel created`);
|
||||
|
||||
// log channel errors
|
||||
this.channel.on("error", (err) => {
|
||||
console.error("[RabbitMQ] Channel Error:", err);
|
||||
});
|
||||
|
||||
this.channel.on("close", () => {
|
||||
console.log("[RabbitMQ] channel closed");
|
||||
this.channel = null;
|
||||
});
|
||||
|
||||
return this.channel;
|
||||
},
|
||||
};
|
||||
+22
-46
@@ -18,15 +18,12 @@
|
||||
|
||||
import jwt, { VerifyOptions } from "jsonwebtoken";
|
||||
import { Config } from "./Config";
|
||||
import { User } from "../entities";
|
||||
import { InstanceBan, User } from "../entities";
|
||||
import crypto from "node:crypto";
|
||||
import fs from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
// TODO: dont use deprecated APIs lol
|
||||
import {
|
||||
FindOptionsRelationByString,
|
||||
FindOptionsSelectByString,
|
||||
} from "typeorm";
|
||||
import { FindManyOptions, FindOptions, FindOptionsRelationByString, FindOptionsSelect, FindOptionsSelectByString, FindOptionsWhere } from "typeorm";
|
||||
import * as console from "node:console";
|
||||
|
||||
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
|
||||
@@ -37,7 +34,7 @@ export type UserTokenData = {
|
||||
};
|
||||
|
||||
function logAuth(text: string) {
|
||||
if(process.env.LOG_AUTH !== "true") return;
|
||||
if (process.env.LOG_AUTH !== "true") return;
|
||||
console.log(`[AUTH] ${text}`);
|
||||
}
|
||||
|
||||
@@ -51,6 +48,8 @@ export const checkToken = (
|
||||
opts?: {
|
||||
select?: FindOptionsSelectByString<User>;
|
||||
relations?: FindOptionsRelationByString;
|
||||
ipAddress?: string;
|
||||
fingerprint?: string;
|
||||
},
|
||||
): Promise<UserTokenData> =>
|
||||
new Promise((resolve, reject) => {
|
||||
@@ -66,15 +65,7 @@ export const checkToken = (
|
||||
|
||||
const user = await User.findOne({
|
||||
where: { id: decoded.id },
|
||||
select: [
|
||||
...(opts?.select || []),
|
||||
"id",
|
||||
"bot",
|
||||
"disabled",
|
||||
"deleted",
|
||||
"rights",
|
||||
"data",
|
||||
],
|
||||
select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"],
|
||||
relations: opts?.relations,
|
||||
});
|
||||
|
||||
@@ -84,10 +75,7 @@ export const checkToken = (
|
||||
}
|
||||
|
||||
// we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
|
||||
if (
|
||||
decoded.iat * 1000 <
|
||||
new Date(user.data.valid_tokens_since).setSeconds(0, 0)
|
||||
) {
|
||||
if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) {
|
||||
logAuth("validateUser rejected: Token not yet valid");
|
||||
return rejectAndLog(reject, "Invalid Token");
|
||||
}
|
||||
@@ -96,11 +84,18 @@ export const checkToken = (
|
||||
logAuth("validateUser rejected: User disabled");
|
||||
return rejectAndLog(reject, "User disabled");
|
||||
}
|
||||
|
||||
if (user.deleted) {
|
||||
logAuth("validateUser rejected: User deleted");
|
||||
return rejectAndLog(reject, "User not found");
|
||||
}
|
||||
|
||||
const banReasons = await InstanceBan.findInstanceBans({ userId: user.id, ipAddress: opts?.ipAddress, fingerprint: opts?.fingerprint, propagateBan: true });
|
||||
if (banReasons.length > 0) {
|
||||
logAuth("validateUser rejected: User banned for reasons: " + banReasons.join(", "));
|
||||
return rejectAndLog(reject, "Invalid Token");
|
||||
}
|
||||
|
||||
logAuth("validateUser success: " + JSON.stringify({ decoded, user }));
|
||||
return resolve({ decoded, user });
|
||||
};
|
||||
@@ -109,21 +104,11 @@ export const checkToken = (
|
||||
if (!dec) return reject("Could not parse token");
|
||||
logAuth("Decoded token: " + JSON.stringify(dec));
|
||||
|
||||
if (dec.header.alg == "HS256") {
|
||||
jwt.verify(
|
||||
token,
|
||||
Config.get().security.jwtSecret,
|
||||
JWTOptions,
|
||||
validateUser,
|
||||
);
|
||||
if (dec.header.alg == "HS256" && Config.get().security.jwtSecret !== null) {
|
||||
jwt.verify(token, Config.get().security.jwtSecret!, JWTOptions, validateUser);
|
||||
} else if (dec.header.alg == "ES512") {
|
||||
loadOrGenerateKeypair().then((keyPair) => {
|
||||
jwt.verify(
|
||||
token,
|
||||
keyPair.publicKey,
|
||||
{ algorithms: ["ES512"] },
|
||||
validateUser,
|
||||
);
|
||||
jwt.verify(token, keyPair.publicKey, { algorithms: ["ES512"] }, validateUser);
|
||||
});
|
||||
} else return reject("Invalid token algorithm");
|
||||
});
|
||||
@@ -152,7 +137,7 @@ let cachedKeypair: {
|
||||
privateKey: crypto.KeyObject;
|
||||
publicKey: crypto.KeyObject;
|
||||
fingerprint: string;
|
||||
}
|
||||
};
|
||||
|
||||
// Get ECDSA keypair from file or generate it
|
||||
export async function loadOrGenerateKeypair() {
|
||||
@@ -176,10 +161,7 @@ export async function loadOrGenerateKeypair() {
|
||||
let publicKey: crypto.KeyObject;
|
||||
|
||||
if (existsSync("jwt.key") && existsSync("jwt.key.pub")) {
|
||||
const [loadedPrivateKey, loadedPublicKey] = await Promise.all([
|
||||
fs.readFile("jwt.key"),
|
||||
fs.readFile("jwt.key.pub"),
|
||||
]);
|
||||
const [loadedPrivateKey, loadedPublicKey] = await Promise.all([fs.readFile("jwt.key"), fs.readFile("jwt.key.pub")]);
|
||||
|
||||
privateKey = crypto.createPrivateKey(loadedPrivateKey);
|
||||
publicKey = crypto.createPublicKey(loadedPublicKey);
|
||||
@@ -192,14 +174,8 @@ export async function loadOrGenerateKeypair() {
|
||||
publicKey = res.publicKey;
|
||||
|
||||
await Promise.all([
|
||||
fs.writeFile(
|
||||
"jwt.key",
|
||||
privateKey.export({ format: "pem", type: "sec1" }),
|
||||
),
|
||||
fs.writeFile(
|
||||
"jwt.key.pub",
|
||||
publicKey.export({ format: "pem", type: "spki" }),
|
||||
),
|
||||
fs.writeFile("jwt.key", privateKey.export({ format: "pem", type: "sec1" })),
|
||||
fs.writeFile("jwt.key.pub", publicKey.export({ format: "pem", type: "spki" })),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -209,5 +185,5 @@ export async function loadOrGenerateKeypair() {
|
||||
.digest("hex");
|
||||
|
||||
lastFsCheck = Date.now();
|
||||
return cachedKeypair = { privateKey, publicKey, fingerprint };
|
||||
return (cachedKeypair = { privateKey, publicKey, fingerprint });
|
||||
}
|
||||
+23
-18
@@ -19,13 +19,14 @@
|
||||
import { Fido2Lib } from "fido2-lib";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { Config } from "./Config";
|
||||
import { loadOrGenerateKeypair } from "./Token";
|
||||
|
||||
const jwtSignOptions: jwt.SignOptions = {
|
||||
algorithm: "HS256",
|
||||
algorithm: "ES512",
|
||||
expiresIn: "5m",
|
||||
};
|
||||
const jwtVerifyOptions: jwt.VerifyOptions = {
|
||||
algorithms: ["HS256"],
|
||||
algorithms: ["ES512"],
|
||||
};
|
||||
|
||||
export const WebAuthn: {
|
||||
@@ -44,28 +45,32 @@ export async function generateWebAuthnTicket(
|
||||
challenge: string,
|
||||
): Promise<string> {
|
||||
return new Promise((res, rej) => {
|
||||
jwt.sign(
|
||||
{ challenge },
|
||||
Config.get().security.jwtSecret,
|
||||
jwtSignOptions,
|
||||
(err, token) => {
|
||||
if (err || !token) return rej(err || "no token");
|
||||
return res(token);
|
||||
},
|
||||
loadOrGenerateKeypair().then(kp=>
|
||||
jwt.sign(
|
||||
{ challenge },
|
||||
kp.privateKey,
|
||||
jwtSignOptions,
|
||||
(err, token) => {
|
||||
if (err || !token) return rej(err || "no token");
|
||||
return res(token);
|
||||
},
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export async function verifyWebAuthnToken(token: string) {
|
||||
return new Promise((res, rej) => {
|
||||
jwt.verify(
|
||||
token,
|
||||
Config.get().security.jwtSecret,
|
||||
jwtVerifyOptions,
|
||||
async (err, decoded) => {
|
||||
if (err) return rej(err);
|
||||
return res(decoded);
|
||||
},
|
||||
loadOrGenerateKeypair().then(kp=>
|
||||
jwt.verify(
|
||||
token,
|
||||
kp.publicKey,
|
||||
jwtVerifyOptions,
|
||||
async (err, decoded) => {
|
||||
if (err) return rej(err);
|
||||
return res(decoded);
|
||||
},
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user