diff --git a/README.md b/README.md
index 679f531d..b05bed13 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>
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/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 58dfb1cc..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
@@ -51,6 +51,18 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE
return res.json(message);
});
+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;
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/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/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/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 e75e48f6..439700d0 100644
--- a/api/src/util/utility/passwordStrength.ts
+++ b/api/src/util/utility/passwordStrength.ts
@@ -13,7 +13,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
* - min <n> numbers
* - min <n> symbols
* - min <n> uppercase chars
- * - shannon entropy divided by password entropy
+ * - shannon entropy folded into [0, 1) interval
*
* Returns: 0 > pw > 1
*/
@@ -46,15 +46,15 @@ export function checkPassword(password: string): number {
strength = 0;
}
- let entropyMap;
+ 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 = Array(entropyMap);
-
+ let entropies = Object.values(entropyMap);
+
entropies.map(x => (x / entropyMap.length));
- strength += entropies.reduceRight((a, x), a - (x * Math.log2(x))) / Math.log2(password.length);
+ strength += entropies.reduceRight((a: number, x: number) => a - (x * Math.log2(x))) / Math.log2(password.length);
return strength;
}
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/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts
index eb15c28f..860000da 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) {
@@ -87,7 +87,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",
@@ -101,7 +101,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];
@@ -271,7 +271,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/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
};
}
+
|