Added end to end encryption
This commit is contained in:
parent
5733975aa0
commit
6f292756ed
34 changed files with 682 additions and 69 deletions
96
package-lock.json
generated
96
package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "tether",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tether",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.2",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@fastify/cookie": "^11.0.2",
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
"fastify": "^5.6.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"pg": "^8.16.3",
|
||||
"ua-parser-js": "^2.0.8",
|
||||
"uuid": "^13.0.0",
|
||||
"ws": "^8.19.0"
|
||||
},
|
||||
|
|
@ -965,6 +966,26 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/detect-europe-js": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz",
|
||||
"integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
|
|
@ -1324,6 +1345,26 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-standalone-pwa": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz",
|
||||
"integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
|
|
@ -2381,6 +2422,57 @@
|
|||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ua-is-frozen": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz",
|
||||
"integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ua-parser-js": {
|
||||
"version": "2.0.8",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.8.tgz",
|
||||
"integrity": "sha512-BdnBM5waFormdrOFBU+cA90R689V0tWUWlIG2i30UXxElHjuCu5+dOV2Etw3547jcQ/yaLtPm9wrqIuOY2bSJg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/ua-parser-js"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/faisalman"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/faisalman"
|
||||
}
|
||||
],
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"detect-europe-js": "^0.1.2",
|
||||
"is-standalone-pwa": "^0.1.1",
|
||||
"ua-is-frozen": "^0.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"ua-parser-js": "script/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.16.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "tether",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.2",
|
||||
"description": "Communication server using the Nexlink protocol",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
"fastify": "^5.6.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"pg": "^8.16.3",
|
||||
"ua-parser-js": "^2.0.8",
|
||||
"uuid": "^13.0.0",
|
||||
"ws": "^8.19.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Session" ADD COLUMN "name" TEXT,
|
||||
ADD COLUMN "sessionStorageKey" TEXT,
|
||||
ADD COLUMN "userAgent" TEXT;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `sessionStorageKey` on the `Session` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Session" DROP COLUMN "sessionStorageKey",
|
||||
ADD COLUMN "storageKey" TEXT;
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
Warnings:
|
||||
|
||||
- You are about to drop the column `storageKey` on the `Session` table. All the data in the column will be lost.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "Session" DROP COLUMN "storageKey",
|
||||
ADD COLUMN "storageSecret" TEXT;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Session" ADD COLUMN "refreshDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Message" ADD COLUMN "iv" TEXT;
|
||||
|
|
@ -60,11 +60,15 @@ model User {
|
|||
}
|
||||
|
||||
model Session {
|
||||
id String @id @unique @default(uuid())
|
||||
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
cookie String
|
||||
creationDate DateTime @default(now())
|
||||
id String @id @unique @default(uuid())
|
||||
name String?
|
||||
userAgent String?
|
||||
owner User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
cookie String
|
||||
storageSecret String?
|
||||
creationDate DateTime @default(now())
|
||||
refreshDate DateTime @default(now())
|
||||
}
|
||||
|
||||
model Invite {
|
||||
|
|
@ -82,6 +86,7 @@ model Invite {
|
|||
model Message {
|
||||
id String @id @unique @default(uuid())
|
||||
text String
|
||||
iv String?
|
||||
editHistory String[] @default([])
|
||||
edited Boolean @default(false)
|
||||
owner User @relation(fields: [ownerId], references: [id], onDelete: Cascade)
|
||||
|
|
|
|||
|
|
@ -41,10 +41,12 @@ const postRegister = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
|
||||
const postLogin = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
const { username, password } = request.body as IPostLoginRequest;
|
||||
const userAgent = request.headers["user-agent"];
|
||||
|
||||
const session = await loginUser({
|
||||
username: username,
|
||||
password: password,
|
||||
userAgent: userAgent ?? "",
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
|
|
@ -85,6 +87,7 @@ const getRefresh = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
id: refresh[0].id,
|
||||
ownerId: refresh[0].userId,
|
||||
token: refresh[1],
|
||||
storageSecret: refresh[0].storageSecret,
|
||||
} as IGetRefreshResponseSuccess;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ interface IGetRefreshResponseSuccess {
|
|||
id: string;
|
||||
ownerId: string;
|
||||
token: string;
|
||||
storageSecret: string;
|
||||
}
|
||||
|
||||
interface IGetRefreshResponseError {
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ const getMessages = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
messages: messages.map((message) => ({
|
||||
id: message.id,
|
||||
text: message.text,
|
||||
iv: message.iv,
|
||||
edited: message.edited,
|
||||
ownerId: message.ownerId,
|
||||
creationDate: message.creationDate.getTime(),
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ interface IGetMessagesResponseSuccess {
|
|||
interface IGetMessagesResponseMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
iv: string;
|
||||
edited: boolean;
|
||||
ownerId: string;
|
||||
creationDate: number;
|
||||
|
|
|
|||
|
|
@ -29,17 +29,21 @@ import type {
|
|||
IPostCreateInviteRequest,
|
||||
IPostCreateInviteResponseError,
|
||||
IPostCreateInviteResponseSuccess,
|
||||
IDeleteMemberParams,
|
||||
IDeleteMemberResponseError,
|
||||
IDeleteMemberResponseSuccess,
|
||||
} from "./types.js";
|
||||
import {
|
||||
getCommunityById,
|
||||
createCommunityAuth,
|
||||
updateCommunityByIdAuth,
|
||||
deleteCommunityByIdAuth,
|
||||
getCommunityChannelsByIdAuth,
|
||||
getCommunityMembersByIdAuth,
|
||||
getCommunityRolesByIdAuth,
|
||||
getCommunityInvitesByIdAuth,
|
||||
createInviteAuth,
|
||||
deleteCommunityByIdAuth,
|
||||
deleteMemberByIdAuth,
|
||||
} from "../../services/community/community.js";
|
||||
import { API_ERROR } from "../errors.js";
|
||||
import type { ICreateInvite } from "../../services/community/types.js";
|
||||
|
|
@ -318,6 +322,35 @@ const postCreateInvite = async (
|
|||
} as IPostCreateInviteResponseSuccess;
|
||||
};
|
||||
|
||||
const deleteCommunityMember = async (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => {
|
||||
const { id, memberId } = request.params as IDeleteMemberParams;
|
||||
const authHeader = request.headers["authorization"];
|
||||
|
||||
const community = await deleteMemberByIdAuth(id, memberId, authHeader);
|
||||
if (!community) {
|
||||
reply.status(404);
|
||||
return {
|
||||
id: id,
|
||||
error: API_ERROR.NOT_FOUND,
|
||||
} as IDeleteMemberResponseError;
|
||||
}
|
||||
if (community === API_ERROR.ACCESS_DENIED) {
|
||||
reply.status(403);
|
||||
return {
|
||||
id: id,
|
||||
error: API_ERROR.ACCESS_DENIED,
|
||||
} as IDeleteMemberResponseError;
|
||||
}
|
||||
|
||||
return {
|
||||
id: community.id,
|
||||
userId: memberId,
|
||||
} as IDeleteMemberResponseSuccess;
|
||||
};
|
||||
|
||||
export {
|
||||
getCommunity,
|
||||
postCreateCommunity,
|
||||
|
|
@ -328,4 +361,5 @@ export {
|
|||
getRoles,
|
||||
getInvites,
|
||||
postCreateInvite,
|
||||
deleteCommunityMember,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const communityRoutes = async (fastify: FastifyInstance) => {
|
|||
fastify.get(`/:id/roles`, controller.getRoles);
|
||||
fastify.get(`/:id/invites`, controller.getInvites);
|
||||
fastify.post(`/:id/invite`, controller.postCreateInvite);
|
||||
fastify.delete(`/:id/members/:memberId`, controller.deleteCommunityMember);
|
||||
};
|
||||
|
||||
export { communityRoutes };
|
||||
|
|
|
|||
|
|
@ -158,6 +158,21 @@ interface IPostCreateInviteResponseSuccess {
|
|||
inviteId: string;
|
||||
}
|
||||
|
||||
interface IDeleteMemberParams {
|
||||
id: string;
|
||||
memberId: string;
|
||||
}
|
||||
|
||||
interface IDeleteMemberResponseError {
|
||||
id: string;
|
||||
error: API_ERROR;
|
||||
}
|
||||
|
||||
interface IDeleteMemberResponseSuccess {
|
||||
id: string;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export {
|
||||
type ICommunity,
|
||||
type IGetCommunityParams,
|
||||
|
|
@ -193,4 +208,7 @@ export {
|
|||
type IPostCreateInviteRequest,
|
||||
type IPostCreateInviteResponseError,
|
||||
type IPostCreateInviteResponseSuccess,
|
||||
type IDeleteMemberParams,
|
||||
type IDeleteMemberResponseError,
|
||||
type IDeleteMemberResponseSuccess,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ const getMessage = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
return {
|
||||
id: message.id,
|
||||
text: message.text,
|
||||
iv: message.iv,
|
||||
editHistory: message.editHistory,
|
||||
edited: message.edited,
|
||||
ownerId: message.ownerId,
|
||||
|
|
@ -71,6 +72,7 @@ const postCreateMessage = async (
|
|||
return {
|
||||
id: message.id,
|
||||
text: message.text,
|
||||
iv: message.iv,
|
||||
editHistory: message.editHistory,
|
||||
edited: message.edited,
|
||||
ownerId: message.ownerId,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type { API_ERROR } from "../errors.js";
|
|||
interface IMessage {
|
||||
id: string;
|
||||
text: string;
|
||||
iv: string;
|
||||
editHistory: string[];
|
||||
edited: boolean;
|
||||
ownerId: string;
|
||||
|
|
@ -23,6 +24,7 @@ interface IGetMessageResponseSuccess extends IMessage {}
|
|||
|
||||
interface IPostCreateMessageRequest {
|
||||
text: string;
|
||||
iv: string;
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ const getSession = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
return {
|
||||
id: session.id,
|
||||
userId: session.userId,
|
||||
name: session.name,
|
||||
userAgent: session.userAgent,
|
||||
creationDate: session.creationDate.getTime(),
|
||||
refreshDate: session.refreshDate.getTime(),
|
||||
} as IGetSessionResponseSuccess;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import type { API_ERROR } from "../errors.js";
|
|||
interface ISession {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
userAgent: string;
|
||||
creationDate: number;
|
||||
refreshDate: number;
|
||||
}
|
||||
|
||||
interface IGetSessionParams {
|
||||
|
|
|
|||
|
|
@ -90,6 +90,10 @@ interface IGetSessionsResponseSuccess {
|
|||
interface IGetSessionsResponseSession {
|
||||
id: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
userAgent: string;
|
||||
creationDate: number;
|
||||
refreshDate: number;
|
||||
}
|
||||
|
||||
interface IGetCommunitiesParams {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,10 @@ const getSessions = async (request: FastifyRequest, reply: FastifyReply) => {
|
|||
sessions: sessions.map((session) => ({
|
||||
id: session.id,
|
||||
userId: session.userId,
|
||||
name: session.name,
|
||||
userAgent: session.userAgent,
|
||||
creationDate: session.creationDate.getTime(),
|
||||
refreshDate: session.refreshDate.getTime(),
|
||||
})),
|
||||
} as IGetSessionsResponseSuccess;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { roleRoutes } from "./controllers/role/routes.js";
|
|||
import { inviteRoutes } from "./controllers/invite/routes.js";
|
||||
import { messageRoutes } from "./controllers/message/routes.js";
|
||||
import { websocketRoutes } from "./controllers/websocket/routes.js";
|
||||
import { initializeCommunitiesWithReadableUsersCache } from "./services/user/user.js";
|
||||
|
||||
const app = Fastify({
|
||||
logger: true,
|
||||
|
|
@ -25,6 +26,7 @@ const app = Fastify({
|
|||
app.register(cors, {
|
||||
origin: "http://localhost:3000",
|
||||
credentials: true,
|
||||
methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"],
|
||||
});
|
||||
|
||||
app.register(cookie, { secret: getCookieSecret() });
|
||||
|
|
@ -46,3 +48,5 @@ app.listen({ port: config.port }, (err, address) => {
|
|||
if (err) throw err;
|
||||
console.log(`Server is now listening on ${address}`);
|
||||
});
|
||||
|
||||
initializeCommunitiesWithReadableUsersCache();
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { UAParser } from "ua-parser-js";
|
||||
import type { User, Session } from "../../generated/prisma/client.js";
|
||||
import { getDB } from "../../store/store.js";
|
||||
import {
|
||||
createSessionCookie,
|
||||
createToken,
|
||||
getRandomBytesHex,
|
||||
hashPassword,
|
||||
verifyPassword,
|
||||
} from "./helpers.js";
|
||||
|
|
@ -45,6 +47,9 @@ const registerUser = async (
|
|||
};
|
||||
|
||||
const loginUser = async (login: IUserLogin): Promise<Session | null> => {
|
||||
const uaParser = new UAParser(login.userAgent);
|
||||
const sessionName = `${uaParser.getBrowser()} on ${uaParser.getOS()}`;
|
||||
|
||||
const user = await getDB().user.findUnique({
|
||||
where: { username: login.username },
|
||||
});
|
||||
|
|
@ -69,6 +74,9 @@ const loginUser = async (login: IUserLogin): Promise<Session | null> => {
|
|||
data: {
|
||||
cookie: createSessionCookie(),
|
||||
userId: user.id,
|
||||
name: sessionName,
|
||||
userAgent: login.userAgent,
|
||||
storageSecret: `${getRandomBytesHex(32)};${getRandomBytesHex(12)}`,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -90,6 +98,15 @@ const refreshSession = async (
|
|||
return null;
|
||||
}
|
||||
|
||||
await getDB().session.update({
|
||||
where: {
|
||||
id: session.id,
|
||||
},
|
||||
data: {
|
||||
refreshDate: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return [session, createToken(session.id)];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,12 @@ const getCookieSecret = (): string => {
|
|||
return process.env.COOKIE_SECRET || "";
|
||||
};
|
||||
|
||||
const getRandomBytesHex = (amount: number) => {
|
||||
return crypto.randomBytes(amount).toString("hex");
|
||||
};
|
||||
|
||||
const createSessionCookie = () => {
|
||||
return crypto.randomBytes(32).toString("hex");
|
||||
return getRandomBytesHex(32);
|
||||
};
|
||||
|
||||
const createToken = (sessionId: string) => {
|
||||
|
|
@ -152,19 +156,15 @@ const isUserOwnerOrAdmin = async (
|
|||
};
|
||||
|
||||
const getUserPermissions = async (
|
||||
user: User | null,
|
||||
community: Community | null,
|
||||
userId: string,
|
||||
communityId: string,
|
||||
): Promise<PERMISSION[]> => {
|
||||
if (!user || !community) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const roles = await getDB().role.findMany({
|
||||
where: {
|
||||
communityId: community.id,
|
||||
communityId: communityId,
|
||||
users: {
|
||||
some: {
|
||||
id: user.id,
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -184,15 +184,15 @@ const getUserPermissions = async (
|
|||
};
|
||||
|
||||
const userHasPermissions = async (
|
||||
user: User | null,
|
||||
community: Community | null,
|
||||
userId: string | undefined,
|
||||
communityId: string | undefined,
|
||||
requiredPermissions: PERMISSION[],
|
||||
): Promise<boolean> => {
|
||||
if (!user || !community) {
|
||||
if (!userId || !communityId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const userPermissions = await getUserPermissions(user, community);
|
||||
const userPermissions = await getUserPermissions(userId, communityId);
|
||||
|
||||
return requiredPermissions.every((requiredPermission) =>
|
||||
userPermissions.includes(requiredPermission),
|
||||
|
|
@ -208,7 +208,9 @@ const isUserAllowed = async (
|
|||
if (await isUserOwnerOrAdmin(user, ownerCheck)) {
|
||||
return true;
|
||||
}
|
||||
if (await userHasPermissions(user, community, requiredPermissions)) {
|
||||
if (
|
||||
await userHasPermissions(user?.id, community?.id, requiredPermissions)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -240,6 +242,7 @@ const isUserInCommunity = async (
|
|||
export {
|
||||
getJwtSecret,
|
||||
getCookieSecret,
|
||||
getRandomBytesHex,
|
||||
createSessionCookie,
|
||||
createToken,
|
||||
verifyToken,
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ interface IUserRegistration {
|
|||
interface IUserLogin {
|
||||
username: string;
|
||||
password: string;
|
||||
userAgent: string;
|
||||
}
|
||||
|
||||
interface IOwnerCheck {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import { getDB } from "../../store/store.js";
|
|||
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
|
||||
import { PERMISSION } from "../auth/permission.js";
|
||||
import { getCommunityById } from "../community/community.js";
|
||||
import { getUserIdsInCommunity } from "../user/user.js";
|
||||
import { SocketMessageTypes } from "../websocket/types.js";
|
||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||
import type { ICreateChannel, IUpdateChannel } from "./types.js";
|
||||
|
||||
const getChannelById = async (id: string): Promise<Channel | null> => {
|
||||
|
|
@ -37,11 +40,22 @@ const getChannelByIdAuth = async (
|
|||
};
|
||||
|
||||
const createChannel = async (create: ICreateChannel): Promise<Channel> => {
|
||||
return await getDB().channel.create({
|
||||
const createdChannel = await getDB().channel.create({
|
||||
data: {
|
||||
...create,
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(createdChannel.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_CHANNELS,
|
||||
payload: {
|
||||
communityId: createdChannel.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return createdChannel;
|
||||
};
|
||||
|
||||
const createChannelAuth = async (
|
||||
|
|
@ -71,7 +85,7 @@ const updateChannelById = async (
|
|||
id: string,
|
||||
update: IUpdateChannel,
|
||||
): Promise<Channel | null> => {
|
||||
return await getDB().channel.update({
|
||||
const updatedChannel = await getDB().channel.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
|
|
@ -79,6 +93,17 @@ const updateChannelById = async (
|
|||
...update,
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedChannel.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_CHANNELS,
|
||||
payload: {
|
||||
communityId: updatedChannel.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedChannel;
|
||||
};
|
||||
|
||||
const updateChannelByIdAuth = async (
|
||||
|
|
@ -107,9 +132,20 @@ const updateChannelByIdAuth = async (
|
|||
};
|
||||
|
||||
const deleteChannelById = async (id: string): Promise<Channel | null> => {
|
||||
return await getDB().channel.delete({
|
||||
const deletedChannel = await getDB().channel.delete({
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(deletedChannel.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_CHANNELS,
|
||||
payload: {
|
||||
communityId: deletedChannel.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return deletedChannel;
|
||||
};
|
||||
|
||||
const deleteChannelByIdAuth = async (
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import {
|
|||
isUserOwnerOrAdmin,
|
||||
} from "../auth/helpers.js";
|
||||
import { PERMISSION } from "../auth/permission.js";
|
||||
import { getUserIdsInCommunity } from "../user/user.js";
|
||||
import { SocketMessageTypes } from "../websocket/types.js";
|
||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||
import type {
|
||||
ICreateCommunity,
|
||||
IUpdateCommunity,
|
||||
|
|
@ -287,6 +290,7 @@ const createInviteAuth = async (
|
|||
const community = await getCommunityById(id);
|
||||
|
||||
if (
|
||||
!authUser ||
|
||||
!(await isUserAllowed(
|
||||
authUser,
|
||||
{
|
||||
|
|
@ -294,8 +298,7 @@ const createInviteAuth = async (
|
|||
},
|
||||
community,
|
||||
[PERMISSION.INVITES_CREATE],
|
||||
)) ||
|
||||
!authUser
|
||||
))
|
||||
) {
|
||||
return API_ERROR.ACCESS_DENIED;
|
||||
}
|
||||
|
|
@ -303,6 +306,60 @@ const createInviteAuth = async (
|
|||
return await createInvite(id, authUser.id, createInviteData);
|
||||
};
|
||||
|
||||
const deleteMemberById = async (
|
||||
id: string,
|
||||
memberId: string,
|
||||
): Promise<Community> => {
|
||||
const updatedCommunity = await getDB().community.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
data: {
|
||||
members: {
|
||||
disconnect: {
|
||||
id: memberId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedCommunity.id);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_MEMBERS,
|
||||
payload: {
|
||||
communityId: updatedCommunity.id,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedCommunity;
|
||||
};
|
||||
|
||||
const deleteMemberByIdAuth = async (
|
||||
id: string,
|
||||
memberId: string,
|
||||
authHeader: string | undefined,
|
||||
): Promise<Community | API_ERROR.ACCESS_DENIED> => {
|
||||
const authUser = await getUserFromAuth(authHeader);
|
||||
const community = await getCommunityById(id);
|
||||
|
||||
if (
|
||||
!(await isUserAllowed(
|
||||
authUser,
|
||||
{
|
||||
community: community,
|
||||
},
|
||||
community,
|
||||
[PERMISSION.MEMBERS_KICK],
|
||||
)) &&
|
||||
authUser?.id !== memberId
|
||||
) {
|
||||
return API_ERROR.ACCESS_DENIED;
|
||||
}
|
||||
|
||||
return await deleteMemberById(id, memberId);
|
||||
};
|
||||
|
||||
export {
|
||||
getCommunityById,
|
||||
createCommunity,
|
||||
|
|
@ -321,4 +378,6 @@ export {
|
|||
getCommunityInvitesByIdAuth,
|
||||
createInvite,
|
||||
createInviteAuth,
|
||||
deleteMemberById,
|
||||
deleteMemberByIdAuth,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,9 @@ import { getDB } from "../../store/store.js";
|
|||
import { getCommunityById } from "../community/community.js";
|
||||
import { PERMISSION } from "../auth/permission.js";
|
||||
import { API_ERROR } from "../../controllers/errors.js";
|
||||
import { getUserIdsInCommunity } from "../user/user.js";
|
||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||
import { SocketMessageTypes } from "../websocket/types.js";
|
||||
|
||||
const getInviteById = async (id: string): Promise<Invite | null> => {
|
||||
return await getDB().invite.findUnique({
|
||||
|
|
@ -63,7 +66,7 @@ const acceptInviteById = async (
|
|||
},
|
||||
});
|
||||
|
||||
return await getDB().community.update({
|
||||
const updatedCommunity = await getDB().community.update({
|
||||
where: {
|
||||
id: invite.communityId,
|
||||
},
|
||||
|
|
@ -75,6 +78,17 @@ const acceptInviteById = async (
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedCommunity.id);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_MEMBERS,
|
||||
payload: {
|
||||
communityId: updatedCommunity.id,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedCommunity;
|
||||
};
|
||||
|
||||
const acceptInviteByIdAuth = async (
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ import {
|
|||
import { PERMISSION } from "../auth/permission.js";
|
||||
import { getChannelById } from "../channel/channel.js";
|
||||
import { getCommunityById } from "../community/community.js";
|
||||
import { getUserIdsInCommunityReadMessagesPermission } from "../user/user.js";
|
||||
import { SocketMessageTypes } from "../websocket/types.js";
|
||||
import { sendMessageToUsers } from "../websocket/websocket.js";
|
||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||
import type { ICreateMessage, IUpdateMessage } from "./types.js";
|
||||
|
||||
const getMessageById = async (id: string): Promise<Message | null> => {
|
||||
|
|
@ -57,27 +58,17 @@ const createMessage = async (
|
|||
},
|
||||
});
|
||||
|
||||
const usersInCommunity = await getDB().user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
communities: {
|
||||
some: {
|
||||
id: communityId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
const userIds = usersInCommunity.map((user) => user.id);
|
||||
const userIds =
|
||||
await getUserIdsInCommunityReadMessagesPermission(communityId);
|
||||
|
||||
sendMessageToUsers(userIds, {
|
||||
type: SocketMessageTypes.NEW_MESSAGE,
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.SET_MESSAGE,
|
||||
payload: {
|
||||
channelId: message.channelId,
|
||||
message: {
|
||||
id: message.id,
|
||||
text: message.text,
|
||||
iv: message.iv ?? "",
|
||||
edited: message.edited,
|
||||
ownerId: message.ownerId,
|
||||
creationDate: message.creationDate.getTime(),
|
||||
|
|
@ -116,6 +107,7 @@ const createMessageAuth = async (
|
|||
|
||||
const updateMessageById = async (
|
||||
id: string,
|
||||
communityId: string,
|
||||
update: IUpdateMessage,
|
||||
): Promise<Message | null> => {
|
||||
const message = await getMessageById(id);
|
||||
|
|
@ -125,7 +117,7 @@ const updateMessageById = async (
|
|||
|
||||
const newEditHistory = [...message.editHistory, message.text];
|
||||
|
||||
return await getDB().message.update({
|
||||
const updatedMessage = await getDB().message.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
|
|
@ -135,6 +127,26 @@ const updateMessageById = async (
|
|||
edited: true,
|
||||
},
|
||||
});
|
||||
|
||||
const userIds =
|
||||
await getUserIdsInCommunityReadMessagesPermission(communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.SET_MESSAGE,
|
||||
payload: {
|
||||
channelId: updatedMessage.channelId,
|
||||
message: {
|
||||
id: updatedMessage.id,
|
||||
text: updatedMessage.text,
|
||||
iv: updatedMessage.iv ?? "",
|
||||
edited: updatedMessage.edited,
|
||||
ownerId: updatedMessage.ownerId,
|
||||
creationDate: updatedMessage.creationDate.getTime(),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return updatedMessage;
|
||||
};
|
||||
|
||||
const updateMessageByIdAuth = async (
|
||||
|
|
@ -148,6 +160,7 @@ const updateMessageByIdAuth = async (
|
|||
const community = await getCommunityById(channel?.communityId ?? "");
|
||||
|
||||
if (
|
||||
!community ||
|
||||
!(await isUserOwnerOrAdmin(authUser, {
|
||||
message: message,
|
||||
})) ||
|
||||
|
|
@ -156,13 +169,29 @@ const updateMessageByIdAuth = async (
|
|||
return API_ERROR.ACCESS_DENIED;
|
||||
}
|
||||
|
||||
return await updateMessageById(id, update);
|
||||
return await updateMessageById(id, community?.id, update);
|
||||
};
|
||||
|
||||
const deleteMessageById = async (id: string): Promise<Message | null> => {
|
||||
return await getDB().message.delete({
|
||||
const deleteMessageById = async (
|
||||
id: string,
|
||||
communityId: string,
|
||||
): Promise<Message | null> => {
|
||||
const deletedMessage = await getDB().message.delete({
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
const userIds =
|
||||
await getUserIdsInCommunityReadMessagesPermission(communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.DELETE_MESSAGE,
|
||||
payload: {
|
||||
channelId: deletedMessage.channelId,
|
||||
messageId: deletedMessage.id,
|
||||
},
|
||||
});
|
||||
|
||||
return deletedMessage;
|
||||
};
|
||||
|
||||
const deleteMessageByIdAuth = async (
|
||||
|
|
@ -175,6 +204,7 @@ const deleteMessageByIdAuth = async (
|
|||
const community = await getCommunityById(channel?.communityId ?? "");
|
||||
|
||||
if (
|
||||
!community ||
|
||||
!(await isUserAllowed(
|
||||
authUser,
|
||||
{
|
||||
|
|
@ -187,7 +217,7 @@ const deleteMessageByIdAuth = async (
|
|||
return API_ERROR.ACCESS_DENIED;
|
||||
}
|
||||
|
||||
return await deleteMessageById(id);
|
||||
return await deleteMessageById(id, community.id);
|
||||
};
|
||||
|
||||
export {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
interface ICreateMessage {
|
||||
text: string;
|
||||
iv: string;
|
||||
channelId: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ import { getDB } from "../../store/store.js";
|
|||
import { getUserFromAuth, isUserAllowed } from "../auth/helpers.js";
|
||||
import { PERMISSION } from "../auth/permission.js";
|
||||
import { getCommunityById } from "../community/community.js";
|
||||
import {
|
||||
getUserIdsInCommunity,
|
||||
getUserIdsWithRole,
|
||||
updateCommunitiesWithReadableUsersCache,
|
||||
} from "../user/user.js";
|
||||
import { SocketMessageTypes } from "../websocket/types.js";
|
||||
import { sendMessageToUsersWS } from "../websocket/websocket.js";
|
||||
import type { ICreateRole, IUpdateRole } from "./types.js";
|
||||
|
||||
const getRoleById = async (id: string): Promise<Role | null> => {
|
||||
|
|
@ -37,11 +44,22 @@ const getRoleByIdAuth = async (
|
|||
};
|
||||
|
||||
const createRole = async (create: ICreateRole): Promise<Role> => {
|
||||
return await getDB().role.create({
|
||||
const createdRole = await getDB().role.create({
|
||||
data: {
|
||||
...create,
|
||||
},
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(createdRole.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_ROLES,
|
||||
payload: {
|
||||
communityId: createdRole.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return createdRole;
|
||||
};
|
||||
|
||||
const createRoleAuth = async (
|
||||
|
|
@ -71,7 +89,7 @@ const updateRoleById = async (
|
|||
id: string,
|
||||
update: IUpdateRole,
|
||||
): Promise<Role | null> => {
|
||||
return await getDB().role.update({
|
||||
const updatedRole = await getDB().role.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
|
|
@ -79,6 +97,30 @@ const updateRoleById = async (
|
|||
...update,
|
||||
},
|
||||
});
|
||||
|
||||
if (!update.permissions) {
|
||||
return updatedRole;
|
||||
}
|
||||
|
||||
const usersWithRole = await getUserIdsWithRole(id);
|
||||
|
||||
usersWithRole.forEach((userId) => {
|
||||
updateCommunitiesWithReadableUsersCache(
|
||||
updatedRole.communityId,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedRole.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_ROLES,
|
||||
payload: {
|
||||
communityId: updatedRole.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedRole;
|
||||
};
|
||||
|
||||
const updateRoleByIdAuth = async (
|
||||
|
|
@ -107,9 +149,29 @@ const updateRoleByIdAuth = async (
|
|||
};
|
||||
|
||||
const deleteRoleById = async (id: string): Promise<Role | null> => {
|
||||
return await getDB().role.delete({
|
||||
const usersWithRole = await getUserIdsWithRole(id);
|
||||
|
||||
const deletedRole = await getDB().role.delete({
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
usersWithRole.forEach((userId) => {
|
||||
updateCommunitiesWithReadableUsersCache(
|
||||
deletedRole.communityId,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(deletedRole.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_ROLES,
|
||||
payload: {
|
||||
communityId: deletedRole.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return deletedRole;
|
||||
};
|
||||
|
||||
const deleteRoleByIdAuth = async (
|
||||
|
|
@ -140,7 +202,7 @@ const assignRoleById = async (
|
|||
id: string,
|
||||
userId: string,
|
||||
): Promise<Role | null> => {
|
||||
return await getDB().role.update({
|
||||
const updatedRole = await getDB().role.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
|
|
@ -152,6 +214,26 @@ const assignRoleById = async (
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const usersWithRole = await getUserIdsWithRole(id);
|
||||
|
||||
usersWithRole.forEach((userId) => {
|
||||
updateCommunitiesWithReadableUsersCache(
|
||||
updatedRole.communityId,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedRole.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_MEMBERS,
|
||||
payload: {
|
||||
communityId: updatedRole.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedRole;
|
||||
};
|
||||
|
||||
const assignRoleByIdAuth = async (
|
||||
|
|
@ -183,7 +265,7 @@ const unassignRoleById = async (
|
|||
id: string,
|
||||
userId: string,
|
||||
): Promise<Role | null> => {
|
||||
return await getDB().role.update({
|
||||
const updatedRole = await getDB().role.update({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
|
|
@ -195,6 +277,26 @@ const unassignRoleById = async (
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
const usersWithRole = await getUserIdsWithRole(id);
|
||||
|
||||
usersWithRole.forEach((userId) => {
|
||||
updateCommunitiesWithReadableUsersCache(
|
||||
updatedRole.communityId,
|
||||
userId,
|
||||
);
|
||||
});
|
||||
|
||||
const userIds = await getUserIdsInCommunity(updatedRole.communityId);
|
||||
|
||||
sendMessageToUsersWS(userIds, {
|
||||
type: SocketMessageTypes.UPDATE_MEMBERS,
|
||||
payload: {
|
||||
communityId: updatedRole.communityId,
|
||||
},
|
||||
});
|
||||
|
||||
return updatedRole;
|
||||
};
|
||||
|
||||
const unassignRoleByIdAuth = async (
|
||||
|
|
|
|||
|
|
@ -3,10 +3,82 @@ import type {
|
|||
Session,
|
||||
Community,
|
||||
} from "../../generated/prisma/client.js";
|
||||
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
|
||||
import {
|
||||
getUserFromAuth,
|
||||
getUserPermissions,
|
||||
isUserOwnerOrAdmin,
|
||||
} from "../auth/helpers.js";
|
||||
import { getDB } from "../../store/store.js";
|
||||
import { API_ERROR } from "../../controllers/errors.js";
|
||||
import type { ICreateUser, IUpdateUser } from "./types.js";
|
||||
import { PERMISSION } from "../auth/permission.js";
|
||||
|
||||
const communitiesWithReadableUsersCache = new Map<string, Set<string>>();
|
||||
|
||||
const initializeCommunitiesWithReadableUsersCache = async () => {
|
||||
const allCommunities = await getDB().community.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
ownerId: true,
|
||||
members: {
|
||||
select: {
|
||||
id: true,
|
||||
roles: {
|
||||
select: {
|
||||
communityId: true,
|
||||
permissions: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (const community of allCommunities) {
|
||||
for (const member of community.members) {
|
||||
let usersInCommunity = communitiesWithReadableUsersCache.get(
|
||||
community.id,
|
||||
);
|
||||
if (!usersInCommunity) {
|
||||
usersInCommunity = new Set<string>();
|
||||
}
|
||||
|
||||
const hasReadRole = member.roles
|
||||
.filter((role) => role.communityId === community.id)
|
||||
.some((role) =>
|
||||
role.permissions.includes(PERMISSION.MESSAGES_READ),
|
||||
);
|
||||
|
||||
if (member.id === community.ownerId || hasReadRole) {
|
||||
usersInCommunity.add(member.id);
|
||||
}
|
||||
|
||||
communitiesWithReadableUsersCache.set(
|
||||
community.id,
|
||||
usersInCommunity,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const updateCommunitiesWithReadableUsersCache = async (
|
||||
communityId: string,
|
||||
userId: string,
|
||||
) => {
|
||||
let usersInCommunity = communitiesWithReadableUsersCache.get(communityId);
|
||||
if (!usersInCommunity) {
|
||||
usersInCommunity = new Set<string>();
|
||||
communitiesWithReadableUsersCache.set(communityId, usersInCommunity);
|
||||
}
|
||||
|
||||
const userPermissions = await getUserPermissions(userId, communityId);
|
||||
|
||||
if (userPermissions.includes(PERMISSION.MESSAGES_READ)) {
|
||||
usersInCommunity.add(userId);
|
||||
} else {
|
||||
usersInCommunity.delete(userId);
|
||||
}
|
||||
};
|
||||
|
||||
const getLoggedUserAuth = async (
|
||||
authHeader: string | undefined,
|
||||
|
|
@ -182,7 +254,57 @@ const getUserCommunitiesByIdAuth = async (
|
|||
return communities;
|
||||
};
|
||||
|
||||
const getUserIdsInCommunity = async (
|
||||
communityId: string,
|
||||
): Promise<string[]> => {
|
||||
const usersInCommunity = await getDB().user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
communities: {
|
||||
some: {
|
||||
id: communityId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return usersInCommunity.map((user) => user.id);
|
||||
};
|
||||
|
||||
const getUserIdsInCommunityReadMessagesPermission = async (
|
||||
communityId: string,
|
||||
): Promise<string[]> => {
|
||||
const userIds = communitiesWithReadableUsersCache.get(communityId);
|
||||
if (!userIds) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...userIds];
|
||||
};
|
||||
|
||||
const getUserIdsWithRole = async (id: string): Promise<string[]> => {
|
||||
const usersWithRole = await getDB().user.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
roles: {
|
||||
some: {
|
||||
id: id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return usersWithRole.map((user) => user.id);
|
||||
};
|
||||
|
||||
export {
|
||||
communitiesWithReadableUsersCache,
|
||||
initializeCommunitiesWithReadableUsersCache,
|
||||
updateCommunitiesWithReadableUsersCache,
|
||||
getLoggedUserAuth,
|
||||
getUserById,
|
||||
getUserByIdAuth,
|
||||
|
|
@ -196,4 +318,7 @@ export {
|
|||
getUserSessionsByIdAuth,
|
||||
getUserCommunitiesById,
|
||||
getUserCommunitiesByIdAuth,
|
||||
getUserIdsInCommunity,
|
||||
getUserIdsInCommunityReadMessagesPermission,
|
||||
getUserIdsWithRole,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,9 +11,12 @@ enum SocketRequestTypes {
|
|||
}
|
||||
|
||||
enum SocketMessageTypes {
|
||||
NEW_ANNOUNCEMENT = "NEW_ANNOUNCEMENT",
|
||||
NEW_MESSAGE = "NEW_MESSAGE",
|
||||
NEW_CHANNEL = "NEW_CHANNEL",
|
||||
ANNOUNCEMENT = "ANNOUNCEMENT",
|
||||
SET_MESSAGE = "SET_MESSAGE",
|
||||
DELETE_MESSAGE = "DELETE_MESSAGE",
|
||||
UPDATE_CHANNELS = "UPDATE_CHANNELS",
|
||||
UPDATE_ROLES = "UPDATE_ROLES",
|
||||
UPDATE_MEMBERS = "UPDATE_MEMBERS",
|
||||
}
|
||||
|
||||
type SocketRequest = {
|
||||
|
|
@ -22,25 +25,42 @@ type SocketRequest = {
|
|||
|
||||
type SocketMessage =
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_ANNOUNCEMENT;
|
||||
type: SocketMessageTypes.ANNOUNCEMENT;
|
||||
payload: {
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_MESSAGE;
|
||||
type: SocketMessageTypes.SET_MESSAGE;
|
||||
payload: {
|
||||
channelId: string;
|
||||
message: IGetMessagesResponseMessage;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.NEW_CHANNEL;
|
||||
type: SocketMessageTypes.DELETE_MESSAGE;
|
||||
payload: {
|
||||
channelId: string;
|
||||
messageId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.UPDATE_CHANNELS;
|
||||
payload: {
|
||||
communityId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.UPDATE_ROLES;
|
||||
payload: {
|
||||
communityId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
type: SocketMessageTypes.UPDATE_MEMBERS;
|
||||
payload: {
|
||||
id: string;
|
||||
communityId: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ const onMessageWsHandler = (connection: ISocketConnection) => {
|
|||
};
|
||||
};
|
||||
|
||||
const sendMessageToUser = (userId: string, message: SocketMessage) => {
|
||||
const sendMessageToUserWS = (userId: string, message: SocketMessage) => {
|
||||
const connections = userConnections.get(userId);
|
||||
|
||||
connections?.forEach((connection) => {
|
||||
|
|
@ -61,15 +61,15 @@ const sendMessageToUser = (userId: string, message: SocketMessage) => {
|
|||
});
|
||||
};
|
||||
|
||||
const sendMessageToUsers = (userIds: string[], message: SocketMessage) => {
|
||||
const sendMessageToUsersWS = (userIds: string[], message: SocketMessage) => {
|
||||
userIds?.forEach((userId) => {
|
||||
sendMessageToUser(userId, message);
|
||||
sendMessageToUserWS(userId, message);
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
userConnections,
|
||||
handleNewWebSocket,
|
||||
sendMessageToUser,
|
||||
sendMessageToUsers,
|
||||
sendMessageToUserWS,
|
||||
sendMessageToUsersWS,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue