1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-23 02:42:28 +01:00
This commit is contained in:
Flam3rboy 2021-08-27 11:10:42 +02:00
parent 18deb1e4b0
commit 0ecc5d8c0e
17 changed files with 1129 additions and 164 deletions

View File

@ -1 +0,0 @@
it("works", () => {});

View File

@ -145,9 +145,9 @@ export default {
// The glob patterns Jest uses to detect test files
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/tests/**/*.[jt]s?(x)"
// "**/?(*.)+(spec|test).[tj]s?(x)"
],
]
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [

1069
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@
"main": "dist/Server.js",
"types": "dist/Server.d.ts",
"scripts": {
"test": "jest",
"test": "npm run build && jest",
"test:watch": "jest --watch",
"start": "npm run build && node dist/start",
"build": "npx tsc -b .",
@ -35,6 +35,7 @@
"@types/bcrypt": "^5.0.0",
"@types/express": "^4.17.9",
"@types/i18next-node-fs-backend": "^2.1.0",
"@types/jest": "^27.0.1",
"@types/jsonwebtoken": "^8.5.0",
"@types/mongodb": "^3.6.9",
"@types/mongoose": "^5.10.5",
@ -79,6 +80,7 @@
"mongoose-autopopulate": "^0.12.3",
"mongoose-long": "^0.3.2",
"multer": "^1.4.2",
"node-fetch": "^2.6.1"
"node-fetch": "^2.6.1",
"typeorm": "^0.2.37"
}
}

View File

@ -0,0 +1 @@
// TODO: start api

View File

@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { FieldError } from "../util/instanceOf";
// TODO: update with new body/typorm validation
export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
if (!error) next();

View File

@ -21,9 +21,6 @@ router.post(
async (req: Request, res: Response) => {
const { login, password, captcha_key, undelete } = req.body;
const email = adjustEmail(login);
const query: any[] = [{ phone: login }];
if (email) query.push({ email });
console.log(req.body, email);
const config = Config.get();
@ -41,11 +38,10 @@ router.post(
// TODO: check captcha
}
const user = await User.findOneOrFail(
{ $or: query },
{ "data.hash": true, id: true, disabled: true, deleted: true, "settings.locale": true, "settings.theme": true }
).catch((e) => {
console.log(e, query);
const user = await User.findOneOrFail({
where: [{ phone: login }, { email: login }],
select: ["data", "id", "disabled", "deleted", "settings"]
}).catch((e) => {
throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } });
});

View File

@ -1,12 +1,12 @@
import { Request, Response, Router } from "express";
import { trimSpecial, User, Snowflake, User, Config } from "@fosscord/util";
import { trimSpecial, User, Snowflake, Config } from "@fosscord/util";
import bcrypt from "bcrypt";
import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf";
import "missing-native-js-functions";
import { generateToken } from "./login";
import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress";
import { HTTPError } from "lambert-server";
import RateLimit from "../../middlewares/RateLimit";
import { In } from "typeorm";
const router: Router = Router();
@ -55,13 +55,13 @@ router.post(
// TODO: check password strength
// adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick
let adjusted_email: string | null = adjustEmail(email);
let adjusted_email = adjustEmail(email);
// adjusted_password will be the hash of the password
let adjusted_password: string = "";
let adjusted_password = "";
// trim special uf8 control characters -> Backspace, Newline, ...
let adjusted_username: string = trimSpecial(username);
let adjusted_username = trimSpecial(username);
// discriminator will be randomly generated
let discriminator = "";
@ -129,7 +129,7 @@ router.post(
if (!register.allowMultipleAccounts) {
// TODO: check if fingerprint was eligible generated
const exists = await User.findOneOrFail({ fingerprints: fingerprint }).catch((e) => {});
const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } });
if (exists) {
throw FieldErrors({
@ -164,12 +164,8 @@ router.post(
// TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database?
for (let tries = 0; tries < 5; tries++) {
discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0");
try {
exists = await User.findOneOrFail({ discriminator, username: adjusted_username }, "id");
} catch (error) {
// doesn't exist -> break
break;
}
exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] });
if (!exists) break;
}
if (exists) {
@ -185,35 +181,26 @@ router.post(
// appearently discord doesn't save the date of birth and just calculate if nsfw is allowed
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
const user: User = {
const user = await new User({
id: Snowflake.generate(),
created_at: new Date(),
username: adjusted_username,
discriminator,
avatar: null,
accent_color: null,
banner: null,
avatar: undefined,
accent_color: undefined,
banner: undefined,
bot: false,
system: false,
desktop: false,
mobile: false,
premium: true,
premium_type: 2,
phone: null,
phone: undefined,
bio: "",
mfa_enabled: false,
verified: false,
disabled: false,
deleted: false,
presence: {
activities: [],
client_status: {
desktop: undefined,
mobile: undefined,
web: undefined
},
status: "offline"
},
email: adjusted_email,
nsfw_allowed: true, // TODO: depending on age
public_flags: 0n,
@ -221,10 +208,7 @@ router.post(
guilds: [],
data: {
hash: adjusted_password,
valid_tokens_since: new Date(),
relationships: [],
connected_accounts: [],
fingerprints: []
valid_tokens_since: new Date()
},
settings: {
afk_timeout: 300,
@ -234,10 +218,10 @@ router.post(
contact_sync_enabled: false,
convert_emoticons: false,
custom_status: {
emoji_id: null,
emoji_name: null,
expires_at: null,
text: null
emoji_id: undefined,
emoji_name: undefined,
expires_at: undefined,
text: undefined
},
default_guilds_restricted: false,
detect_platform_accounts: true,
@ -265,16 +249,13 @@ router.post(
timezone_offset: 0
// timezone_offset: // TODO: timezone from request
}
};
// insert user into database
await new User(user).save();
}).save();
return res.json({ token: await generateToken(user.id) });
}
);
export function adjustEmail(email: string): string | null {
export function adjustEmail(email: string): string | undefined {
// body parser already checked if it is a valid email
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
// @ts-ignore

View File

@ -1,5 +1,5 @@
import { Router, Response, Request } from "express";
import { Channel, toObject, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { ChannelModifySchema } from "../../../schema/Channel";
@ -48,15 +48,19 @@ router.patch(
if (x.parent_id) {
opts.parent_id = x.parent_id;
const parent_channel = await Channel.findOneOrFail({ id: x.parent_id, guild_id }, { permission_overwrites: true });
const parent_channel = await Channel.findOneOrFail({
where: { id: x.parent_id, guild_id },
select: ["permission_overwrites"]
});
if (x.lock_permissions) {
opts.permission_overwrites = parent_channel.permission_overwrites;
}
}
const channel = await Channel.findOneOrFailAndUpdate({ id: x.id, guild_id }, opts, { new: true });
await Channel.update({ guild_id, id: x.id }, opts);
const channel = await Channel.findOneOrFail({ guild_id, id: x.id });
await emitEvent({ event: "CHANNEL_UPDATE", data: channel), channel_id: x.id, guild_id } as ChannelUpdateEvent;
await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent);
})
]);

View File

@ -1,5 +1,5 @@
import { Request, Response, Router } from "express";
import { TemplateModel, Guild, getPermission, toObject, User, Snowflake } from "@fosscord/util";
import { Guild, getPermission, Template } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template";
import { check } from "../../../util/instanceOf";
@ -7,22 +7,22 @@ import { generateCode } from "../../../util/String";
const router: Router = Router();
const TemplateGuildProjection = {
name: true,
description: true,
region: true,
verification_level: true,
default_message_notifications: true,
explicit_content_filter: true,
preferred_locale: true,
afk_timeout: true,
roles: true,
channels: true,
afk_channel_id: true,
system_channel_id: true,
system_channel_flags: true,
icon_hash: true
};
const TemplateGuildProjection: (keyof Guild)[] = [
"name",
"description",
"region",
"verification_level",
"default_message_notifications",
"explicit_content_filter",
"preferred_locale",
"afk_timeout",
"roles",
"channels",
"afk_channel_id",
"system_channel_id",
"system_channel_flags",
"icon"
];
router.get("/", async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -34,14 +34,14 @@ router.get("/", async (req: Request, res: Response) => {
router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection);
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_GUILD");
const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {});
if (exists) throw new HTTPError("Template already exists", 400);
const template = await new TemplateModel({
const template = await new Template({
...req.body,
code: generateCode(),
creator_id: req.user_id,
@ -51,7 +51,7 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response
serialized_source_guild: guild
}).save();
res.json(template)).send(;
res.json(template);
});
router.delete("/:code", async (req: Request, res: Response) => {
@ -61,37 +61,39 @@ router.delete("/:code", async (req: Request, res: Response) => {
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_GUILD");
const template = await Template.findOneOrFailAndDelete({
const template = await Template.delete({
code
});
res.send(template);
res.json(template);
});
router.put("/:code", async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
const { code } = req.params;
// synchronizes the template
const { code, guild_id } = req.params;
const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection);
const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection });
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_GUILD");
const template = await Template.findOneOrFailAndUpdate({ code }, { serialized_source_guild: guild }, { new: true });
const template = await new Template({ code, serialized_source_guild: guild }).save();
res.json(template)).send(;
res.json(template);
});
router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => {
// updates the template description
const { guild_id } = req.params;
const { code } = req.params;
const { name, description } = req.body;
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_GUILD");
const template = await Template.findOneOrFailAndUpdate({ code }, { name: req.body.name, description: req.body.description }, { new: true });
const template = await new Template({ code, name: name, description: description }).save();
res.json(template)).send(;
res.json(template);
});
export default router;

View File

@ -1,9 +1,8 @@
import { Request, Response, Router } from "express";
const router: Router = Router();
import { TemplateModel, Guild, toObject, User, Role, Snowflake, Guild, Config } from "@fosscord/util";
import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { GuildTemplateCreateSchema } from "../../../schema/Guild";
import { getPublicUser } from "../../../util/User";
import { check } from "../../../util/instanceOf";
import { addMember } from "../../../util/Member";
@ -12,7 +11,7 @@ router.get("/:code", async (req: Request, res: Response) => {
const template = await Template.findOneOrFail({ code: code });
res.json(template)).send(;
res.json(template);
});
router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => {
@ -20,7 +19,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res
const body = req.body as GuildTemplateCreateSchema;
const { maxGuilds } = Config.get().limits.user;
const user = await getPublicUser(req.user_id, { guilds: true });
const user = await User.getPublicUser(req.user_id, { guilds: true });
if (user.guilds.length >= maxGuilds) {
throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403);

View File

@ -1,5 +1,5 @@
import { Router, Request, Response } from "express";
import { getPermission, Guild, InviteModel, toObject } from "@fosscord/util";
import { getPermission, Guild, Invite } from "@fosscord/util";
import { HTTPError } from "lambert-server";
import { addMember } from "../../util/Member";
const router: Router = Router();
@ -40,7 +40,7 @@ router.delete("/:code", async (req: Request, res: Response) => {
await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {});
res.status(200).send({ invite: invite) };
res.json({ invite: invite });
});
export default router;

View File

@ -7,7 +7,7 @@ router.get("/", async (req: Request, res: Response) => {
const user = await getPublicUser(req.params.id, { data: true });
res.json({
connected_accounts: user.data.connected_accounts,
connected_accounts: user.connected_accounts,
premium_guild_since: null, // TODO
premium_since: null, // TODO
user: {

View File

@ -15,14 +15,14 @@ import { check, Length } from "../../../util/instanceOf";
const router = Router();
const userProjection = { "user_data.relationships": true, ...PublicUserProjection };
const userProjection = { "data.relationships": true, ...PublicUserProjection };
router.get("/", async (req: Request, res: Response) => {
const user = await User.findOneOrFail({ id: req.user_id }, { user_data: { relationships: true } }).populate({
path: "user_data.relationships.id",
const user = await User.findOneOrFail({ id: req.user_id }, { data: { relationships: true } }).populate({
path: "data.relationships.id",
model: User
});
return res.json(user.user_data.relationships);
return res.json(user.data.relationships);
});
async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) {
@ -30,8 +30,8 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument
if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
const user = await User.findOneOrFail({ id: req.user_id }, userProjection);
const newUserRelationships = [...user.user_data.relationships];
const newFriendRelationships = [...friend.user_data.relationships];
const newUserRelationships = [...user.data.relationships];
const newFriendRelationships = [...friend.data.relationships];
var relationship = newUserRelationships.find((x) => x.id === id);
const friendRequest = newFriendRelationships.find((x) => x.id === req.user_id);
@ -48,7 +48,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument
if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
newFriendRelationships.remove(friendRequest);
await Promise.all([
User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }),
User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }),
emitEvent({
event: "RELATIONSHIP_REMOVE",
data: friendRequest,
@ -58,12 +58,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument
}
await Promise.all([
User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }),
User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }),
emitEvent({
event: "RELATIONSHIP_ADD",
data: {
...relationship,
user: { ...friend, user_data: undefined }
user: { ...friend, data: undefined }
},
user_id: req.user_id
} as RelationshipAddEvent)
@ -91,13 +91,13 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument
} else newUserRelationships.push(outgoing_relationship);
await Promise.all([
User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }),
User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }),
User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }),
User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }),
emitEvent({
event: "RELATIONSHIP_ADD",
data: {
...outgoing_relationship,
user: { ...friend, user_data: undefined }
user: { ...friend, data: undefined }
},
user_id: req.user_id
} as RelationshipAddEvent),
@ -106,7 +106,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument
data: {
...incoming_relationship,
should_notify: true,
user: { ...user, user_data: undefined }
user: { ...user, data: undefined }
},
user_id: id
} as RelationshipAddEvent)
@ -138,11 +138,11 @@ router.delete("/:id", async (req: Request, res: Response) => {
const friend = await User.findOneOrFail({ id }, userProjection);
if (!friend) throw new HTTPError("User not found", 404);
const relationship = user.user_data.relationships.find((x) => x.id === id);
const friendRequest = friend.user_data.relationships.find((x) => x.id === req.user_id);
const relationship = user.data.relationships.find((x) => x.id === id);
const friendRequest = friend.data.relationships.find((x) => x.id === req.user_id);
if (relationship?.type === RelationshipType.blocked) {
// unblock user
user.user_data.relationships.remove(relationship);
user.data.relationships.remove(relationship);
await Promise.all([
user.save(),
@ -153,8 +153,8 @@ router.delete("/:id", async (req: Request, res: Response) => {
if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404);
if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
user.user_data.relationships.remove(relationship);
friend.user_data.relationships.remove(friendRequest);
user.data.relationships.remove(relationship);
friend.data.relationships.remove(friendRequest);
await Promise.all([
user.save(),

View File

@ -57,7 +57,7 @@ export async function createChannel(
recipient_ids: null
}).save();
await emitEvent({ event: "CHANNEL_CREATE", data: channel), guild_id: channel.guild_id } as ChannelCreateEvent;
await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent);
return channel;
}

View File

@ -1,16 +0,0 @@
import { toObject, User, PublicUserProjection } from "@fosscord/util";
import { HTTPError } from "lambert-server";
export { PublicUserProjection };
export async function getPublicUser(user_id: string, additional_fields?: any) {
const user = await User.findOneOrFail(
{ id: user_id },
{
...PublicUserProjection,
...additional_fields
}
);
if (!user) throw new HTTPError("User not found", 404);
return user;
}

View File

@ -0,0 +1 @@
test("works", () => {});