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;
|
||||
|
|
@ -1,64 +1,76 @@
|
|||
generator client {
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
engineType = "client"
|
||||
provider = "prisma-client"
|
||||
output = "../src/generated/prisma"
|
||||
engineType = "client"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
provider = "postgresql"
|
||||
}
|
||||
|
||||
model Community {
|
||||
id String @id @unique @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
members User[]
|
||||
Channel Channel[]
|
||||
Role Role[]
|
||||
invites Invite[]
|
||||
id String @id @unique @default(uuid())
|
||||
name String @unique
|
||||
description String?
|
||||
creationDate DateTime @default(now())
|
||||
User User @relation(name: "OwnerCommunityToUser", fields: [ownerId], references: [id])
|
||||
ownerId String
|
||||
members User[] @relation(name: "MembersCommunitiesToUsers")
|
||||
channels Channel[]
|
||||
roles Role[]
|
||||
invites Invite[]
|
||||
}
|
||||
|
||||
model Channel {
|
||||
id String @id @unique @default(uuid())
|
||||
name String?
|
||||
community Community? @relation(fields: [communityId], references: [id])
|
||||
communityId String?
|
||||
id String @id @unique @default(uuid())
|
||||
name String?
|
||||
community Community? @relation(fields: [communityId], references: [id])
|
||||
communityId String?
|
||||
creationDate DateTime @default(now())
|
||||
}
|
||||
|
||||
model Role {
|
||||
id String @id @unique @default(uuid())
|
||||
name String?
|
||||
community Community @relation(fields: [communityId], references: [id])
|
||||
communityId String
|
||||
id String @id @unique @default(uuid())
|
||||
name String?
|
||||
community Community @relation(fields: [communityId], references: [id])
|
||||
communityId String
|
||||
users User[] @relation(name: "UsersRolesToUsers")
|
||||
permissions String[]
|
||||
creationDate DateTime @default(now())
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @unique @default(uuid())
|
||||
username String @unique
|
||||
email String? @unique
|
||||
passwordHash String?
|
||||
description String?
|
||||
admin Boolean @default(false)
|
||||
registerDate DateTime @default(now())
|
||||
lastLogin DateTime?
|
||||
Community Community? @relation(fields: [communityId], references: [id])
|
||||
communityId String?
|
||||
Session Session[]
|
||||
id String @id @unique @default(uuid())
|
||||
username String @unique
|
||||
email String? @unique
|
||||
passwordHash String?
|
||||
description String?
|
||||
admin Boolean @default(false)
|
||||
registerDate DateTime @default(now())
|
||||
lastLogin DateTime?
|
||||
Session Session[]
|
||||
ownedInvites Invite[]
|
||||
ownedCommunities Community[] @relation(name: "OwnerCommunityToUser")
|
||||
communities Community[] @relation(name: "MembersCommunitiesToUsers")
|
||||
roles Role[] @relation(name: "UsersRolesToUsers")
|
||||
}
|
||||
|
||||
model Session {
|
||||
id String @id @unique @default(uuid())
|
||||
owner User @relation(fields: [userId], references: [id])
|
||||
token String
|
||||
userId String
|
||||
id String @id @unique @default(uuid())
|
||||
owner User @relation(fields: [userId], references: [id])
|
||||
userId String
|
||||
token String
|
||||
creationDate DateTime @default(now())
|
||||
}
|
||||
|
||||
model Invite {
|
||||
id String @id @unique @default(uuid())
|
||||
Community Community? @relation(fields: [communityId], references: [id])
|
||||
communityId String?
|
||||
totalInvites Int @default(0)
|
||||
remainingInvites Int @default(0)
|
||||
creationDate DateTime @default(now())
|
||||
expirationDate DateTime?
|
||||
id String @id @unique @default(uuid())
|
||||
Community Community @relation(fields: [communityId], references: [id])
|
||||
communityId String
|
||||
User User @relation(fields: [creatorId], references: [id])
|
||||
creatorId String
|
||||
totalInvites Int @default(0)
|
||||
remainingInvites Int @default(0)
|
||||
creationDate DateTime @default(now())
|
||||
expirationDate DateTime?
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ const postLogin = async (request: FastifyRequest, _reply: FastifyReply) => {
|
|||
|
||||
if (!session) {
|
||||
return {
|
||||
ownerId: "",
|
||||
username: username,
|
||||
error: "incorrect credentials",
|
||||
} as ILoginResponseError;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ interface ILoginResponseSuccess {
|
|||
}
|
||||
|
||||
interface ILoginResponseError {
|
||||
ownerId: string;
|
||||
username: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,38 @@
|
|||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||
import type {
|
||||
IChannelParams,
|
||||
IChannelResponseError,
|
||||
IChannelResponseSuccess,
|
||||
IGetChannelParams,
|
||||
IGetChannelResponseError,
|
||||
IGetChannelResponseSuccess,
|
||||
} 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 { id } = request.params as IChannelParams;
|
||||
const getChannel = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
const { id } = request.params as IGetChannelParams;
|
||||
const authHeader = request.headers["authorization"];
|
||||
|
||||
const channel = await getChannelById(id);
|
||||
const channel = await getChannelByIdAuth(id, authHeader);
|
||||
if (!channel) {
|
||||
reply.status(404);
|
||||
return {
|
||||
id: id,
|
||||
error: "channel does not exist",
|
||||
} as IChannelResponseError;
|
||||
error: API_ERROR.NOT_FOUND,
|
||||
} as IGetChannelResponseError;
|
||||
}
|
||||
if (channel === API_ERROR.ACCESS_DENIED) {
|
||||
reply.status(403);
|
||||
return {
|
||||
id: id,
|
||||
error: API_ERROR.ACCESS_DENIED,
|
||||
} as IGetChannelResponseError;
|
||||
}
|
||||
|
||||
return {
|
||||
id: channel.id,
|
||||
name: channel.name,
|
||||
communityId: channel.communityId,
|
||||
} as IChannelResponseSuccess;
|
||||
creationDate: channel.creationDate.getTime(),
|
||||
} as IGetChannelResponseSuccess;
|
||||
};
|
||||
|
||||
export { getChannel };
|
||||
|
|
|
|||
|
|
@ -1,20 +1,21 @@
|
|||
interface IChannelParams {
|
||||
interface IGetChannelParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface IChannelResponseError {
|
||||
interface IGetChannelResponseError {
|
||||
id: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface IChannelResponseSuccess {
|
||||
interface IGetChannelResponseSuccess {
|
||||
id: string;
|
||||
name: string;
|
||||
communityId: string;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
export {
|
||||
type IChannelParams,
|
||||
type IChannelResponseError,
|
||||
type IChannelResponseSuccess,
|
||||
type IGetChannelParams,
|
||||
type IGetChannelResponseError,
|
||||
type IGetChannelResponseSuccess,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ const getCommunity = async (request: FastifyRequest, _reply: FastifyReply) => {
|
|||
id: community.id,
|
||||
name: community.name,
|
||||
description: community.description,
|
||||
creationDate: community.creationDate.getTime(),
|
||||
} as ICommunityResponseSuccess;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ interface ICommunityResponseSuccess {
|
|||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
creationDate: number;
|
||||
}
|
||||
|
||||
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 {
|
||||
IRoleParams,
|
||||
IRoleResponseError,
|
||||
IRoleResponseSuccess,
|
||||
IGetRoleParams,
|
||||
IGetRoleResponseError,
|
||||
IGetRoleResponseSuccess,
|
||||
} 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 { id } = request.params as IRoleParams;
|
||||
const getRole = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
const { id } = request.params as IGetRoleParams;
|
||||
const authHeader = request.headers["authorization"];
|
||||
|
||||
const role = await getRoleById(id);
|
||||
const role = await getRoleByIdAuth(id, authHeader);
|
||||
if (!role) {
|
||||
reply.status(404);
|
||||
return {
|
||||
id: id,
|
||||
error: "role does not exist",
|
||||
} as IRoleResponseError;
|
||||
error: API_ERROR.NOT_FOUND,
|
||||
} as IGetRoleResponseError;
|
||||
}
|
||||
if (role === API_ERROR.ACCESS_DENIED) {
|
||||
reply.status(403);
|
||||
return {
|
||||
id: id,
|
||||
error: API_ERROR.ACCESS_DENIED,
|
||||
} as IGetRoleResponseError;
|
||||
}
|
||||
|
||||
return {
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
communityId: role.communityId,
|
||||
} as IRoleResponseSuccess;
|
||||
creationDate: role.creationDate.getTime(),
|
||||
} as IGetRoleResponseSuccess;
|
||||
};
|
||||
|
||||
export { getRole };
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
interface IRoleParams {
|
||||
interface IGetRoleParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface IRoleResponseError {
|
||||
interface IGetRoleResponseError {
|
||||
id: string;
|
||||
error: string;
|
||||
}
|
||||
|
||||
interface IRoleResponseSuccess {
|
||||
interface IGetRoleResponseSuccess {
|
||||
id: string;
|
||||
name: 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) => {
|
||||
fastify.get(`/:id`, controller.getSession);
|
||||
fastify.delete(`/:id`, controller.deleteSession);
|
||||
};
|
||||
|
||||
export { sessionRoutes };
|
||||
|
|
|
|||
|
|
@ -1,26 +1,69 @@
|
|||
import { type FastifyReply, type FastifyRequest } from "fastify";
|
||||
import type {
|
||||
ISessionParams,
|
||||
ISessionResponseError,
|
||||
ISessionResponseSuccess,
|
||||
IGetSessionParams,
|
||||
IGetSessionResponseError,
|
||||
IGetSessionResponseSuccess,
|
||||
IDeleteSessionParams,
|
||||
IDeleteSessionResponseError,
|
||||
IDeleteSessionResponseSuccess,
|
||||
} 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 { id } = request.params as ISessionParams;
|
||||
const getSession = async (request: FastifyRequest, reply: FastifyReply) => {
|
||||
const { id } = request.params as IGetSessionParams;
|
||||
const authHeader = request.headers["authorization"];
|
||||
|
||||
const session = await getSessionById(id);
|
||||
const session = await getSessionByIdAuth(id, authHeader);
|
||||
if (!session) {
|
||||
reply.status(404);
|
||||
return {
|
||||
id: id,
|
||||
error: "session does not exist",
|
||||
} as ISessionResponseError;
|
||||
error: API_ERROR.NOT_FOUND,
|
||||
} as IGetSessionResponseError;
|
||||
}
|
||||
if (session === API_ERROR.ACCESS_DENIED) {
|
||||
reply.status(403);
|
||||
return {
|
||||
id: id,
|
||||
error: API_ERROR.ACCESS_DENIED,
|
||||
} as IGetSessionResponseError;
|
||||
}
|
||||
|
||||
return {
|
||||
id: session.id,
|
||||
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;
|
||||
}
|
||||
|
||||
interface ISessionResponseError {
|
||||
interface IGetSessionResponseError {
|
||||
id: 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;
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export {
|
||||
type ISessionParams,
|
||||
type ISessionResponseError,
|
||||
type ISessionResponseSuccess,
|
||||
type IGetSessionParams,
|
||||
type IGetSessionResponseError,
|
||||
type IGetSessionResponseSuccess,
|
||||
type IDeleteSessionParams,
|
||||
type IDeleteSessionResponseError,
|
||||
type IDeleteSessionResponseSuccess,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,50 @@
|
|||
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) => {
|
||||
return [{ message: "pong" }];
|
||||
};
|
||||
|
||||
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" }];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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 { channelRoutes } from "./controllers/channel/routes.js";
|
||||
import { roleRoutes } from "./controllers/role/routes.js";
|
||||
import { inviteRoutes } from "./controllers/invite/routes.js";
|
||||
|
||||
const app = Fastify({
|
||||
logger: true,
|
||||
|
|
@ -20,6 +21,7 @@ app.register(sessionRoutes, { prefix: "/api/v1/session" });
|
|||
app.register(communityRoutes, { prefix: "/api/v1/community" });
|
||||
app.register(channelRoutes, { prefix: "/api/v1/channel" });
|
||||
app.register(roleRoutes, { prefix: "/api/v1/role" });
|
||||
app.register(inviteRoutes, { prefix: "/api/v1/invite" });
|
||||
|
||||
app.listen({ port: config.port }, (err, address) => {
|
||||
if (err) throw err;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import jwt from "jsonwebtoken";
|
|||
import type { User, Session } from "../../generated/prisma/client.js";
|
||||
import { getDB } from "../../store/store.js";
|
||||
import type { IUserLogin, IUserRegistration } from "./types.js";
|
||||
import { getJwtSecret } from "../../helpers.js";
|
||||
import { getJwtSecret } from "./helpers.js";
|
||||
|
||||
const registerUser = async (
|
||||
registration: IUserRegistration,
|
||||
|
|
@ -38,16 +38,27 @@ const loginUser = async (login: IUserLogin): Promise<Session | null> => {
|
|||
const user = await getDB().user.findUnique({
|
||||
where: { username: login.username },
|
||||
});
|
||||
|
||||
const passwordCorrect = await argon2.verify(
|
||||
user?.passwordHash ?? "",
|
||||
login.password,
|
||||
);
|
||||
|
||||
if (!user || !passwordCorrect) {
|
||||
if (!user || !user.passwordHash) {
|
||||
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({
|
||||
data: {
|
||||
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 {
|
||||
username: string;
|
||||
password: string;
|
||||
|
|
@ -9,4 +19,13 @@ interface IUserLogin {
|
|||
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 { 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> => {
|
||||
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 { 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> => {
|
||||
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 { getDB } from "../../store/store.js";
|
||||
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
|
||||
|
||||
const getSessionById = async (id: string): Promise<Session | null> => {
|
||||
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 { getUserFromAuth } from "../../helpers.js";
|
||||
import { getUserFromAuth, isUserOwnerOrAdmin } from "../auth/helpers.js";
|
||||
import { getDB } from "../../store/store.js";
|
||||
|
||||
const getUserById = async (id: string): Promise<User | null> => {
|
||||
|
|
@ -12,8 +12,12 @@ const getUserSessionsById = async (
|
|||
id: string,
|
||||
authHeader: string | undefined,
|
||||
): Promise<Session[] | null> => {
|
||||
const user = await getUserFromAuth(authHeader);
|
||||
if (!user || user.id !== id) {
|
||||
const authUser = await getUserFromAuth(authHeader);
|
||||
if (
|
||||
!(await isUserOwnerOrAdmin(authUser, {
|
||||
user: await getUserById(id),
|
||||
}))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue