summary refs log tree commit diff
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-04-20 18:30:17 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-04-20 18:30:17 +1000
commitd35f7b7fcf0a18904e4372dfeaa8b297d4d2ffdc (patch)
treefc043057061b1264800cb6899fc9aec4ad1c08fc
parentTrying my hand at implementing desktop voice, magic packets courtesy of that ... (diff)
parentTry catch cpu log (diff)
downloadserver-d35f7b7fcf0a18904e4372dfeaa8b297d4d2ffdc.tar.xz
Merge branch 'master' into maddyrtc
-rw-r--r--README.md4
-rw-r--r--api/locales/he/common.json14
-rw-r--r--api/locales/sv/auth.json20
-rw-r--r--api/locales/sv/common.json28
-rw-r--r--api/package-lock.json18
-rw-r--r--api/scripts/droptables.sql2
-rw-r--r--api/src/middlewares/Authentication.ts1
-rw-r--r--api/src/routes/channels/#channel_id/invites.ts3
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/ack.ts3
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/index.ts44
-rw-r--r--api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts2
-rw-r--r--api/src/routes/channels/#channel_id/messages/index.ts7
-rw-r--r--api/src/routes/downloads.ts6
-rw-r--r--api/src/routes/guilds/#guild_id/index.ts12
-rw-r--r--api/src/routes/guilds/#guild_id/members/index.ts1
-rw-r--r--api/src/routes/guilds/index.ts7
-rw-r--r--api/src/routes/invites/index.ts2
-rw-r--r--api/src/routes/scheduled-maintenances/upcoming_json.ts12
-rw-r--r--api/src/routes/store/published-listings/applications.ts2
-rw-r--r--api/src/routes/store/published-listings/skus.ts2
-rw-r--r--api/src/routes/updates.ts12
-rw-r--r--api/src/routes/users/@me/notes.ts35
-rw-r--r--api/src/start.ts7
-rw-r--r--api/src/util/handlers/Message.ts14
-rw-r--r--api/src/util/handlers/route.ts3
-rw-r--r--api/src/util/utility/passwordStrength.ts21
-rw-r--r--bundle/package-lock.json10
-rw-r--r--bundle/scripts/benchmark/connections.js7
-rw-r--r--bundle/src/start.ts7
-rw-r--r--bundle/src/stats.ts8
-rw-r--r--cdn/package-lock.json12
-rw-r--r--gateway/package-lock.json20
-rw-r--r--gateway/src/opcodes/Identify.ts10
-rw-r--r--util/package-lock.json12
-rw-r--r--util/src/entities/Channel.ts6
-rw-r--r--util/src/entities/ClientRelease.ts (renamed from util/src/entities/ClientRelase.ts)4
-rw-r--r--util/src/entities/Config.ts10
-rw-r--r--util/src/entities/Encryption.ts35
-rw-r--r--util/src/entities/Guild.ts12
-rw-r--r--util/src/entities/Member.ts13
-rw-r--r--util/src/entities/Message.ts9
-rw-r--r--util/src/entities/ReadState.ts3
-rw-r--r--util/src/entities/User.ts20
-rw-r--r--util/src/entities/index.ts2
-rw-r--r--util/src/interfaces/Event.ts1
-rw-r--r--util/src/migrations/1648643945733-ReleaseTypo.ts16
-rw-r--r--util/src/util/Email.ts7
-rw-r--r--util/src/util/Intents.ts41
-rw-r--r--util/src/util/Rights.ts10
49 files changed, 391 insertions, 166 deletions
diff --git a/README.md b/README.md
index 1bcea1e3..f2743ed1 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
 </p>
 <h1 align="center">Fosscord Server</h1>
 
-<p>
+<p align="center">
   <a href="https://discord.gg/ZrnGQP6p3d">
     <img src="https://img.shields.io/discord/806142446094385153?color=7489d5&logo=discord&logoColor=ffffff" />
   </a>
@@ -30,6 +30,6 @@ This repository contains:
 
 -   [Contributing](https://docs.fosscord.com/contributing/server/)
 
-## [Setup](https://docs.fosscord.com/setup/server/)
+## [Setup](https://docs.fosscord.com/server/setup/)
 
 -   [Download](https://github.com/fosscord/fosscord-server/releases)
diff --git a/api/locales/he/common.json b/api/locales/he/common.json
index 8bb9c042..9e72e941 100644
--- a/api/locales/he/common.json
+++ b/api/locales/he/common.json
@@ -7,12 +7,12 @@
 		"BASE_TYPE_BOOLEAN": "This field must be a boolean",
 		"BASE_TYPE_CHOICES": "This field must be one of ({{types}})",
 		"BASE_TYPE_CLASS": "This field must be an instance of {{type}}",
-		"BASE_TYPE_OBJECT": "This field must be an object",
-		"BASE_TYPE_ARRAY": "This field must be an array",
-		"UNKOWN_FIELD": "Unknown key: {{key}}",
-		"BASE_TYPE_CONSTANT": "This field must be {{value}}",
-		"EMAIL_TYPE_INVALID_EMAIL": "Not a well-formed email address",
-		"DATE_TYPE_PARSE": "Could not parse {{date}}. Should be ISO8601",
-		"BASE_TYPE_BAD_LENGTH": "Must be between {{length}} in length"
+		"BASE_TYPE_OBJECT": "שדה זה חייב להיות אובייקט",
+		"BASE_TYPE_ARRAY": "שדה זה חייב להיות מערך",
+		"UNKOWN_FIELD": "מפתח לא ידוע: {{key}}",
+		"BASE_TYPE_CONSTANT": "שדה זה להיות {{value}}",
+		"EMAIL_TYPE_INVALID_EMAIL": "כתובת דואר אלקטרוני לא חוקית",
+		"DATE_TYPE_PARSE": "לא ניתן לנתח {{date}}. צריך להיות ISO8601",
+		"BASE_TYPE_BAD_LENGTH": "האורך חייב להיות בין {{length}}"
 	}
 }
diff --git a/api/locales/sv/auth.json b/api/locales/sv/auth.json
index e19547a0..04e55752 100644
--- a/api/locales/sv/auth.json
+++ b/api/locales/sv/auth.json
@@ -1,16 +1,16 @@
 {
 	"login": {
-		"INVALID_LOGIN": "E-Mail or Phone not found",
-		"INVALID_PASSWORD": "Invalid Password",
-		"ACCOUNT_DISABLED": "This account is disabled"
+		"INVALID_LOGIN": "E-post eller telefon hittades inte",
+		"INVALID_PASSWORD": "Ogiltigt lösenord",
+		"ACCOUNT_DISABLED": "Detta konto är inaktiverat"
 	},
 	"register": {
-		"REGISTRATION_DISABLED": "New user registration is disabled",
-		"INVITE_ONLY": "You must be invited to register",
-		"EMAIL_INVALID": "Invalid Email",
-		"EMAIL_ALREADY_REGISTERED": "Email is already registered",
-		"DATE_OF_BIRTH_UNDERAGE": "You need to be {{years}} years or older",
-		"CONSENT_REQUIRED": "You must agree to the Terms of Service and Privacy Policy.",
-		"USERNAME_TOO_MANY_USERS": "Too many users have this username, please try another"
+		"REGISTRATION_DISABLED": "Registrering av nya användare är inaktiverat",
+		"INVITE_ONLY": "Du måste vara inbjuden för att registrera dig",
+		"EMAIL_INVALID": "Ogiltig e-post",
+		"EMAIL_ALREADY_REGISTERED": "E-postadressen är redan registrerad",
+		"DATE_OF_BIRTH_UNDERAGE": "Du måste vara {{years}} år eller äldre",
+		"CONSENT_REQUIRED": "Du måste godkänna användarvillkoren och sekretesspolicyn.",
+		"USERNAME_TOO_MANY_USERS": "För många användare har detta användarnamn, försök med ett annat"
 	}
 }
diff --git a/api/locales/sv/common.json b/api/locales/sv/common.json
index 8bb9c042..56c02a64 100644
--- a/api/locales/sv/common.json
+++ b/api/locales/sv/common.json
@@ -1,18 +1,18 @@
 {
 	"field": {
-		"BASE_TYPE_REQUIRED": "This field is required",
-		"BASE_TYPE_STRING": "This field must be a string",
-		"BASE_TYPE_NUMBER": "This field must be a number",
-		"BASE_TYPE_BIGINT": "This field must be a bigint",
-		"BASE_TYPE_BOOLEAN": "This field must be a boolean",
-		"BASE_TYPE_CHOICES": "This field must be one of ({{types}})",
-		"BASE_TYPE_CLASS": "This field must be an instance of {{type}}",
-		"BASE_TYPE_OBJECT": "This field must be an object",
-		"BASE_TYPE_ARRAY": "This field must be an array",
-		"UNKOWN_FIELD": "Unknown key: {{key}}",
-		"BASE_TYPE_CONSTANT": "This field must be {{value}}",
-		"EMAIL_TYPE_INVALID_EMAIL": "Not a well-formed email address",
-		"DATE_TYPE_PARSE": "Could not parse {{date}}. Should be ISO8601",
-		"BASE_TYPE_BAD_LENGTH": "Must be between {{length}} in length"
+		"BASE_TYPE_REQUIRED": "Detta fältet krävs",
+		"BASE_TYPE_STRING": "Detta fält måste vara en sträng",
+		"BASE_TYPE_NUMBER": "Detta fält måste vara ett nummer",
+		"BASE_TYPE_BIGINT": "Detta fält måste vara av typen bigint",
+		"BASE_TYPE_BOOLEAN": "Detta fält måste vara booleskt",
+		"BASE_TYPE_CHOICES": "Detta fält måste vara av typen av ett av följande ({{types}})",
+		"BASE_TYPE_CLASS": "Det här fältet måste vara en instans av {{type}}",
+		"BASE_TYPE_OBJECT": "Detta fält måste vara ett objekt",
+		"BASE_TYPE_ARRAY": "Detta fält måste vara en array",
+		"UNKOWN_FIELD": "Okänd nyckel: {{key}}",
+		"BASE_TYPE_CONSTANT": "Det här fältet måste vara {{value}}",
+		"EMAIL_TYPE_INVALID_EMAIL": "E-postadressen har inte korrekt format",
+		"DATE_TYPE_PARSE": "Kunde inte tolka {{date}}. Bör vara ISO8601",
+		"BASE_TYPE_BAD_LENGTH": "Måste vara mellan {{length}} i längd"
 	}
 }
diff --git a/api/package-lock.json b/api/package-lock.json
index 1aa41056..de889188 100644
--- a/api/package-lock.json
+++ b/api/package-lock.json
@@ -4607,8 +4607,9 @@
 			}
 		},
 		"../util/node_modules/minimist": {
-			"version": "1.2.5",
-			"license": "MIT"
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"../util/node_modules/minipass": {
 			"version": "2.9.0",
@@ -13196,8 +13197,9 @@
 			}
 		},
 		"node_modules/minimist": {
-			"version": "1.2.5",
-			"license": "MIT"
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"node_modules/minipass": {
 			"version": "3.1.5",
@@ -19764,7 +19766,9 @@
 					}
 				},
 				"minimist": {
-					"version": "1.2.5"
+					"version": "1.2.6",
+					"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+					"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 				},
 				"minipass": {
 					"version": "2.9.0",
@@ -24388,7 +24392,9 @@
 			}
 		},
 		"minimist": {
-			"version": "1.2.5"
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"minipass": {
 			"version": "3.1.5",
diff --git a/api/scripts/droptables.sql b/api/scripts/droptables.sql
index 57d1b271..8a852048 100644
--- a/api/scripts/droptables.sql
+++ b/api/scripts/droptables.sql
@@ -26,6 +26,6 @@ DROP TABLE webhooks;
 DROP TABLE channels;
 DROP TABLE members;
 DROP TABLE guilds;
-DROP TABLE client_relase;
+DROP TABLE client_release;
 -- DROP TABLE users;
 -- DROP TABLE config;
\ No newline at end of file
diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts
index 429cf11e..5a08caf3 100644
--- a/api/src/middlewares/Authentication.ts
+++ b/api/src/middlewares/Authentication.ts
@@ -15,6 +15,7 @@ export const NO_AUTHORIZATION_ROUTES = [
 	"/experiments",
 	"/updates",
 	"/downloads/",
+	"/scheduled-maintenances/upcoming.json",
 	// Public kubernetes integration
 	"/-/readyz",
 	"/-/healthz",
diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts
index 6d2c625d..9c361164 100644
--- a/api/src/routes/channels/#channel_id/invites.ts
+++ b/api/src/routes/channels/#channel_id/invites.ts
@@ -19,7 +19,8 @@ export interface InviteCreateSchema {
 	target_user_type?: number;
 }
 
-router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE" }), async (req: Request, res: Response) => {
+router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }),
+			async (req: Request, res: Response) => {
 	const { user_id } = req;
 	const { channel_id } = req.params;
 	const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] });
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
index 208c1da4..885c5eca 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -4,8 +4,9 @@ import { route } from "@fosscord/api";
 
 const router = Router();
 
-// TODO: check if message exists
+// TODO: public read receipts & privacy scoping
 // TODO: send read state event to all channel members
+// TODO: advance-only notification cursor
 
 export interface MessageAcknowledgeSchema {
 	manual?: boolean;
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
index 7f7de264..a27c71e1 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -1,4 +1,4 @@
-import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util";
+import { Channel, emitEvent, getPermission, getRights, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util";
 import { Router, Response, Request } from "express";
 import { route } from "@fosscord/api";
 import { handleMessage, postHandleMessage } from "@fosscord/api";
@@ -7,18 +7,23 @@ import { MessageCreateSchema } from "../index";
 const router = Router();
 // TODO: message content/embed string length limit
 
-router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
+router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id } = req.params;
 	var body = req.body as MessageCreateSchema;
 
 	const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
 
 	const permissions = await getPermission(req.user_id, undefined, channel_id);
-
-	if (req.user_id !== message.author_id) {
-		permissions.hasThrow("MANAGE_MESSAGES");
-		body = { flags: body.flags }; // admins can only suppress embeds of other messages
-	}
+	
+	const rights = await getRights(req.user_id);
+
+	if ((req.user_id !== message.author_id)) {
+		if (!rights.has("MANAGE_MESSAGES")) {
+			permissions.hasThrow("MANAGE_MESSAGES");
+			body = { flags: body.flags };
+// guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
+		}
+	} else rights.hasThrow("SELF_EDIT_MESSAGES");
 
 	const new_message = await handleMessage({
 		...message,
@@ -46,17 +51,32 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE
 	return res.json(message);
 });
 
-// permission check only if deletes messagr from other user
+router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => {
+	const { message_id, channel_id } = req.params;
+
+	const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] });
+
+	const permissions = await getPermission(req.user_id, undefined, channel_id);
+	
+	if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY");
+
+	return res.json(message);
+});
+
 router.delete("/", route({}), async (req: Request, res: Response) => {
 	const { message_id, channel_id } = req.params;
 
 	const channel = await Channel.findOneOrFail({ id: channel_id });
 	const message = await Message.findOneOrFail({ id: message_id });
+	
+	const rights = await getRights(req.user_id);
 
-	if (message.author_id !== req.user_id) {
-		const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
-		permission.hasThrow("MANAGE_MESSAGES");
-	}
+	if ((message.author_id !== req.user_id)) {
+		if (!rights.has("MANAGE_MESSAGES")) {
+			const permission = await getPermission(req.user_id, channel.guild_id, channel_id);
+			permission.hasThrow("MANAGE_MESSAGES");
+		}
+	} else rights.hasThrow("SELF_DELETE_MESSAGES");
 
 	await Message.delete({ id: message_id });
 
diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
index 6b6a66b2..d93cf70f 100644
--- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -101,7 +101,7 @@ router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request
 	res.json(users);
 });
 
-router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY" }), async (req: Request, res: Response) => {
+router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), async (req: Request, res: Response) => {
 	const { message_id, channel_id, user_id } = req.params;
 	if (user_id !== "@me") throw new HTTPError("Invalid user");
 	const emoji = getEmoji(req.params.emoji);
diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts
index 2fd08b04..af0ae32d 100644
--- a/api/src/routes/channels/#channel_id/messages/index.ts
+++ b/api/src/routes/channels/#channel_id/messages/index.ts
@@ -8,6 +8,7 @@ import {
 	Embed,
 	emitEvent,
 	getPermission,
+	getRights,
 	Message,
 	MessageCreateEvent,
 	uploadFile,
@@ -119,7 +120,7 @@ router.get("/", async (req: Request, res: Response) => {
 				delete x.user_ids;
 			});
 			// @ts-ignore
-			if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null };
+			if (!x.author) x.author = { id: "4", discriminator: "0000", username: "Fosscord Ghost", public_flags: "0", avatar: null };
 			x.attachments?.forEach((y: any) => {
 				// dynamically set attachment proxy_url in case the endpoint changed
 				const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`;
@@ -149,7 +150,7 @@ const messageUpload = multer({
 }); // max upload 50 mb
 
 // TODO: dynamically change limit of MessageCreateSchema with config
-// TODO: check: sum of all characters in an embed structure must not exceed 6000 characters
+// TODO: check: sum of all characters in an embed structure must not exceed instance limits
 
 // https://discord.com/developers/docs/resources/channel#create-message
 // TODO: text channel slowdown
@@ -167,7 +168,7 @@ router.post(
 
 		next();
 	},
-	route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }),
+	route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }),
 	async (req: Request, res: Response) => {
 		const { channel_id } = req.params;
 		var body = req.body as MessageCreateSchema;
diff --git a/api/src/routes/downloads.ts b/api/src/routes/downloads.ts
index ad78b62f..ddfc080c 100644
--- a/api/src/routes/downloads.ts
+++ b/api/src/routes/downloads.ts
@@ -1,6 +1,6 @@
 import { Router, Response, Request } from "express";
 import { route } from "@fosscord/api";
-import { Relase, Config } from "@fosscord/util";
+import { Release, Config } from "@fosscord/util";
 
 const router = Router();
 
@@ -12,9 +12,9 @@ router.get("/:branch", route({}), async (req: Request, res: Response) => {
 
 	if(!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404)
 
-	const relase = await Relase.findOneOrFail({ name: client.relases.upstreamVersion });
+	const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion });
 
-	res.redirect(relase[`win_url`]);
+	res.redirect(release[`win_url`]);
 });
 
 export default router;
diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts
index 991c3f93..4ec3df72 100644
--- a/api/src/routes/guilds/#guild_id/index.ts
+++ b/api/src/routes/guilds/#guild_id/index.ts
@@ -1,5 +1,5 @@
 import { Request, Response, Router } from "express";
-import { emitEvent, getPermission, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util";
+import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util";
 import { HTTPError } from "lambert-server";
 import { route } from "@fosscord/api";
 import "missing-native-js-functions";
@@ -37,9 +37,17 @@ router.get("/", route({}), async (req: Request, res: Response) => {
 	return res.send(guild);
 });
 
-router.patch("/", route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => {
+router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res: Response) => {
 	const body = req.body as GuildUpdateSchema;
 	const { guild_id } = req.params;
+	
+	
+	const rights = await getRights(req.user_id);
+	const permission = await getPermission(req.user_id, guild_id);
+	
+	if (!rights.has("MANAGE_GUILDS")||!permission.has("MANAGE_GUILD"))
+		throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD");
+	
 	// TODO: guild update check image
 
 	if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts
index 386276c8..b730a4e7 100644
--- a/api/src/routes/guilds/#guild_id/members/index.ts
+++ b/api/src/routes/guilds/#guild_id/members/index.ts
@@ -6,7 +6,6 @@ import { HTTPError } from "lambert-server";
 
 const router = Router();
 
-// TODO: not allowed for user -> only allowed for bots with privileged intents
 // TODO: send over websocket
 // TODO: check for GUILD_MEMBERS intent
 
diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts
index 7b676211..10721413 100644
--- a/api/src/routes/guilds/index.ts
+++ b/api/src/routes/guilds/index.ts
@@ -1,5 +1,5 @@
 import { Router, Request, Response } from "express";
-import { Role, Guild, Snowflake, Config, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util";
+import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util";
 import { route } from "@fosscord/api";
 import { ChannelModifySchema } from "../channels/#channel_id";
 
@@ -20,12 +20,13 @@ export interface GuildCreateSchema {
 
 //TODO: create default channel
 
-router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: Response) => {
+router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), async (req: Request, res: Response) => {
 	const body = req.body as GuildCreateSchema;
 
 	const { maxGuilds } = Config.get().limits.user;
 	const guild_count = await Member.count({ id: req.user_id });
-	if (guild_count >= maxGuilds) {
+	const rights = await getRights(req.user_id);
+	if ((guild_count >= maxGuilds)&&!rights.has("MANAGE_GUILDS")) {
 		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds);
 	}
 
diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts
index 37e9e05a..21da2d18 100644
--- a/api/src/routes/invites/index.ts
+++ b/api/src/routes/invites/index.ts
@@ -13,7 +13,7 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
 	res.status(200).send(invite);
 });
 
-router.post("/:code", route({}), async (req: Request, res: Response) => {
+router.post("/:code", route({right: "JOIN_GUILDS"}), async (req: Request, res: Response) => {
 	const { code } = req.params;
     const { guild_id } = await Invite.findOneOrFail({ code })
 	const { features } = await Guild.findOneOrFail({ id: guild_id});
diff --git a/api/src/routes/scheduled-maintenances/upcoming_json.ts b/api/src/routes/scheduled-maintenances/upcoming_json.ts
new file mode 100644
index 00000000..83092e44
--- /dev/null
+++ b/api/src/routes/scheduled-maintenances/upcoming_json.ts
@@ -0,0 +1,12 @@
+import { Router, Request, Response } from "express";
+import { route } from "@fosscord/api";
+const router = Router();
+
+router.get("/scheduled-maintenances/upcoming.json",route({}), async (req: Request, res: Response) => {
+	res.json({
+  "page": {},
+  "scheduled_maintenances": {}
+  });
+});
+
+export default router;
diff --git a/api/src/routes/store/published-listings/applications.ts b/api/src/routes/store/published-listings/applications.ts
index f06a01e4..060a4c3d 100644
--- a/api/src/routes/store/published-listings/applications.ts
+++ b/api/src/routes/store/published-listings/applications.ts
@@ -18,7 +18,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
 			access_type: 2,
 			name: "",
 			features: [],
-			relase_date: "",
+			release_date: "",
 			premium: false,
 			slug: "",
 			flags: 4,
diff --git a/api/src/routes/store/published-listings/skus.ts b/api/src/routes/store/published-listings/skus.ts
index f06a01e4..060a4c3d 100644
--- a/api/src/routes/store/published-listings/skus.ts
+++ b/api/src/routes/store/published-listings/skus.ts
@@ -18,7 +18,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => {
 			access_type: 2,
 			name: "",
 			features: [],
-			relase_date: "",
+			release_date: "",
 			premium: false,
 			slug: "",
 			flags: 4,
diff --git a/api/src/routes/updates.ts b/api/src/routes/updates.ts
index 4682ce7c..cb4577c8 100644
--- a/api/src/routes/updates.ts
+++ b/api/src/routes/updates.ts
@@ -1,19 +1,19 @@
 import { Router, Response, Request } from "express";
 import { route } from "@fosscord/api";
-import { Config, Relase } from "@fosscord/util";
+import { Config, Release } from "@fosscord/util";
 
 const router = Router();
 
 router.get("/", route({}), async (req: Request, res: Response) => {
 	const { client } = Config.get();
 
-    const relase = await Relase.findOneOrFail({ name: client.relases.upstreamVersion})
+    const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion})
 
 	res.json({
-        name: relase.name,
-        pub_date: relase.pub_date,
-        url: relase.url,
-        notes: relase.notes
+        name: release.name,
+        pub_date: release.pub_date,
+        url: release.url,
+        notes: release.notes
     });
 });
 
diff --git a/api/src/routes/users/@me/notes.ts b/api/src/routes/users/@me/notes.ts
index 96067bf5..4887b191 100644
--- a/api/src/routes/users/@me/notes.ts
+++ b/api/src/routes/users/@me/notes.ts
@@ -1,14 +1,39 @@
 import { Request, Response, Router } from "express";
 import { route } from "@fosscord/api";
+import { User, emitEvent } from "@fosscord/util";
 
 const router: Router = Router();
 
+router.get("/:id", route({}), async (req: Request, res: Response) => {
+	const { id } = req.params;
+	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["notes"] });
+
+	const note = user.notes[id];
+	return res.json({
+		note: note,
+		note_user_id: id,
+		user_id: user.id,
+	});
+});
+
 router.put("/:id", route({}), async (req: Request, res: Response) => {
-	//TODO
-	res.json({
-		message: "Unknown User",
-		code: 10013
-	}).status(404);
+	const { id } = req.params;
+	const user = await User.findOneOrFail({ where: { id: req.user_id } });
+	const noteUser = await User.findOneOrFail({ where: { id: id }});		//if noted user does not exist throw
+	const { note } = req.body;
+
+	await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } });
+
+	await emitEvent({
+		event: "USER_NOTE_UPDATE",
+		data: {
+			note: note,
+			id: noteUser.id
+		},
+		user_id: user.id,
+	})
+
+	return res.status(204);
 });
 
 export default router;
diff --git a/api/src/start.ts b/api/src/start.ts
index 717e1b8f..ccb4d108 100644
--- a/api/src/start.ts
+++ b/api/src/start.ts
@@ -7,7 +7,12 @@ config();
 import { FosscordServer } from "./Server";
 import cluster from "cluster";
 import os from "os";
-const cores = Number(process.env.THREADS) || os.cpus().length;
+var cores = 1;
+try {
+	cores = Number(process.env.THREADS) || os.cpus().length;
+} catch {
+	console.log("[API] Failed to get thread count! Using 1...")
+}
 
 if (cluster.isMaster && process.env.NODE_ENV == "production") {
 	console.log(`Primary ${process.pid} is running`);
diff --git a/api/src/util/handlers/Message.ts b/api/src/util/handlers/Message.ts
index 2d9f7032..5a5ac666 100644
--- a/api/src/util/handlers/Message.ts
+++ b/api/src/util/handlers/Message.ts
@@ -7,6 +7,7 @@ import {
 	MessageCreateEvent,
 	MessageUpdateEvent,
 	getPermission,
+	getRights,
 	CHANNEL_MENTION,
 	Snowflake,
 	USER_MENTION,
@@ -61,19 +62,20 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
 		throw new HTTPError("Content length over max character limit")
 	}
 
-	// TODO: are tts messages allowed in dm channels? should permission be checked?
 	if (opts.author_id) {
 		message.author = await User.getPublicUser(opts.author_id);
-	}
+		const rights = await getRights(opts.author_id);
+		rights.hasThrow("SEND_MESSAGES");
+	}	
 	if (opts.application_id) {
 		message.application = await Application.findOneOrFail({ id: opts.application_id });
 	}
 	if (opts.webhook_id) {
 		message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id });
 	}
-
+	
 	const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id);
-	permission.hasThrow("SEND_MESSAGES"); // TODO: add the rights check
+	permission.hasThrow("SEND_MESSAGES");
 	if (permission.cache.member) {
 		message.member = permission.cache.member;
 	}
@@ -81,7 +83,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
 	if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES");
 	if (opts.message_reference) {
 		permission.hasThrow("READ_MESSAGE_HISTORY");
-		// code below has to be redone when we add custom message routing and cross-channel replies
+		// code below has to be redone when we add custom message routing
 		if (message.guild_id !== null) {
 			const guild = await Guild.findOneOrFail({ id: channel.guild_id });
 			if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) {
@@ -89,7 +91,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> {
 				if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel");
 			}
 		}
-		// TODO: should be checked if the referenced message exists?
+		// Q: should be checked if the referenced message exists? ANSWER: NO
 		// @ts-ignore
 		message.type = MessageType.REPLY;
 	}
diff --git a/api/src/util/handlers/route.ts b/api/src/util/handlers/route.ts
index 0048c4dd..3d3bbc37 100644
--- a/api/src/util/handlers/route.ts
+++ b/api/src/util/handlers/route.ts
@@ -6,6 +6,7 @@ import {
 	FieldErrors,
 	FosscordApiErrors,
 	getPermission,
+	getRights,
 	PermissionResolvable,
 	Permissions,
 	RightResolvable,
@@ -105,6 +106,8 @@ export function route(opts: RouteOptions) {
 
 		if (opts.right) {
 			const required = new Rights(opts.right);
+			req.rights = await getRights(req.user_id);
+
 			if (!req.rights || !req.rights.has(required)) {
 				throw FosscordApiErrors.MISSING_RIGHTS.withParams(opts.right as string);
 			}
diff --git a/api/src/util/utility/passwordStrength.ts b/api/src/util/utility/passwordStrength.ts
index 047df008..439700d0 100644
--- a/api/src/util/utility/passwordStrength.ts
+++ b/api/src/util/utility/passwordStrength.ts
@@ -13,6 +13,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
  *  - min <n> numbers
  *  - min <n> symbols
  *  - min <n> uppercase chars
+ *  - shannon entropy folded into [0, 1) interval
  *
  * Returns: 0 > pw > 1
  */
@@ -22,28 +23,38 @@ export function checkPassword(password: string): number {
 
 	// checks for total password len
 	if (password.length >= minLength - 1) {
-		strength += 0.25;
+		strength += 0.05;
 	}
 
 	// checks for amount of Numbers
 	if (password.count(reNUMBER) >= minNumbers - 1) {
-		strength += 0.25;
+		strength += 0.05;
 	}
 
 	// checks for amount of Uppercase Letters
 	if (password.count(reUPPERCASELETTER) >= minUpperCase - 1) {
-		strength += 0.25;
+		strength += 0.05;
 	}
 
 	// checks for amount of symbols
 	if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) {
-		strength += 0.25;
+		strength += 0.05;
 	}
 
 	// checks if password only consists of numbers or only consists of chars
 	if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) {
 		strength = 0;
 	}
-
+	
+	let entropyMap: { [key: string]: number } = {};
+	for (let i = 0; i < password.length; i++) {
+		if (entropyMap[password[i]]) entropyMap[password[i]]++;
+		else entropyMap[password[i]] = 1;
+	}
+	
+	let entropies = Object.values(entropyMap);
+	
+	entropies.map(x => (x / entropyMap.length));
+	strength += entropies.reduceRight((a: number, x: number) => a - (x * Math.log2(x))) / Math.log2(password.length);	
 	return strength;
 }
diff --git a/bundle/package-lock.json b/bundle/package-lock.json
index 4351f3fd..d2931ce3 100644
--- a/bundle/package-lock.json
+++ b/bundle/package-lock.json
@@ -7350,8 +7350,9 @@
 			}
 		},
 		"node_modules/minimist": {
-			"version": "1.2.5",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"node_modules/minipass": {
 			"version": "3.1.5",
@@ -16582,8 +16583,9 @@
 			}
 		},
 		"minimist": {
-			"version": "1.2.5",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"minipass": {
 			"version": "3.1.5",
diff --git a/bundle/scripts/benchmark/connections.js b/bundle/scripts/benchmark/connections.js
index 2a4125b4..ffca2628 100644
--- a/bundle/scripts/benchmark/connections.js
+++ b/bundle/scripts/benchmark/connections.js
@@ -3,8 +3,13 @@ const cluster = require("cluster");
 const WebSocket = require("ws");
 const endpoint = process.env.GATEWAY || "ws://localhost:3001";
 const connections = Number(process.env.CONNECTIONS) || 50;
-const threads = Number(process.env.THREADS) || require("os").cpus().length || 1;
 const token = process.env.TOKEN;
+var cores = 1;
+try {
+	cores = Number(process.env.THREADS) || os.cpus().length;
+} catch {
+	console.log("[Bundle] Failed to get thread count! Using 1...")
+}
 
 if (!token) {
 	console.error("TOKEN env var missing");
diff --git a/bundle/src/start.ts b/bundle/src/start.ts
index 7660b296..de3b5848 100644
--- a/bundle/src/start.ts
+++ b/bundle/src/start.ts
@@ -9,7 +9,12 @@ config();
 import { execSync } from "child_process";
 
 // TODO: add socket event transmission
-let cores = Number(process.env.THREADS) || os.cpus().length;
+var cores = 1;
+try {
+	cores = Number(process.env.THREADS) || os.cpus().length;
+} catch {
+	console.log("[API] Failed to get thread count! Using 1...")
+}
 
 if (cluster.isMaster) {
 	function getCommitOrFail() {
diff --git a/bundle/src/stats.ts b/bundle/src/stats.ts
index 3c5163c3..0234e0b4 100644
--- a/bundle/src/stats.ts
+++ b/bundle/src/stats.ts
@@ -4,7 +4,13 @@ import { red } from "picocolors";
 
 export function initStats() {
 	console.log(`[Path] running in ${__dirname}`);
-	console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`);
+	try {
+		console.log(`[CPU] ${osu.cpu.model()} Cores x${osu.cpu.count()}`);
+	}
+	catch {
+		console.log('[CPU] Failed to get cpu model!')
+	}
+	
 	console.log(`[System] ${os.platform()} ${os.arch()}`);
 	console.log(`[Process] running with PID: ${process.pid}`);
 	if (process.getuid && process.getuid() === 0) {
diff --git a/cdn/package-lock.json b/cdn/package-lock.json
index b48833b0..e6e9bb1a 100644
--- a/cdn/package-lock.json
+++ b/cdn/package-lock.json
@@ -5739,9 +5739,9 @@
 			}
 		},
 		"node_modules/minimist": {
-			"version": "1.2.5",
-			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"node_modules/minipass": {
 			"version": "3.1.6",
@@ -12301,9 +12301,9 @@
 			}
 		},
 		"minimist": {
-			"version": "1.2.5",
-			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"minipass": {
 			"version": "3.1.6",
diff --git a/gateway/package-lock.json b/gateway/package-lock.json
index 9b3841af..38bdab90 100644
--- a/gateway/package-lock.json
+++ b/gateway/package-lock.json
@@ -4479,8 +4479,9 @@
 			}
 		},
 		"../util/node_modules/minimist": {
-			"version": "1.2.5",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"../util/node_modules/minipass": {
 			"version": "2.9.0",
@@ -8768,8 +8769,9 @@
 			}
 		},
 		"node_modules/minimist": {
-			"version": "1.2.5",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
 			"dev": true
 		},
 		"node_modules/minipass": {
@@ -13666,8 +13668,9 @@
 					}
 				},
 				"minimist": {
-					"version": "1.2.5",
-					"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+					"version": "1.2.6",
+					"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+					"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 				},
 				"minipass": {
 					"version": "2.9.0",
@@ -16870,8 +16873,9 @@
 			}
 		},
 		"minimist": {
-			"version": "1.2.5",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
 			"dev": true
 		},
 		"minipass": {
diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index 42b3713c..4b617fdd 100644
--- a/gateway/src/opcodes/Identify.ts
+++ b/gateway/src/opcodes/Identify.ts
@@ -29,8 +29,8 @@ const experiments: any = [];
 import { check } from "./instanceOf";
 import { Recipient } from "@fosscord/util";
 
-// TODO: bot sharding
-// TODO: check priviliged intents
+// TODO: user sharding
+// TODO: check privileged intents, if defined in the config
 // TODO: check if already identified
 
 export async function onIdentify(this: WebSocket, data: Payload) {
@@ -89,7 +89,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 				user_id: this.user_id,
 				session_id: session_id,
 				// TODO: check if status is only one of: online, dnd, offline, idle
-				status: identify.presence?.status || "online", //does the session always start as online?
+				status: identify.presence?.status || "offline", //does the session always start as online?
 				client_info: {
 					//TODO read from identity
 					client: "desktop",
@@ -103,7 +103,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 
 	if (!user) return this.close(CLOSECODES.Authentication_failed);
 
-	if (!identify.intents) identify.intents = BigInt("0b11111111111111");
+	if (!identify.intents) identify.intents = BigInt("0x6ffffffff");
 	this.intents = new Intents(identify.intents);
 	if (identify.shard) {
 		this.shard_id = identify.shard[0];
@@ -273,7 +273,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
 		guild_join_requests: [], // TODO what is this?
 		users: users.filter((x) => x).unique(),
 		merged_members: merged_members,
-		// shard // TODO: only for bots sharding
+		// shard // TODO: only for user sharding
 	};
 
 	// TODO: send real proper data structure
diff --git a/util/package-lock.json b/util/package-lock.json
index 82e90b36..b2fa8bbf 100644
--- a/util/package-lock.json
+++ b/util/package-lock.json
@@ -5003,9 +5003,9 @@
 			}
 		},
 		"node_modules/minimist": {
-			"version": "1.2.5",
-			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"node_modules/minipass": {
 			"version": "2.9.0",
@@ -12060,9 +12060,9 @@
 			}
 		},
 		"minimist": {
-			"version": "1.2.5",
-			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
-			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+			"version": "1.2.6",
+			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
+			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
 		},
 		"minipass": {
 			"version": "2.9.0",
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 08be1e02..4bf81901 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -20,13 +20,17 @@ export enum ChannelType {
 	GROUP_DM = 3, // a direct message between multiple users

 	GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels

 	GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route

-	GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord

+	GUILD_STORE = 6, // a channel in which game developers can sell their things

 	ENCRYPTED = 7, // end-to-end encrypted channel

 	ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel

+	TRANSACTIONAL = 9, // event chain style transactional channel

 	GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel

 	GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel

 	GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission

 	GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience

+	TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12

+	KANBAN = 34, // confluence like kanban board

+	VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage)

 	CUSTOM_START = 64, // start custom channel types from here

 	UNHANDLED = 255 // unhandled unowned pass-through channel type

 }

diff --git a/util/src/entities/ClientRelase.ts b/util/src/entities/ClientRelease.ts
index e021b82b..c5afd307 100644
--- a/util/src/entities/ClientRelase.ts
+++ b/util/src/entities/ClientRelease.ts
@@ -1,8 +1,8 @@
 import { Column, Entity} from "typeorm";
 import { BaseClass } from "./BaseClass";
 
-@Entity("client_relase")
-export class Relase extends BaseClass {
+@Entity("client_release")
+export class Release extends BaseClass {
 	@Column()
 	name: string;
 
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index f4a266dc..8d29b387 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -188,8 +188,8 @@ export interface ConfigValue {
 	},
 	client: {
 		useTestClient: Boolean;
-		relases: {
-			useLocalRelases: Boolean; //TODO
+		releases: {
+			useLocalRelease: Boolean; //TODO
 			upstreamVersion: string;
 		}
 	},
@@ -222,7 +222,7 @@ export const DefaultConfigOptions: ConfigValue = {
 	},
 	general: {
 		instanceName: "Fosscord Instance",
-		instanceDescription: "This is a Fosscord instance made in pre-relase days",
+		instanceDescription: "This is a Fosscord instance made in pre-release days",
 		frontPage: null,
 		tosPage: null,
 		correspondenceEmail: "noreply@localhost.local",
@@ -389,8 +389,8 @@ export const DefaultConfigOptions: ConfigValue = {
 	},
 	client: {
 		useTestClient: true,
-		relases: {
-			useLocalRelases: true,
+		releases: {
+			useLocalRelease: true,
 			upstreamVersion: "0.0.264"
 		}
 	},
diff --git a/util/src/entities/Encryption.ts b/util/src/entities/Encryption.ts
new file mode 100644
index 00000000..3b82ff84
--- /dev/null
+++ b/util/src/entities/Encryption.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { Guild } from "./Guild";
+import { PublicUserProjection, User } from "./User";
+import { HTTPError } from "lambert-server";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
+import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField";
+import { Recipient } from "./Recipient";
+import { Message } from "./Message";
+import { ReadState } from "./ReadState";
+import { Invite } from "./Invite";
+import { DmChannelDTO } from "../dtos";
+
+@Entity("security_settings")
+export class SecuritySettings extends BaseClass {
+
+  @Column({nullable: true})
+  guild_id: Snowflake;
+
+  @Column({nullable: true})
+  channel_id: Snowflake;
+
+  @Column()
+  encryption_permission_mask: BitField;
+
+  @Column()
+  allowed_algorithms: string[];
+
+  @Column()
+  current_algorithm: string;
+
+  @Column({nullable: true})
+  used_since_message: Snowflake;
+
+}
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 9ac148ee..70bb41c5 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -187,11 +187,11 @@ export class Guild extends BaseClass {
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.owner)
-	owner_id: string;
+	owner_id?: string; // optional to allow for ownerless guilds
 
 	@JoinColumn({ name: "owner_id", referencedColumnName: "id" })
 	@ManyToOne(() => User)
-	owner: User;
+	owner?: User; // optional to allow for ownerless guilds
 
 	@Column({ nullable: true })
 	preferred_locale?: string;
@@ -200,7 +200,7 @@ export class Guild extends BaseClass {
 	premium_subscription_count?: number;
 
 	@Column({ nullable: true })
-	premium_tier?: number; // nitro boost level
+	premium_tier?: number; // crowd premium level
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.public_updates_channel)
@@ -269,6 +269,10 @@ export class Guild extends BaseClass {
 
 	@Column({ nullable: true })
 	nsfw?: boolean;
+	
+	// TODO: nested guilds
+	@Column({ nullable: true })
+	parent?: string;
 
 	// only for developer portal
 	permissions?: number;
@@ -308,7 +312,7 @@ export class Guild extends BaseClass {
 			verification_level: 0,
 			welcome_screen: {
 				enabled: false,
-				description: "No description",
+				description: "Fill in your description",
 				welcome_channels: [],
 			},
 			widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index a246b891..fe2d5590 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -70,7 +70,7 @@ export class Member extends BaseClassWithoutId {
 
 	@Column({ nullable: true })
 	nick?: string;
-
+	
 	@JoinTable({
 		name: "member_roles",
 		joinColumn: { name: "index", referencedColumnName: "index" },
@@ -102,8 +102,17 @@ export class Member extends BaseClassWithoutId {
 
 	@Column({ nullable: true })
 	last_message_id?: string;
+	
+	/**
+	@JoinColumn({ name: "id" })
+	@ManyToOne(() => User, {
+		onDelete: "DO NOTHING",
+	// do not auto-kick force-joined members just because their joiners left the server
+	}) **/
+	@Column({ nullable: true})
+	joined_by?: string;
 
-	// TODO: update
+	// TODO: add this when we have proper read receipts
 	// @Column({ type: "simple-json" })
 	// read_state: ReadState;
 
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index e577d5df..b32bbd94 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -41,8 +41,14 @@ export enum MessageType {
 	CHANNEL_FOLLOW_ADD = 12,
 	GUILD_DISCOVERY_DISQUALIFIED = 14,
 	GUILD_DISCOVERY_REQUALIFIED = 15,
+	ENCRYPTED = 16,
 	REPLY = 19,
 	APPLICATION_COMMAND = 20,
+	ROUTE_ADDED = 41, // custom message routing: new route affecting that channel
+	ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel
+	ENCRYPTION = 50,
+	CUSTOM_START = 63,
+	UNHANDLED = 255
 }
 
 @Entity("messages")
@@ -84,7 +90,7 @@ export class Message extends BaseClass {
 	@RelationId((message: Message) => message.member)
 	member_id: string;
 
-	@JoinColumn({ name: "author_id", referencedColumnName: "id" })
+	@JoinColumn({ name: "member_id", referencedColumnName: "id" })
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE",
 	})
@@ -203,6 +209,7 @@ export interface MessageComponent {
 }
 
 export enum MessageComponentType {
+	Script = 0, // self command script
 	ActionRow = 1,
 	Button = 2,
 }
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index e6d73105..b915573b 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -49,6 +49,7 @@ export class ReadState extends BaseClass {
 	@Column({ nullable: true })
 	mention_count: number;
 
-	@Column({ nullable: true })
+	// @Column({ nullable: true })
+	// TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true
 	manual: boolean;
 }
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index ed7bd4ce..a5c4c136 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -60,7 +60,7 @@ export class User extends BaseClass {
 	username: string; // username max length 32, min 2 (should be configurable)
 
 	@Column()
-	discriminator: string; // #0001 4 digit long string from #0001 - #9999
+	discriminator: string; // opaque string: 4 digits on discord.com
 
 	setDiscriminator(val: string) {
 		const number = Number(val);
@@ -88,10 +88,10 @@ export class User extends BaseClass {
 	mobile: boolean; // if the user has mobile app installed
 
 	@Column()
-	premium: boolean; // if user bought nitro
-
+	premium: boolean; // if user bought individual premium
+	
 	@Column()
-	premium_type: number; // nitro level
+	premium_type: number; // individual premium level
 
 	@Column()
 	bot: boolean; // if user is bot
@@ -100,11 +100,11 @@ export class User extends BaseClass {
 	bio: string; // short description of the user (max 190 chars -> should be configurable)
 
 	@Column()
-	system: boolean; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author
+	system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author
 
 	@Column({ select: false })
-	nsfw_allowed: boolean; // if the user is older than 18 (resp. Config)
-
+	nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands)
+	
 	@Column({ select: false })
 	mfa_enabled: boolean; // if multi factor authentication is enabled
 
@@ -132,7 +132,7 @@ export class User extends BaseClass {
 	@Column()
 	public_flags: number;
 
-	@Column()
+	@Column({ type: "bigint" })
 	rights: string; // Rights
 
 	@OneToMany(() => Session, (session: Session) => session.user)
@@ -164,6 +164,9 @@ export class User extends BaseClass {
 	@Column({ type: "simple-json", select: false })
 	settings: UserSettings;
 
+	@Column({ type: "simple-json" })
+	notes: { [key: string]: string };	//key is ID of user
+
 	toPublicUser() {
 		const user: any = {};
 		PublicUserProjection.forEach((x) => {
@@ -271,6 +274,7 @@ export class User extends BaseClass {
 			},
 			settings: { ...defaultSettings, locale: language },
 			fingerprints: [],
+			notes: {},
 		});
 
 		await user.save();
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index fc18d422..f023d5a6 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -27,4 +27,4 @@ export * from "./Template";
 export * from "./User";
 export * from "./VoiceState";
 export * from "./Webhook";
-export * from "./ClientRelase";
\ No newline at end of file
+export * from "./ClientRelease";
\ No newline at end of file
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index a5253c09..416082ed 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -623,6 +623,7 @@ export type EVENT =
 	| "PRESENCE_UPDATE"
 	| "TYPING_START"
 	| "USER_UPDATE"
+	| "USER_NOTE_UPDATE"
 	| "WEBHOOKS_UPDATE"
 	| "INTERACTION_CREATE"
 	| "VOICE_STATE_UPDATE"
diff --git a/util/src/migrations/1648643945733-ReleaseTypo.ts b/util/src/migrations/1648643945733-ReleaseTypo.ts
new file mode 100644
index 00000000..944b9dd9
--- /dev/null
+++ b/util/src/migrations/1648643945733-ReleaseTypo.ts
@@ -0,0 +1,16 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class ReleaseTypo1648643945733 implements MigrationInterface {
+	name = "ReleaseTypo1648643945733";
+
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		//drop table first because typeorm creates it before migrations run
+		await queryRunner.dropTable("client_release", true);
+		await queryRunner.renameTable("client_relase", "client_release");
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.dropTable("client_relase", true);
+		await queryRunner.renameTable("client_release", "client_relase");
+	}
+}
diff --git a/util/src/util/Email.ts b/util/src/util/Email.ts
index b1a7599b..6885da33 100644
--- a/util/src/util/Email.ts
+++ b/util/src/util/Email.ts
@@ -13,7 +13,12 @@ export function adjustEmail(email?: string): string | undefined {
 	// TODO: check accounts with uncommon email domains
 	if (domain === "gmail.com" || domain === "googlemail.com") {
 		// replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
-		return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
+		let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
+	}
+	
+	if (domain === "google.com") {
+		// replace .dots and +alternatives -> Google Staff GMail Dot Trick
+		let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com";
 	}
 
 	return email;
diff --git a/util/src/util/Intents.ts b/util/src/util/Intents.ts
index 943b29cf..d9a60e4a 100644
--- a/util/src/util/Intents.ts
+++ b/util/src/util/Intents.ts
@@ -2,20 +2,31 @@ import { BitField } from "./BitField";
 
 export class Intents extends BitField {
 	static FLAGS = {
-		GUILDS: BigInt(1) << BigInt(0),
-		GUILD_MEMBERS: BigInt(1) << BigInt(1),
-		GUILD_BANS: BigInt(1) << BigInt(2),
-		GUILD_EMOJIS: BigInt(1) << BigInt(3),
-		GUILD_INTEGRATIONS: BigInt(1) << BigInt(4),
-		GUILD_WEBHOOKS: BigInt(1) << BigInt(5),
-		GUILD_INVITES: BigInt(1) << BigInt(6),
-		GUILD_VOICE_STATES: BigInt(1) << BigInt(7),
-		GUILD_PRESENCES: BigInt(1) << BigInt(8),
-		GUILD_MESSAGES: BigInt(1) << BigInt(9),
-		GUILD_MESSAGE_REACTIONS: BigInt(1) << BigInt(10),
-		GUILD_MESSAGE_TYPING: BigInt(1) << BigInt(11),
-		DIRECT_MESSAGES: BigInt(1) << BigInt(12),
-		DIRECT_MESSAGE_REACTIONS: BigInt(1) << BigInt(13),
-		DIRECT_MESSAGE_TYPING: BigInt(1) << BigInt(14),
+		GUILDS: BigInt(1) << BigInt(0), // guilds and guild merge-split events affecting the user
+		GUILD_MEMBERS: BigInt(1) << BigInt(1), // memberships
+		GUILD_BANS: BigInt(1) << BigInt(2), // bans and ban lists
+		GUILD_EMOJIS: BigInt(1) << BigInt(3), // custom emojis
+		GUILD_INTEGRATIONS: BigInt(1) << BigInt(4), // applications
+		GUILD_WEBHOOKS: BigInt(1) << BigInt(5), // webhooks
+		GUILD_INVITES: BigInt(1) << BigInt(6), // mass invites (no user can receive user specific invites of another user)
+		GUILD_VOICE_STATES: BigInt(1) << BigInt(7), // voice updates
+		GUILD_PRESENCES: BigInt(1) << BigInt(8), // presence updates
+		GUILD_MESSAGES_METADATA: BigInt(1) << BigInt(9), // guild message metadata
+		GUILD_MESSAGE_REACTIONS: BigInt(1) << BigInt(10), // guild message reactions
+		GUILD_MESSAGE_TYPING: BigInt(1) << BigInt(11), // guild channel typing notifications
+		DIRECT_MESSAGES: BigInt(1) << BigInt(12), // DM or orphan channels
+		DIRECT_MESSAGE_REACTIONS: BigInt(1) << BigInt(13), // DM or orphan channel message reactions
+		DIRECT_MESSAGE_TYPING: BigInt(1) << BigInt(14), // DM typing notifications
+		GUILD_MESSAGES_CONTENT: BigInt(1) << BigInt(15), // guild message content
+		LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway
+		GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild
+		DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42),  // direct message threads
+		JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later)
+		LOBBIES: BigInt(1) << BigInt(44), // lobbies
+		INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes 
+		INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events
+		INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates
+		INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63) // all instance user updates
 	};
 }
+
diff --git a/util/src/util/Rights.ts b/util/src/util/Rights.ts
index db5384d0..35ad9514 100644
--- a/util/src/util/Rights.ts
+++ b/util/src/util/Rights.ts
@@ -1,6 +1,7 @@
 import { BitField } from "./BitField";
 import "missing-native-js-functions";
 import { BitFieldResolvable, BitFlag } from "./BitField";
+import { User } from "../entities";
 
 var HTTPError: any;
 
@@ -85,6 +86,15 @@ export class Rights extends BitField {
 		// @ts-ignore
 		throw new HTTPError(`You are missing the following rights ${permission}`, 403);
 	}
+	
 }
 
 const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0));
+
+export async function getRights(	user_id: string
+	/**, opts: {
+		in_behalf?: (keyof User)[];
+	} = {} **/) {
+	let user = await User.findOneOrFail({ where: { id: user_id } });
+	return new Rights(user.rights);
+}