New controllers and services; Auth
This commit is contained in:
parent
2fc0f9c404
commit
d17f37749d
35 changed files with 1040 additions and 164 deletions
50
prisma/migrations/20251224125500_relations/migration.sql
Normal file
50
prisma/migrations/20251224125500_relations/migration.sql
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `communityId` on the `User` table. All the data in the column will be lost.
|
||||||
|
- Added the required column `ownerId` to the `Community` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Added the required column `creatorId` to the `Invite` table without a default value. This is not possible if the table is not empty.
|
||||||
|
- Made the column `communityId` on table `Invite` required. This step will fail if there are existing NULL values in that column.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Invite" DROP CONSTRAINT "Invite_communityId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "User" DROP CONSTRAINT "User_communityId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Community" ADD COLUMN "ownerId" TEXT NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Invite" ADD COLUMN "creatorId" TEXT NOT NULL,
|
||||||
|
ALTER COLUMN "communityId" SET NOT NULL;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" DROP COLUMN "communityId";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_MembersCommunitiesToUsers" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_MembersCommunitiesToUsers_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_MembersCommunitiesToUsers_B_index" ON "_MembersCommunitiesToUsers"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Community" ADD CONSTRAINT "Community_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_communityId_fkey" FOREIGN KEY ("communityId") REFERENCES "Community"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_creatorId_fkey" FOREIGN KEY ("creatorId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_MembersCommunitiesToUsers" ADD CONSTRAINT "_MembersCommunitiesToUsers_A_fkey" FOREIGN KEY ("A") REFERENCES "Community"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_MembersCommunitiesToUsers" ADD CONSTRAINT "_MembersCommunitiesToUsers_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
11
prisma/migrations/20251224231937_creationdate/migration.sql
Normal file
11
prisma/migrations/20251224231937_creationdate/migration.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Channel" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Community" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Role" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Session" ADD COLUMN "creationDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Role" ADD COLUMN "permissions" TEXT[];
|
||||||
16
prisma/migrations/20251225001552_roles/migration.sql
Normal file
16
prisma/migrations/20251225001552_roles/migration.sql
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_UsersRoleToUsers" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_UsersRoleToUsers_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_UsersRoleToUsers_B_index" ON "_UsersRoleToUsers"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UsersRoleToUsers" ADD CONSTRAINT "_UsersRoleToUsers_A_fkey" FOREIGN KEY ("A") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UsersRoleToUsers" ADD CONSTRAINT "_UsersRoleToUsers_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
31
prisma/migrations/20251225001619_roles2/migration.sql
Normal file
31
prisma/migrations/20251225001619_roles2/migration.sql
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the `_UsersRoleToUsers` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_UsersRoleToUsers" DROP CONSTRAINT "_UsersRoleToUsers_A_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "_UsersRoleToUsers" DROP CONSTRAINT "_UsersRoleToUsers_B_fkey";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "_UsersRoleToUsers";
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_UsersRolesToUsers" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_UsersRolesToUsers_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_UsersRolesToUsers_B_index" ON "_UsersRolesToUsers"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UsersRolesToUsers" ADD CONSTRAINT "_UsersRolesToUsers_A_fkey" FOREIGN KEY ("A") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_UsersRolesToUsers" ADD CONSTRAINT "_UsersRolesToUsers_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
@ -12,9 +12,12 @@ model Community {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
name String @unique
|
name String @unique
|
||||||
description String?
|
description String?
|
||||||
members User[]
|
creationDate DateTime @default(now())
|
||||||
Channel Channel[]
|
User User @relation(name: "OwnerCommunityToUser", fields: [ownerId], references: [id])
|
||||||
Role Role[]
|
ownerId String
|
||||||
|
members User[] @relation(name: "MembersCommunitiesToUsers")
|
||||||
|
channels Channel[]
|
||||||
|
roles Role[]
|
||||||
invites Invite[]
|
invites Invite[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,6 +26,7 @@ model Channel {
|
||||||
name String?
|
name String?
|
||||||
community Community? @relation(fields: [communityId], references: [id])
|
community Community? @relation(fields: [communityId], references: [id])
|
||||||
communityId String?
|
communityId String?
|
||||||
|
creationDate DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model Role {
|
model Role {
|
||||||
|
|
@ -30,6 +34,9 @@ model Role {
|
||||||
name String?
|
name String?
|
||||||
community Community @relation(fields: [communityId], references: [id])
|
community Community @relation(fields: [communityId], references: [id])
|
||||||
communityId String
|
communityId String
|
||||||
|
users User[] @relation(name: "UsersRolesToUsers")
|
||||||
|
permissions String[]
|
||||||
|
creationDate DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
|
|
@ -41,22 +48,27 @@ model User {
|
||||||
admin Boolean @default(false)
|
admin Boolean @default(false)
|
||||||
registerDate DateTime @default(now())
|
registerDate DateTime @default(now())
|
||||||
lastLogin DateTime?
|
lastLogin DateTime?
|
||||||
Community Community? @relation(fields: [communityId], references: [id])
|
|
||||||
communityId String?
|
|
||||||
Session Session[]
|
Session Session[]
|
||||||
|
ownedInvites Invite[]
|
||||||
|
ownedCommunities Community[] @relation(name: "OwnerCommunityToUser")
|
||||||
|
communities Community[] @relation(name: "MembersCommunitiesToUsers")
|
||||||
|
roles Role[] @relation(name: "UsersRolesToUsers")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Session {
|
model Session {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
owner User @relation(fields: [userId], references: [id])
|
owner User @relation(fields: [userId], references: [id])
|
||||||
token String
|
|
||||||
userId String
|
userId String
|
||||||
|
token String
|
||||||
|
creationDate DateTime @default(now())
|
||||||
}
|
}
|
||||||
|
|
||||||
model Invite {
|
model Invite {
|
||||||
id String @id @unique @default(uuid())
|
id String @id @unique @default(uuid())
|
||||||
Community Community? @relation(fields: [communityId], references: [id])
|
Community Community @relation(fields: [communityId], references: [id])
|
||||||
communityId String?
|
communityId String
|
||||||
|
User User @relation(fields: [creatorId], references: [id])
|
||||||
|
creatorId String
|
||||||
totalInvites Int @default(0)
|
totalInvites Int @default(0)
|
||||||
remainingInvites Int @default(0)
|
remainingInvites Int @default(0)
|
||||||
creationDate DateTime @default(now())
|
creationDate DateTime @default(now())
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const postLogin = async (request: FastifyRequest, _reply: FastifyReply) => {
|
||||||
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return {
|
return {
|
||||||
ownerId: "",
|
username: username,
|
||||||
error: "incorrect credentials",
|
error: "incorrect credentials",
|
||||||
} as ILoginResponseError;
|
} as ILoginResponseError;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ interface ILoginResponseSuccess {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ILoginResponseError {
|
interface ILoginResponseError {
|
||||||
ownerId: string;
|
username: string;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,38 @@
|
||||||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
import type {
|
import type {
|
||||||
IChannelParams,
|
IGetChannelParams,
|
||||||
IChannelResponseError,
|
IGetChannelResponseError,
|
||||||
IChannelResponseSuccess,
|
IGetChannelResponseSuccess,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { getChannelById } from "../../services/channel/channel.js";
|
import { getChannelByIdAuth } from "../../services/channel/channel.js";
|
||||||
|
import { API_ERROR } from "../errors.js";
|
||||||
|
|
||||||
const getChannel = async (request: FastifyRequest, _reply: FastifyReply) => {
|
const getChannel = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
const { id } = request.params as IChannelParams;
|
const { id } = request.params as IGetChannelParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
const channel = await getChannelById(id);
|
const channel = await getChannelByIdAuth(id, authHeader);
|
||||||
if (!channel) {
|
if (!channel) {
|
||||||
|
reply.status(404);
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
error: "channel does not exist",
|
error: API_ERROR.NOT_FOUND,
|
||||||
} as IChannelResponseError;
|
} as IGetChannelResponseError;
|
||||||
|
}
|
||||||
|
if (channel === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IGetChannelResponseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: channel.id,
|
id: channel.id,
|
||||||
name: channel.name,
|
name: channel.name,
|
||||||
communityId: channel.communityId,
|
communityId: channel.communityId,
|
||||||
} as IChannelResponseSuccess;
|
creationDate: channel.creationDate.getTime(),
|
||||||
|
} as IGetChannelResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getChannel };
|
export { getChannel };
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,21 @@
|
||||||
interface IChannelParams {
|
interface IGetChannelParams {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChannelResponseError {
|
interface IGetChannelResponseError {
|
||||||
id: string;
|
id: string;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IChannelResponseSuccess {
|
interface IGetChannelResponseSuccess {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
communityId: string;
|
communityId: string;
|
||||||
|
creationDate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type IChannelParams,
|
type IGetChannelParams,
|
||||||
type IChannelResponseError,
|
type IGetChannelResponseError,
|
||||||
type IChannelResponseSuccess,
|
type IGetChannelResponseSuccess,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ const getCommunity = async (request: FastifyRequest, _reply: FastifyReply) => {
|
||||||
id: community.id,
|
id: community.id,
|
||||||
name: community.name,
|
name: community.name,
|
||||||
description: community.description,
|
description: community.description,
|
||||||
|
creationDate: community.creationDate.getTime(),
|
||||||
} as ICommunityResponseSuccess;
|
} as ICommunityResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ interface ICommunityResponseSuccess {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
creationDate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
||||||
6
src/controllers/errors.ts
Normal file
6
src/controllers/errors.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
enum API_ERROR {
|
||||||
|
NOT_FOUND = "NOT_FOUND",
|
||||||
|
ACCESS_DENIED = "ACCESS_DENIED",
|
||||||
|
}
|
||||||
|
|
||||||
|
export { API_ERROR };
|
||||||
3
src/controllers/invite/index.ts
Normal file
3
src/controllers/invite/index.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
export * from "./invite.js";
|
||||||
|
export * from "./routes.js";
|
||||||
|
export * from "./types.js";
|
||||||
110
src/controllers/invite/invite.ts
Normal file
110
src/controllers/invite/invite.ts
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
|
import type {
|
||||||
|
IDeleteInviteParams,
|
||||||
|
IDeleteInviteResponseError,
|
||||||
|
IDeleteInviteResponseSuccess,
|
||||||
|
IGetInviteParams,
|
||||||
|
IGetInviteResponseError,
|
||||||
|
IGetInviteResponseSuccess,
|
||||||
|
IPostAcceptInviteParams,
|
||||||
|
IPostAcceptInviteRequest,
|
||||||
|
IPostAcceptDeleteInviteResponseError,
|
||||||
|
IPostAcceptDeleteInviteResponseSuccess,
|
||||||
|
} from "./types.js";
|
||||||
|
import {
|
||||||
|
getInviteById,
|
||||||
|
deleteInviteByIdAuth,
|
||||||
|
acceptInviteByIdAuth,
|
||||||
|
hasUnlimitedInvites,
|
||||||
|
isInviteValid,
|
||||||
|
} from "../../services/invite/invite.js";
|
||||||
|
import { API_ERROR } from "../errors.js";
|
||||||
|
import { getUserById } from "../../services/user/user.js";
|
||||||
|
|
||||||
|
const getInvite = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.params as IGetInviteParams;
|
||||||
|
|
||||||
|
const invite = await getInviteById(id);
|
||||||
|
if (!invite) {
|
||||||
|
reply.status(404);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.NOT_FOUND,
|
||||||
|
} as IGetInviteResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: invite.id,
|
||||||
|
communityId: invite.communityId,
|
||||||
|
valid: isInviteValid(invite),
|
||||||
|
unlimitedInvites: hasUnlimitedInvites(invite),
|
||||||
|
hasExpiration: invite.expirationDate != null,
|
||||||
|
remainingInvites: invite.remainingInvites,
|
||||||
|
creationDate: invite.creationDate.getTime(),
|
||||||
|
expirationDate: invite.expirationDate?.getTime() ?? 0,
|
||||||
|
} as IGetInviteResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInvite = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.params as IDeleteInviteParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const invite = await deleteInviteByIdAuth(id, authHeader);
|
||||||
|
if (!invite) {
|
||||||
|
reply.status(404);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.NOT_FOUND,
|
||||||
|
} as IDeleteInviteResponseError;
|
||||||
|
}
|
||||||
|
if (invite === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IDeleteInviteResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: invite.id,
|
||||||
|
communityId: invite.communityId,
|
||||||
|
} as IDeleteInviteResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
const postAcceptInvite = async (
|
||||||
|
request: FastifyRequest,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { id } = request.params as IPostAcceptInviteParams;
|
||||||
|
const { userId } = request.body as IPostAcceptInviteRequest;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const community = await acceptInviteByIdAuth(id, authHeader);
|
||||||
|
const user = await getUserById(id);
|
||||||
|
if (!community || !user) {
|
||||||
|
reply.status(404);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
userId: userId,
|
||||||
|
error: API_ERROR.NOT_FOUND,
|
||||||
|
} as IPostAcceptDeleteInviteResponseError;
|
||||||
|
}
|
||||||
|
if (community === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
userId: userId,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IPostAcceptDeleteInviteResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
userId: user.id,
|
||||||
|
userName: user.username,
|
||||||
|
communityId: community.id,
|
||||||
|
communityName: community.name,
|
||||||
|
} as IPostAcceptDeleteInviteResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getInvite, deleteInvite, postAcceptInvite };
|
||||||
10
src/controllers/invite/routes.ts
Normal file
10
src/controllers/invite/routes.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { type FastifyInstance } from "fastify";
|
||||||
|
import * as controller from "./index.js";
|
||||||
|
|
||||||
|
const inviteRoutes = async (fastify: FastifyInstance) => {
|
||||||
|
fastify.get(`/:id`, controller.getInvite);
|
||||||
|
fastify.delete(`/:id`, controller.deleteInvite);
|
||||||
|
fastify.post(`/:id/accept`, controller.postAcceptInvite);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { inviteRoutes };
|
||||||
70
src/controllers/invite/types.ts
Normal file
70
src/controllers/invite/types.ts
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import type { API_ERROR } from "../errors.js";
|
||||||
|
|
||||||
|
interface IGetInviteParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetInviteResponseError {
|
||||||
|
id: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IGetInviteResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
communityId: string;
|
||||||
|
valid: boolean;
|
||||||
|
unlimitedInvites: boolean;
|
||||||
|
hasExpiration: boolean;
|
||||||
|
remainingInvites: number;
|
||||||
|
creationDate: number;
|
||||||
|
expirationDate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteInviteParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteInviteResponseError {
|
||||||
|
id: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteInviteResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
communityId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPostAcceptInviteParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPostAcceptInviteRequest {
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPostAcceptDeleteInviteResponseError {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
error: API_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPostAcceptDeleteInviteResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
userName: string;
|
||||||
|
communityId: string;
|
||||||
|
communityName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
type IGetInviteParams,
|
||||||
|
type IGetInviteResponseError,
|
||||||
|
type IGetInviteResponseSuccess,
|
||||||
|
type IDeleteInviteParams,
|
||||||
|
type IDeleteInviteResponseError,
|
||||||
|
type IDeleteInviteResponseSuccess,
|
||||||
|
type IPostAcceptInviteParams,
|
||||||
|
type IPostAcceptInviteRequest,
|
||||||
|
type IPostAcceptDeleteInviteResponseError,
|
||||||
|
type IPostAcceptDeleteInviteResponseSuccess,
|
||||||
|
};
|
||||||
|
|
@ -1,27 +1,38 @@
|
||||||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
import type {
|
import type {
|
||||||
IRoleParams,
|
IGetRoleParams,
|
||||||
IRoleResponseError,
|
IGetRoleResponseError,
|
||||||
IRoleResponseSuccess,
|
IGetRoleResponseSuccess,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { getRoleById } from "../../services/role/role.js";
|
import { getRoleByIdAuth } from "../../services/role/role.js";
|
||||||
|
import { API_ERROR } from "../errors.js";
|
||||||
|
|
||||||
const getRole = async (request: FastifyRequest, _reply: FastifyReply) => {
|
const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
const { id } = request.params as IRoleParams;
|
const { id } = request.params as IGetRoleParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
const role = await getRoleById(id);
|
const role = await getRoleByIdAuth(id, authHeader);
|
||||||
if (!role) {
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
error: "role does not exist",
|
error: API_ERROR.NOT_FOUND,
|
||||||
} as IRoleResponseError;
|
} as IGetRoleResponseError;
|
||||||
|
}
|
||||||
|
if (role === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IGetRoleResponseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: role.id,
|
id: role.id,
|
||||||
name: role.name,
|
name: role.name,
|
||||||
communityId: role.communityId,
|
communityId: role.communityId,
|
||||||
} as IRoleResponseSuccess;
|
creationDate: role.creationDate.getTime(),
|
||||||
|
} as IGetRoleResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getRole };
|
export { getRole };
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
interface IRoleParams {
|
interface IGetRoleParams {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoleResponseError {
|
interface IGetRoleResponseError {
|
||||||
id: string;
|
id: string;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoleResponseSuccess {
|
interface IGetRoleResponseSuccess {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
communityId: string;
|
communityId: string;
|
||||||
|
creationDate: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type IRoleParams, type IRoleResponseError, type IRoleResponseSuccess };
|
export {
|
||||||
|
type IGetRoleParams,
|
||||||
|
type IGetRoleResponseError,
|
||||||
|
type IGetRoleResponseSuccess,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import * as controller from "./session.js";
|
||||||
|
|
||||||
const sessionRoutes = async (fastify: FastifyInstance) => {
|
const sessionRoutes = async (fastify: FastifyInstance) => {
|
||||||
fastify.get(`/:id`, controller.getSession);
|
fastify.get(`/:id`, controller.getSession);
|
||||||
|
fastify.delete(`/:id`, controller.deleteSession);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { sessionRoutes };
|
export { sessionRoutes };
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,69 @@
|
||||||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
import type {
|
import type {
|
||||||
ISessionParams,
|
IGetSessionParams,
|
||||||
ISessionResponseError,
|
IGetSessionResponseError,
|
||||||
ISessionResponseSuccess,
|
IGetSessionResponseSuccess,
|
||||||
|
IDeleteSessionParams,
|
||||||
|
IDeleteSessionResponseError,
|
||||||
|
IDeleteSessionResponseSuccess,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { getSessionById } from "../../services/session/session.js";
|
import {
|
||||||
|
deleteSessionByIdAuth,
|
||||||
|
getSessionByIdAuth,
|
||||||
|
} from "../../services/session/session.js";
|
||||||
|
import { API_ERROR } from "../errors.js";
|
||||||
|
|
||||||
const getSession = async (request: FastifyRequest, _reply: FastifyReply) => {
|
const getSession = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
const { id } = request.params as ISessionParams;
|
const { id } = request.params as IGetSessionParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
const session = await getSessionById(id);
|
const session = await getSessionByIdAuth(id, authHeader);
|
||||||
if (!session) {
|
if (!session) {
|
||||||
|
reply.status(404);
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
error: "session does not exist",
|
error: API_ERROR.NOT_FOUND,
|
||||||
} as ISessionResponseError;
|
} as IGetSessionResponseError;
|
||||||
|
}
|
||||||
|
if (session === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IGetSessionResponseError;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: session.id,
|
id: session.id,
|
||||||
userId: session.userId,
|
userId: session.userId,
|
||||||
} as ISessionResponseSuccess;
|
creationDate: session.creationDate.getTime(),
|
||||||
|
} as IGetSessionResponseSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getSession };
|
const deleteSession = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.params as IDeleteSessionParams;
|
||||||
|
const authHeader = request.headers["authorization"];
|
||||||
|
|
||||||
|
const session = await deleteSessionByIdAuth(id, authHeader);
|
||||||
|
if (!session) {
|
||||||
|
reply.status(404);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.NOT_FOUND,
|
||||||
|
} as IDeleteSessionResponseError;
|
||||||
|
}
|
||||||
|
if (session === API_ERROR.ACCESS_DENIED) {
|
||||||
|
reply.status(403);
|
||||||
|
return {
|
||||||
|
id: id,
|
||||||
|
error: API_ERROR.ACCESS_DENIED,
|
||||||
|
} as IDeleteSessionResponseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: session.id,
|
||||||
|
userId: session.userId,
|
||||||
|
} as IDeleteSessionResponseSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getSession, deleteSession };
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,37 @@
|
||||||
interface ISessionParams {
|
interface IGetSessionParams {
|
||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISessionResponseError {
|
interface IGetSessionResponseError {
|
||||||
id: string;
|
id: string;
|
||||||
error: string;
|
error: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ISessionResponseSuccess {
|
interface IGetSessionResponseSuccess {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
creationDate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteSessionParams {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteSessionResponseError {
|
||||||
|
id: string;
|
||||||
|
error: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IDeleteSessionResponseSuccess {
|
||||||
id: string;
|
id: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type ISessionParams,
|
type IGetSessionParams,
|
||||||
type ISessionResponseError,
|
type IGetSessionResponseError,
|
||||||
type ISessionResponseSuccess,
|
type IGetSessionResponseSuccess,
|
||||||
|
type IDeleteSessionParams,
|
||||||
|
type IDeleteSessionResponseError,
|
||||||
|
type IDeleteSessionResponseSuccess,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,50 @@
|
||||||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||||
|
import { getDB } from "../../store/store.js";
|
||||||
|
import { hashPassword } from "../../services/auth/auth.js";
|
||||||
|
import { getUserFromAuth } from "../../services/auth/helpers.js";
|
||||||
|
|
||||||
const getPing = async (_request: FastifyRequest, _reply: FastifyReply) => {
|
const getPing = async (_request: FastifyRequest, _reply: FastifyReply) => {
|
||||||
return [{ message: "pong" }];
|
return [{ message: "pong" }];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTest = async (_request: FastifyRequest, _reply: FastifyReply) => {
|
const getTest = async (_request: FastifyRequest, _reply: FastifyReply) => {
|
||||||
|
const authHeader = _request.headers["authorization"];
|
||||||
|
return [{ user: await getUserFromAuth(authHeader) }];
|
||||||
|
|
||||||
|
const owner = await getDB().user.create({
|
||||||
|
data: {
|
||||||
|
username: "TestUser",
|
||||||
|
passwordHash: await hashPassword("29144"),
|
||||||
|
email: "testuser@aslan2142.space",
|
||||||
|
admin: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await getDB().community.create({
|
||||||
|
data: {
|
||||||
|
name: "TestCommunity",
|
||||||
|
ownerId: owner.id,
|
||||||
|
members: {
|
||||||
|
connect: {
|
||||||
|
id: owner.id,
|
||||||
|
},
|
||||||
|
create: [
|
||||||
|
{
|
||||||
|
username: "Aslo",
|
||||||
|
passwordHash: await hashPassword("pass"),
|
||||||
|
admin: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: "Aslan",
|
||||||
|
passwordHash: await hashPassword("8556"),
|
||||||
|
email: "aslan@aslan2142.space",
|
||||||
|
admin: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return [{ message: "ok" }];
|
return [{ message: "ok" }];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import jwt from "jsonwebtoken";
|
|
||||||
import type { Session, User } from "./generated/prisma/client.js";
|
|
||||||
import { getDB } from "./store/store.js";
|
|
||||||
|
|
||||||
const getJwtSecret = () => {
|
|
||||||
return process.env.JWT_SECRET || "";
|
|
||||||
};
|
|
||||||
|
|
||||||
const verifyToken = (token: string): string | jwt.JwtPayload | null => {
|
|
||||||
try {
|
|
||||||
return jwt.verify(token, getJwtSecret());
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getSessionFromToken = async (token: string): Promise<Session | null> => {
|
|
||||||
return await getDB().session.findFirst({
|
|
||||||
where: {
|
|
||||||
token: token,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserFromToken = async (token: string): Promise<User | null> => {
|
|
||||||
const session = await getSessionFromToken(token);
|
|
||||||
|
|
||||||
return await getDB().user.findFirst({
|
|
||||||
where: {
|
|
||||||
id: session?.userId ?? "invalid",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserFromAuth = async (
|
|
||||||
authHeader: string | undefined,
|
|
||||||
): Promise<User | null> => {
|
|
||||||
const token = authHeader?.replace("Bearer ", "");
|
|
||||||
|
|
||||||
const verified = verifyToken(token ?? "") !== null;
|
|
||||||
if (!verified || !token) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await getUserFromToken(token);
|
|
||||||
if (!user) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
export {
|
|
||||||
getJwtSecret,
|
|
||||||
verifyToken,
|
|
||||||
getSessionFromToken,
|
|
||||||
getUserFromToken,
|
|
||||||
getUserFromAuth,
|
|
||||||
};
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { sessionRoutes } from "./controllers/session/routes.js";
|
||||||
import { communityRoutes } from "./controllers/community/routes.js";
|
import { communityRoutes } from "./controllers/community/routes.js";
|
||||||
import { channelRoutes } from "./controllers/channel/routes.js";
|
import { channelRoutes } from "./controllers/channel/routes.js";
|
||||||
import { roleRoutes } from "./controllers/role/routes.js";
|
import { roleRoutes } from "./controllers/role/routes.js";
|
||||||
|
import { inviteRoutes } from "./controllers/invite/routes.js";
|
||||||
|
|
||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: true,
|
logger: true,
|
||||||
|
|
@ -20,6 +21,7 @@ app.register(sessionRoutes, { prefix: "/api/v1/session" });
|
||||||
app.register(communityRoutes, { prefix: "/api/v1/community" });
|
app.register(communityRoutes, { prefix: "/api/v1/community" });
|
||||||
app.register(channelRoutes, { prefix: "/api/v1/channel" });
|
app.register(channelRoutes, { prefix: "/api/v1/channel" });
|
||||||
app.register(roleRoutes, { prefix: "/api/v1/role" });
|
app.register(roleRoutes, { prefix: "/api/v1/role" });
|
||||||
|
app.register(inviteRoutes, { prefix: "/api/v1/invite" });
|
||||||
|
|
||||||
app.listen({ port: config.port }, (err, address) => {
|
app.listen({ port: config.port }, (err, address) => {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import jwt from "jsonwebtoken";
|
||||||
import type { User, Session } from "../../generated/prisma/client.js";
|
import type { User, Session } from "../../generated/prisma/client.js";
|
||||||
import { getDB } from "../../store/store.js";
|
import { getDB } from "../../store/store.js";
|
||||||
import type { IUserLogin, IUserRegistration } from "./types.js";
|
import type { IUserLogin, IUserRegistration } from "./types.js";
|
||||||
import { getJwtSecret } from "../../helpers.js";
|
import { getJwtSecret } from "./helpers.js";
|
||||||
|
|
||||||
const registerUser = async (
|
const registerUser = async (
|
||||||
registration: IUserRegistration,
|
registration: IUserRegistration,
|
||||||
|
|
@ -38,16 +38,27 @@ const loginUser = async (login: IUserLogin): Promise<Session | null> => {
|
||||||
const user = await getDB().user.findUnique({
|
const user = await getDB().user.findUnique({
|
||||||
where: { username: login.username },
|
where: { username: login.username },
|
||||||
});
|
});
|
||||||
|
if (!user || !user.passwordHash) {
|
||||||
const passwordCorrect = await argon2.verify(
|
|
||||||
user?.passwordHash ?? "",
|
|
||||||
login.password,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!user || !passwordCorrect) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const passwordCorrect = await argon2.verify(
|
||||||
|
user.passwordHash,
|
||||||
|
login.password,
|
||||||
|
);
|
||||||
|
if (!passwordCorrect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getDB().user.update({
|
||||||
|
data: {
|
||||||
|
lastLogin: new Date(),
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return await getDB().session.create({
|
return await getDB().session.create({
|
||||||
data: {
|
data: {
|
||||||
token: createToken(user.id),
|
token: createToken(user.id),
|
||||||
|
|
|
||||||
199
src/services/auth/helpers.ts
Normal file
199
src/services/auth/helpers.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import type {
|
||||||
|
Community,
|
||||||
|
Session,
|
||||||
|
User,
|
||||||
|
} from "../../generated/prisma/client.js";
|
||||||
|
import { getDB } from "../../store/store.js";
|
||||||
|
import type { IOwnerCheck } from "./types.js";
|
||||||
|
import type { PERMISSION } from "./permission.js";
|
||||||
|
|
||||||
|
const getJwtSecret = () => {
|
||||||
|
return process.env.JWT_SECRET || "";
|
||||||
|
};
|
||||||
|
|
||||||
|
const verifyToken = (token: string): string | jwt.JwtPayload | null => {
|
||||||
|
try {
|
||||||
|
return jwt.verify(token, getJwtSecret());
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSessionFromToken = async (token: string): Promise<Session | null> => {
|
||||||
|
return await getDB().session.findFirst({
|
||||||
|
where: {
|
||||||
|
token: token,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserFromToken = async (token: string): Promise<User | null> => {
|
||||||
|
const session = await getSessionFromToken(token);
|
||||||
|
|
||||||
|
return await getDB().user.findFirst({
|
||||||
|
where: {
|
||||||
|
id: session?.userId ?? "invalid",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserFromAuth = async (
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<User | null> => {
|
||||||
|
const token = authHeader?.replace("Bearer ", "");
|
||||||
|
|
||||||
|
const verified = verifyToken(token ?? "") !== null;
|
||||||
|
if (!verified || !token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await getUserFromToken(token);
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isUserOwnerOrAdmin = async (
|
||||||
|
user: User | null,
|
||||||
|
ownerCheck: IOwnerCheck,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.admin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ownerCheck.user !== undefined && ownerCheck?.user?.id !== user.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ownerCheck.session !== undefined &&
|
||||||
|
ownerCheck.session?.userId !== user.id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ownerCheck.community !== undefined &&
|
||||||
|
ownerCheck.community?.ownerId !== user.id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ownerCheck.invite !== undefined &&
|
||||||
|
ownerCheck.invite?.creatorId !== user.id
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ownerCheck.channel !== undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (ownerCheck.role !== undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserPermissions = async (
|
||||||
|
user: User | null,
|
||||||
|
community: Community | null,
|
||||||
|
): Promise<PERMISSION[]> => {
|
||||||
|
if (!user || !community) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = await getDB().role.findMany({
|
||||||
|
where: {
|
||||||
|
communityId: community.id,
|
||||||
|
users: {
|
||||||
|
some: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!roles || roles.length < 1) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = new Set<PERMISSION>();
|
||||||
|
roles.forEach((role) => {
|
||||||
|
role.permissions.forEach((permission) => {
|
||||||
|
permissions.add(permission as PERMISSION);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...permissions];
|
||||||
|
};
|
||||||
|
|
||||||
|
const userHasPermissions = async (
|
||||||
|
user: User | null,
|
||||||
|
community: Community | null,
|
||||||
|
requiredPermissions: PERMISSION[],
|
||||||
|
): Promise<boolean> => {
|
||||||
|
if (!user || !community) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPermissions = await getUserPermissions(user, community);
|
||||||
|
|
||||||
|
return requiredPermissions.every((requiredPermission) =>
|
||||||
|
userPermissions.includes(requiredPermission),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isUserAllowed = async (
|
||||||
|
user: User | null,
|
||||||
|
ownerCheck: IOwnerCheck,
|
||||||
|
community: Community | null,
|
||||||
|
requiredPermissions: PERMISSION[],
|
||||||
|
): Promise<boolean> => {
|
||||||
|
if (await isUserOwnerOrAdmin(user, ownerCheck)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (await userHasPermissions(user, community, requiredPermissions)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isUserInCommunity = async (
|
||||||
|
user: User | null,
|
||||||
|
community: Community | null,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
if (!user || !community) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(await getDB().community.findFirst({
|
||||||
|
where: {
|
||||||
|
id: community.id,
|
||||||
|
members: {
|
||||||
|
some: {
|
||||||
|
id: user.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})) !== null
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getJwtSecret,
|
||||||
|
verifyToken,
|
||||||
|
getSessionFromToken,
|
||||||
|
getUserFromToken,
|
||||||
|
getUserFromAuth,
|
||||||
|
isUserOwnerOrAdmin,
|
||||||
|
getUserPermissions,
|
||||||
|
userHasPermissions,
|
||||||
|
isUserAllowed,
|
||||||
|
isUserInCommunity,
|
||||||
|
};
|
||||||
13
src/services/auth/permission.ts
Normal file
13
src/services/auth/permission.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
enum PERMISSION {
|
||||||
|
COMMUNITY_MANAGE = "COMMUNITY_MANAGE",
|
||||||
|
CHANNELS_MANAGE = "CHANNELS_MANAGE",
|
||||||
|
CHANNELS_READ = "CHANNELS_READ",
|
||||||
|
ROLES_READ = "ROLES_READ",
|
||||||
|
INVITES_CREATE = "INVITES_CREATE",
|
||||||
|
INVITES_DELETE = "INVITES_DELETE",
|
||||||
|
ROLES_MANAGE = "ROLES_MANAGE",
|
||||||
|
USERS_KICK = "USERS_KICK",
|
||||||
|
USERS_BAN = "USERS_BAN",
|
||||||
|
}
|
||||||
|
|
||||||
|
export { PERMISSION };
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
import type {
|
||||||
|
Channel,
|
||||||
|
Community,
|
||||||
|
Invite,
|
||||||
|
Role,
|
||||||
|
Session,
|
||||||
|
User,
|
||||||
|
} from "../../generated/prisma/client.js";
|
||||||
|
import type { PERMISSION } from "./permission.js";
|
||||||
|
|
||||||
interface IUserRegistration {
|
interface IUserRegistration {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
|
|
@ -9,4 +19,13 @@ interface IUserLogin {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { type IUserRegistration, type IUserLogin };
|
interface IOwnerCheck {
|
||||||
|
user?: User | null;
|
||||||
|
session?: Session | null;
|
||||||
|
community?: Community | null;
|
||||||
|
invite?: Invite | null;
|
||||||
|
channel?: Channel | null;
|
||||||
|
role?: Role | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { type IUserRegistration, type IUserLogin, type IOwnerCheck };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
import { API_ERROR } from "../../controllers/errors.js";
|
||||||
import type { Channel } from "../../generated/prisma/client.js";
|
import type { Channel } from "../../generated/prisma/client.js";
|
||||||
import { getDB } from "../../store/store.js";
|
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";
|
||||||
|
|
||||||
const getChannelById = async (id: string): Promise<Channel | null> => {
|
const getChannelById = async (id: string): Promise<Channel | null> => {
|
||||||
return await getDB().channel.findUnique({
|
return await getDB().channel.findUnique({
|
||||||
|
|
@ -7,4 +11,28 @@ const getChannelById = async (id: string): Promise<Channel | null> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getChannelById };
|
const getChannelByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Channel | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const channel = await getChannelById(id);
|
||||||
|
const community = await getCommunityById(channel?.communityId ?? "");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
channel: channel,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.CHANNELS_READ],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return channel;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getChannelById, getChannelByIdAuth };
|
||||||
|
|
|
||||||
1
src/services/invite/index.ts
Normal file
1
src/services/invite/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export * from "./invite.js";
|
||||||
128
src/services/invite/invite.ts
Normal file
128
src/services/invite/invite.ts
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
import type { Community, Invite } from "../../generated/prisma/client.js";
|
||||||
|
import {
|
||||||
|
getUserFromAuth,
|
||||||
|
isUserAllowed,
|
||||||
|
isUserInCommunity,
|
||||||
|
isUserOwnerOrAdmin,
|
||||||
|
userHasPermissions,
|
||||||
|
} from "../auth/helpers.js";
|
||||||
|
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";
|
||||||
|
|
||||||
|
const getInviteById = async (id: string): Promise<Invite | null> => {
|
||||||
|
return await getDB().invite.findUnique({
|
||||||
|
where: { id: id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInviteById = async (id: string): Promise<Invite | null> => {
|
||||||
|
return await getDB().invite.delete({
|
||||||
|
where: { id: id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInviteByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Invite | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const invite = await deleteInviteById(id);
|
||||||
|
const community = await getCommunityById(invite?.communityId ?? "");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
invite: invite,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.INVITES_DELETE],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return invite;
|
||||||
|
};
|
||||||
|
|
||||||
|
const acceptInviteById = async (
|
||||||
|
id: string,
|
||||||
|
userId: string,
|
||||||
|
): Promise<Community | null> => {
|
||||||
|
const invite = await getInviteById(id);
|
||||||
|
if (!invite) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
await getDB().invite.update({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
remainingInvites: invite?.remainingInvites - 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return await getDB().community.update({
|
||||||
|
where: {
|
||||||
|
id: invite.communityId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
members: {
|
||||||
|
connect: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const acceptInviteByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Community | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const invite = await getInviteById(id);
|
||||||
|
const community = await getCommunityById(invite?.communityId ?? "");
|
||||||
|
if (!authUser || !invite || !community) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await isUserInCommunity(authUser, community)) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await acceptInviteById(id, authUser.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isInviteValid = (invite: Invite): boolean => {
|
||||||
|
if (!hasUnlimitedInvites(invite) && invite.remainingInvites < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentDate = Date.now();
|
||||||
|
if (
|
||||||
|
invite.expirationDate &&
|
||||||
|
invite.expirationDate.getTime() <= currentDate
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasUnlimitedInvites = (invite: Invite): boolean => {
|
||||||
|
return invite.totalInvites === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getInviteById,
|
||||||
|
deleteInviteById,
|
||||||
|
deleteInviteByIdAuth,
|
||||||
|
acceptInviteById,
|
||||||
|
acceptInviteByIdAuth,
|
||||||
|
isInviteValid,
|
||||||
|
hasUnlimitedInvites,
|
||||||
|
};
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
|
import { API_ERROR } from "../../controllers/errors.js";
|
||||||
import type { Role } from "../../generated/prisma/client.js";
|
import type { Role } from "../../generated/prisma/client.js";
|
||||||
import { getDB } from "../../store/store.js";
|
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";
|
||||||
|
|
||||||
const getRoleById = async (id: string): Promise<Role | null> => {
|
const getRoleById = async (id: string): Promise<Role | null> => {
|
||||||
return await getDB().role.findUnique({
|
return await getDB().role.findUnique({
|
||||||
|
|
@ -7,4 +11,28 @@ const getRoleById = async (id: string): Promise<Role | null> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getRoleById };
|
const getRoleByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Role | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const role = await getRoleById(id);
|
||||||
|
const community = await getCommunityById(role?.communityId ?? "");
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserAllowed(
|
||||||
|
authUser,
|
||||||
|
{
|
||||||
|
role: role,
|
||||||
|
},
|
||||||
|
community,
|
||||||
|
[PERMISSION.ROLES_READ],
|
||||||
|
))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { getRoleById, getRoleByIdAuth };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
import { API_ERROR } from "../../controllers/errors.js";
|
||||||
import type { Session } from "../../generated/prisma/client.js";
|
import type { Session } from "../../generated/prisma/client.js";
|
||||||
import { getDB } from "../../store/store.js";
|
import { getDB } from "../../store/store.js";
|
||||||
|
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
|
||||||
|
|
||||||
const getSessionById = async (id: string): Promise<Session | null> => {
|
const getSessionById = async (id: string): Promise<Session | null> => {
|
||||||
return await getDB().session.findUnique({
|
return await getDB().session.findUnique({
|
||||||
|
|
@ -7,4 +9,51 @@ const getSessionById = async (id: string): Promise<Session | null> => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export { getSessionById };
|
const getSessionByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Session | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const session = await getSessionById(id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserOwnerOrAdmin(authUser, {
|
||||||
|
session: session,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSessionById = async (id: string): Promise<Session | null> => {
|
||||||
|
return await getDB().session.delete({
|
||||||
|
where: { id: id },
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteSessionByIdAuth = async (
|
||||||
|
id: string,
|
||||||
|
authHeader: string | undefined,
|
||||||
|
): Promise<Session | null | API_ERROR.ACCESS_DENIED> => {
|
||||||
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
|
const session = await deleteSessionById(id);
|
||||||
|
|
||||||
|
if (
|
||||||
|
!(await isUserOwnerOrAdmin(authUser, {
|
||||||
|
session: session,
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
return API_ERROR.ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return session;
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
getSessionById,
|
||||||
|
getSessionByIdAuth,
|
||||||
|
deleteSessionById,
|
||||||
|
deleteSessionByIdAuth,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import type { User, Session } from "../../generated/prisma/client.js";
|
import type { User, Session } from "../../generated/prisma/client.js";
|
||||||
import { getUserFromAuth } from "../../helpers.js";
|
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
|
||||||
import { getDB } from "../../store/store.js";
|
import { getDB } from "../../store/store.js";
|
||||||
|
|
||||||
const getUserById = async (id: string): Promise<User | null> => {
|
const getUserById = async (id: string): Promise<User | null> => {
|
||||||
|
|
@ -12,8 +12,12 @@ const getUserSessionsById = async (
|
||||||
id: string,
|
id: string,
|
||||||
authHeader: string | undefined,
|
authHeader: string | undefined,
|
||||||
): Promise<Session[] | null> => {
|
): Promise<Session[] | null> => {
|
||||||
const user = await getUserFromAuth(authHeader);
|
const authUser = await getUserFromAuth(authHeader);
|
||||||
if (!user || user.id !== id) {
|
if (
|
||||||
|
!(await isUserOwnerOrAdmin(authUser, {
|
||||||
|
user: await getUserById(id),
|
||||||
|
}))
|
||||||
|
) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue