mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-13 16:36:41 +00:00
ability to remove user from org
This commit is contained in:
@@ -15,7 +15,7 @@ export async function ensureActions() {
|
||||
const defaultRoles = await db
|
||||
.select()
|
||||
.from(roles)
|
||||
.where(eq(roles.isSuperuserRole, true))
|
||||
.where(eq(roles.isSuperUserRole, true))
|
||||
.execute();
|
||||
|
||||
// Add new actions
|
||||
@@ -38,15 +38,15 @@ export async function ensureActions() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSuperuserRole(orgId: string) {
|
||||
export async function createSuperUserRole(orgId: string) {
|
||||
// Create the Default role if it doesn't exist
|
||||
const [insertedRole] = await db
|
||||
.insert(roles)
|
||||
.values({
|
||||
orgId,
|
||||
isSuperuserRole: true,
|
||||
name: 'Superuser',
|
||||
description: 'Superuser role with all actions'
|
||||
isSuperUserRole: true,
|
||||
name: 'Super User',
|
||||
description: 'Super User role with all actions'
|
||||
})
|
||||
.returning({ roleId: roles.roleId })
|
||||
.execute();
|
||||
@@ -56,7 +56,7 @@ export async function createSuperuserRole(orgId: string) {
|
||||
const actionIds = await db.select().from(actions).execute();
|
||||
|
||||
if (actionIds.length === 0) {
|
||||
logger.info('No actions to assign to the Superuser role');
|
||||
logger.info('No actions to assign to the Super User role');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -131,7 +131,7 @@ export const roles = sqliteTable("roles", {
|
||||
orgId: text("orgId").references(() => orgs.orgId, {
|
||||
onDelete: "cascade",
|
||||
}),
|
||||
isSuperuserRole: integer("isSuperuserRole", { mode: "boolean" }),
|
||||
isSuperUserRole: integer("isSuperUserRole", { mode: "boolean" }),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
});
|
||||
|
||||
75
server/emails/templates/SendInviteLink.tsx
Normal file
75
server/emails/templates/SendInviteLink.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import {
|
||||
Body,
|
||||
Container,
|
||||
Head,
|
||||
Heading,
|
||||
Html,
|
||||
Preview,
|
||||
Section,
|
||||
Text,
|
||||
Tailwind,
|
||||
Button,
|
||||
} from "@react-email/components";
|
||||
import * as React from "react";
|
||||
|
||||
interface SendInviteLinkProps {
|
||||
email: string;
|
||||
inviteLink: string;
|
||||
orgName: string;
|
||||
inviterName?: string;
|
||||
expiresInDays: string;
|
||||
}
|
||||
|
||||
export const SendInviteLink = ({
|
||||
email,
|
||||
inviteLink,
|
||||
orgName,
|
||||
inviterName,
|
||||
expiresInDays,
|
||||
}: SendInviteLinkProps) => {
|
||||
const previewText = `${inviterName} invited to join ${orgName}`;
|
||||
|
||||
return (
|
||||
<Html>
|
||||
<Head />
|
||||
<Preview>{previewText}</Preview>
|
||||
<Tailwind>
|
||||
<Body className="font-sans">
|
||||
<Container className="bg-white border border-solid border-gray-200 p-6 max-w-lg mx-auto my-8">
|
||||
<Heading className="text-2xl font-semibold text-gray-800 text-center">
|
||||
You're invite to join a Fossorial organization
|
||||
</Heading>
|
||||
<Text className="text-base text-gray-700 mt-4">
|
||||
Hi {email || "there"},
|
||||
</Text>
|
||||
<Text className="text-base text-gray-700 mt-2">
|
||||
You’ve been invited to join the organization{" "}
|
||||
{orgName}
|
||||
{inviterName ? ` by ${inviterName}.` : ""}. Please
|
||||
access the link below to accept the invite.
|
||||
</Text>
|
||||
<Text className="text-base text-gray-700 mt-2">
|
||||
This invite will expire in{" "}
|
||||
<b>{expiresInDays} days.</b>
|
||||
</Text>
|
||||
<Section className="text-center my-6">
|
||||
<Button
|
||||
href={inviteLink}
|
||||
className="rounded-md bg-gray-600 px-[12px] py-[12px] text-center font-semibold text-white cursor-pointer"
|
||||
>
|
||||
Accept invitation to {orgName}
|
||||
</Button>
|
||||
</Section>
|
||||
<Text className="text-sm text-gray-500 mt-6">
|
||||
Best regards,
|
||||
<br />
|
||||
Fossorial
|
||||
</Text>
|
||||
</Container>
|
||||
</Body>
|
||||
</Tailwind>
|
||||
</Html>
|
||||
);
|
||||
};
|
||||
|
||||
export default SendInviteLink;
|
||||
@@ -11,7 +11,7 @@ export * from "./verifyResourceAccess";
|
||||
export * from "./verifyTargetAccess";
|
||||
export * from "./verifyRoleAccess";
|
||||
export * from "./verifyUserAccess";
|
||||
export * from "./verifySuperuser";
|
||||
export * from "./verifySuperUser";
|
||||
export * from "./verifyEmail";
|
||||
export * from "./requestEmailVerificationCode";
|
||||
export * from "./changePassword";
|
||||
|
||||
@@ -4,7 +4,7 @@ import db from "@server/db";
|
||||
import { users, emailVerificationCodes } from "@server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import VerifyEmail from "@server/emails/templates/verifyEmailCode";
|
||||
import VerifyEmail from "@server/emails/templates/VerifyEmailCode";
|
||||
import config from "@server/config";
|
||||
|
||||
export async function sendEmailVerificationCode(
|
||||
|
||||
@@ -1,21 +1,29 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { db } from '@server/db';
|
||||
import { userOrgs } from '@server/db/schema';
|
||||
import { and, eq } from 'drizzle-orm';
|
||||
import createHttpError from 'http-errors';
|
||||
import HttpCode from '@server/types/HttpCode';
|
||||
import { AuthenticatedRequest } from '@server/types/Auth';
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { db } from "@server/db";
|
||||
import { userOrgs } from "@server/db/schema";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
import { AuthenticatedRequest } from "@server/types/Auth";
|
||||
|
||||
export function verifyOrgAccess(req: Request, res: Response, next: NextFunction) {
|
||||
export function verifyOrgAccess(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const userId = req.user!.userId; // Assuming you have user information in the request
|
||||
const orgId = req.params.orgId;
|
||||
|
||||
if (!userId) {
|
||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
||||
);
|
||||
}
|
||||
|
||||
if (!orgId) {
|
||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
||||
return next(
|
||||
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
|
||||
);
|
||||
}
|
||||
|
||||
db.select()
|
||||
@@ -23,7 +31,12 @@ export function verifyOrgAccess(req: Request, res: Response, next: NextFunction)
|
||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
||||
.then((result) => {
|
||||
if (result.length === 0) {
|
||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||
next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
"User does not have access to this organization"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// User has access, attach the user's role to the request for potential future use
|
||||
req.userOrgRoleId = result[0].roleId;
|
||||
@@ -32,6 +45,11 @@ export function verifyOrgAccess(req: Request, res: Response, next: NextFunction)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
||||
next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Error verifying organization access"
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import createHttpError from 'http-errors';
|
||||
import HttpCode from '@server/types/HttpCode';
|
||||
import logger from '@server/logger';
|
||||
|
||||
export async function verifySuperuser(req: Request, res: Response, next: NextFunction) {
|
||||
export async function verifySuperUser(req: Request, res: Response, next: NextFunction) {
|
||||
const userId = req.user?.userId; // Assuming you have user information in the request
|
||||
const orgId = req.userOrgId;
|
||||
|
||||
@@ -30,14 +30,14 @@ export async function verifySuperuser(req: Request, res: Response, next: NextFun
|
||||
}
|
||||
|
||||
// get userOrgRole[0].roleId
|
||||
// Check if the user's role in the organization is a superuser role
|
||||
// Check if the user's role in the organization is a Super User role
|
||||
const userRole = await db.select()
|
||||
.from(roles)
|
||||
.where(eq(roles.roleId, userOrgRole[0].roleId))
|
||||
.limit(1);
|
||||
|
||||
if (userRole.length === 0 || !userRole[0].isSuperuserRole) {
|
||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have superuser access'));
|
||||
if (userRole.length === 0 || !userRole[0].isSuperUserRole) {
|
||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have Super User access'));
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@@ -1,37 +1,62 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { db } from '@server/db';
|
||||
import { sites, userOrgs, userSites, roleSites, roles } from '@server/db/schema';
|
||||
import { and, eq, or } from 'drizzle-orm';
|
||||
import createHttpError from 'http-errors';
|
||||
import HttpCode from '@server/types/HttpCode';
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { db } from "@server/db";
|
||||
import {
|
||||
sites,
|
||||
userOrgs,
|
||||
userSites,
|
||||
roleSites,
|
||||
roles,
|
||||
} from "@server/db/schema";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
import createHttpError from "http-errors";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
|
||||
export async function verifyUserAccess(req: Request, res: Response, next: NextFunction) {
|
||||
export async function verifyUserAccess(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) {
|
||||
const userId = req.user!.userId; // Assuming you have user information in the request
|
||||
const reqUserId = req.params.userId || req.body.userId || req.query.userId;
|
||||
|
||||
if (!userId) {
|
||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||
return next(
|
||||
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
||||
);
|
||||
}
|
||||
|
||||
if (!reqUserId) {
|
||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid user ID'));
|
||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid user ID"));
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const userOrg = await db.select()
|
||||
const userOrg = await db
|
||||
.select()
|
||||
.from(userOrgs)
|
||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId!)))
|
||||
.where(
|
||||
and(
|
||||
eq(userOrgs.userId, reqUserId),
|
||||
eq(userOrgs.orgId, req.userOrgId!)
|
||||
)
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (userOrg.length === 0) {
|
||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this user'));
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
"User does not have access to this user"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// If we reach here, the user doesn't have access to the site
|
||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this site'));
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access'));
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
"Error checking if user has access to this user"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
verifyResourceAccess,
|
||||
verifyTargetAccess,
|
||||
verifyRoleAccess,
|
||||
verifySuperuser,
|
||||
verifySuperUser,
|
||||
verifyUserInRole,
|
||||
verifyUserAccess,
|
||||
} from "./auth";
|
||||
@@ -40,7 +40,7 @@ authenticated.put("/org", getUserOrgs, org.createOrg);
|
||||
authenticated.get("/orgs", getUserOrgs, org.listOrgs); // TODO we need to check the orgs here
|
||||
authenticated.get("/org/:orgId", verifyOrgAccess, org.getOrg);
|
||||
authenticated.post("/org/:orgId", verifyOrgAccess, org.updateOrg);
|
||||
authenticated.delete("/org/:orgId", verifyOrgAccess, org.deleteOrg);
|
||||
// authenticated.delete("/org/:orgId", verifyOrgAccess, org.deleteOrg);
|
||||
|
||||
authenticated.put("/org/:orgId/site", verifyOrgAccess, site.createSite);
|
||||
authenticated.get("/org/:orgId/sites", verifyOrgAccess, site.listSites);
|
||||
@@ -52,7 +52,7 @@ authenticated.get(
|
||||
site.pickSiteDefaults
|
||||
);
|
||||
authenticated.get("/site/:siteId", verifySiteAccess, site.getSite);
|
||||
authenticated.get("/site/:siteId/roles", verifySiteAccess, site.listSiteRoles);
|
||||
// authenticated.get("/site/:siteId/roles", verifySiteAccess, site.listSiteRoles);
|
||||
authenticated.post("/site/:siteId", verifySiteAccess, site.updateSite);
|
||||
authenticated.delete("/site/:siteId", verifySiteAccess, site.deleteSite);
|
||||
|
||||
@@ -75,11 +75,11 @@ authenticated.post(
|
||||
); // maybe make this /invite/create instead
|
||||
authenticated.post("/invite/accept", user.acceptInvite);
|
||||
|
||||
authenticated.get(
|
||||
"/resource/:resourceId/roles",
|
||||
verifyResourceAccess,
|
||||
resource.listResourceRoles
|
||||
);
|
||||
// authenticated.get(
|
||||
// "/resource/:resourceId/roles",
|
||||
// verifyResourceAccess,
|
||||
// resource.listResourceRoles
|
||||
// );
|
||||
authenticated.get(
|
||||
"/resource/:resourceId",
|
||||
verifyResourceAccess,
|
||||
@@ -121,85 +121,85 @@ authenticated.delete(
|
||||
// authenticated.put(
|
||||
// "/org/:orgId/role",
|
||||
// verifyOrgAccess,
|
||||
// verifySuperuser,
|
||||
// verifySuperUser,
|
||||
// role.createRole
|
||||
// );
|
||||
authenticated.get("/org/:orgId/roles", verifyOrgAccess, role.listRoles);
|
||||
authenticated.get(
|
||||
"/role/:roleId",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.getRole
|
||||
);
|
||||
// authenticated.get("/org/:orgId/roles", verifyOrgAccess, role.listRoles);
|
||||
// authenticated.get(
|
||||
// "/role/:roleId",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.getRole
|
||||
// );
|
||||
// authenticated.post(
|
||||
// "/role/:roleId",
|
||||
// verifyRoleAccess,
|
||||
// verifySuperuser,
|
||||
// verifySuperUser,
|
||||
// role.updateRole
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/role/:roleId",
|
||||
// verifyRoleAccess,
|
||||
// verifySuperuser,
|
||||
// verifySuperUser,
|
||||
// role.deleteRole
|
||||
// );
|
||||
|
||||
authenticated.put(
|
||||
"/role/:roleId/site",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.addRoleSite
|
||||
);
|
||||
authenticated.delete(
|
||||
"/role/:roleId/site",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.removeRoleSite
|
||||
);
|
||||
authenticated.get(
|
||||
"/role/:roleId/sites",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.listRoleSites
|
||||
);
|
||||
authenticated.put(
|
||||
"/role/:roleId/resource",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.addRoleResource
|
||||
);
|
||||
authenticated.delete(
|
||||
"/role/:roleId/resource",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.removeRoleResource
|
||||
);
|
||||
authenticated.get(
|
||||
"/role/:roleId/resources",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.listRoleResources
|
||||
);
|
||||
authenticated.put(
|
||||
"/role/:roleId/action",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
role.addRoleAction
|
||||
);
|
||||
authenticated.delete(
|
||||
"/role/:roleId/action",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
verifySuperuser,
|
||||
role.removeRoleAction
|
||||
);
|
||||
authenticated.get(
|
||||
"/role/:roleId/actions",
|
||||
verifyRoleAccess,
|
||||
verifyUserInRole,
|
||||
verifySuperuser,
|
||||
role.listRoleActions
|
||||
);
|
||||
// authenticated.put(
|
||||
// "/role/:roleId/site",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.addRoleSite
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/role/:roleId/site",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.removeRoleSite
|
||||
// );
|
||||
// authenticated.get(
|
||||
// "/role/:roleId/sites",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.listRoleSites
|
||||
// );
|
||||
// authenticated.put(
|
||||
// "/role/:roleId/resource",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.addRoleResource
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/role/:roleId/resource",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.removeRoleResource
|
||||
// );
|
||||
// authenticated.get(
|
||||
// "/role/:roleId/resources",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.listRoleResources
|
||||
// );
|
||||
// authenticated.put(
|
||||
// "/role/:roleId/action",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// role.addRoleAction
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/role/:roleId/action",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// verifySuperUser,
|
||||
// role.removeRoleAction
|
||||
// );
|
||||
// authenticated.get(
|
||||
// "/role/:roleId/actions",
|
||||
// verifyRoleAccess,
|
||||
// verifyUserInRole,
|
||||
// verifySuperUser,
|
||||
// role.listRoleActions
|
||||
// );
|
||||
|
||||
unauthenticated.get("/user", verifySessionMiddleware, user.getUser);
|
||||
|
||||
@@ -211,44 +211,44 @@ authenticated.delete(
|
||||
user.removeUserOrg
|
||||
);
|
||||
|
||||
authenticated.put(
|
||||
"/user/:userId/site",
|
||||
verifySiteAccess,
|
||||
verifyUserAccess,
|
||||
role.addRoleSite
|
||||
);
|
||||
authenticated.delete(
|
||||
"/user/:userId/site",
|
||||
verifySiteAccess,
|
||||
verifyUserAccess,
|
||||
role.removeRoleSite
|
||||
);
|
||||
authenticated.put(
|
||||
"/user/:userId/resource",
|
||||
verifyResourceAccess,
|
||||
verifyUserAccess,
|
||||
role.addRoleResource
|
||||
);
|
||||
authenticated.delete(
|
||||
"/user/:userId/resource",
|
||||
verifyResourceAccess,
|
||||
verifyUserAccess,
|
||||
role.removeRoleResource
|
||||
);
|
||||
authenticated.put(
|
||||
"/org/:orgId/user/:userId/action",
|
||||
verifyOrgAccess,
|
||||
verifyUserAccess,
|
||||
verifySuperuser,
|
||||
role.addRoleAction
|
||||
);
|
||||
authenticated.delete(
|
||||
"/org/:orgId/user/:userId/action",
|
||||
verifyOrgAccess,
|
||||
verifyUserAccess,
|
||||
verifySuperuser,
|
||||
role.removeRoleAction
|
||||
);
|
||||
// authenticated.put(
|
||||
// "/user/:userId/site",
|
||||
// verifySiteAccess,
|
||||
// verifyUserAccess,
|
||||
// role.addRoleSite
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/user/:userId/site",
|
||||
// verifySiteAccess,
|
||||
// verifyUserAccess,
|
||||
// role.removeRoleSite
|
||||
// );
|
||||
// authenticated.put(
|
||||
// "/user/:userId/resource",
|
||||
// verifyResourceAccess,
|
||||
// verifyUserAccess,
|
||||
// role.addRoleResource
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/user/:userId/resource",
|
||||
// verifyResourceAccess,
|
||||
// verifyUserAccess,
|
||||
// role.removeRoleResource
|
||||
// );
|
||||
// authenticated.put(
|
||||
// "/org/:orgId/user/:userId/action",
|
||||
// verifyOrgAccess,
|
||||
// verifyUserAccess,
|
||||
// verifySuperUser,
|
||||
// role.addRoleAction
|
||||
// );
|
||||
// authenticated.delete(
|
||||
// "/org/:orgId/user/:userId/action",
|
||||
// verifyOrgAccess,
|
||||
// verifyUserAccess,
|
||||
// verifySuperUser,
|
||||
// role.removeRoleAction
|
||||
// );
|
||||
|
||||
// Auth routes
|
||||
export const authRouter = Router();
|
||||
|
||||
@@ -8,7 +8,7 @@ import HttpCode from '@server/types/HttpCode';
|
||||
import createHttpError from 'http-errors';
|
||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||
import logger from '@server/logger';
|
||||
import { createSuperuserRole } from '@server/db/ensureActions';
|
||||
import { createSuperUserRole } from '@server/db/ensureActions';
|
||||
import config, { APP_PATH } from "@server/config";
|
||||
import { fromError } from 'zod-validation-error';
|
||||
|
||||
@@ -75,13 +75,13 @@ export async function createOrg(req: Request, res: Response, next: NextFunction)
|
||||
domain
|
||||
}).returning();
|
||||
|
||||
const roleId = await createSuperuserRole(newOrg[0].orgId);
|
||||
const roleId = await createSuperUserRole(newOrg[0].orgId);
|
||||
|
||||
if (!roleId) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.INTERNAL_SERVER_ERROR,
|
||||
`Error creating superuser role`
|
||||
`Error creating Super User role`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -87,27 +87,27 @@ export async function createResource(req: Request, res: Response, next: NextFunc
|
||||
subdomain,
|
||||
}).returning();
|
||||
|
||||
// find the superuser roleId and also add the resource to the superuser role
|
||||
const superuserRole = await db.select()
|
||||
// find the Super User roleId and also add the resource to the Super User role
|
||||
const superUserRole = await db.select()
|
||||
.from(roles)
|
||||
.where(and(eq(roles.isSuperuserRole, true), eq(roles.orgId, orgId)))
|
||||
.where(and(eq(roles.isSuperUserRole, true), eq(roles.orgId, orgId)))
|
||||
.limit(1);
|
||||
|
||||
if (superuserRole.length === 0) {
|
||||
if (superUserRole.length === 0) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.NOT_FOUND,
|
||||
`Superuser role not found`
|
||||
`Super User role not found`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
await db.insert(roleResources).values({
|
||||
roleId: superuserRole[0].roleId,
|
||||
roleId: superUserRole[0].roleId,
|
||||
resourceId: newResource[0].resourceId,
|
||||
});
|
||||
|
||||
if (req.userOrgRoleId != superuserRole[0].roleId) {
|
||||
if (req.userOrgRoleId != superUserRole[0].roleId) {
|
||||
// make sure the user can access the resource
|
||||
await db.insert(userResources).values({
|
||||
userId: req.user?.userId!,
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function listResourceRoles(
|
||||
roleId: roles.roleId,
|
||||
name: roles.name,
|
||||
description: roles.description,
|
||||
isSuperuserRole: roles.isSuperuserRole,
|
||||
isSuperUserRole: roles.isSuperUserRole,
|
||||
})
|
||||
.from(roleResources)
|
||||
.innerJoin(roles, eq(roleResources.roleId, roles.roleId))
|
||||
|
||||
@@ -61,11 +61,11 @@ export async function deleteRole(
|
||||
);
|
||||
}
|
||||
|
||||
if (role[0].isSuperuserRole) {
|
||||
if (role[0].isSuperUserRole) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
`Cannot delete a superuser role`
|
||||
`Cannot delete a Super User role`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export async function listRoles(
|
||||
.select({
|
||||
roleId: roles.roleId,
|
||||
orgId: roles.orgId,
|
||||
isSuperuserRole: roles.isSuperuserRole,
|
||||
isSuperUserRole: roles.isSuperUserRole,
|
||||
name: roles.name,
|
||||
description: roles.description,
|
||||
orgName: orgs.name,
|
||||
|
||||
@@ -81,11 +81,11 @@ export async function updateRole(
|
||||
);
|
||||
}
|
||||
|
||||
if (role[0].isSuperuserRole) {
|
||||
if (role[0].isSuperUserRole) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.FORBIDDEN,
|
||||
`Cannot update a superuser role`
|
||||
`Cannot update a Super User role`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,25 +107,25 @@ export async function createSite(
|
||||
subnet,
|
||||
})
|
||||
.returning();
|
||||
// find the superuser roleId and also add the resource to the superuser role
|
||||
const superuserRole = await db
|
||||
// find the Super User roleId and also add the resource to the Super User role
|
||||
const superUserRole = await db
|
||||
.select()
|
||||
.from(roles)
|
||||
.where(and(eq(roles.isSuperuserRole, true), eq(roles.orgId, orgId)))
|
||||
.where(and(eq(roles.isSuperUserRole, true), eq(roles.orgId, orgId)))
|
||||
.limit(1);
|
||||
|
||||
if (superuserRole.length === 0) {
|
||||
if (superUserRole.length === 0) {
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, `Superuser role not found`)
|
||||
createHttpError(HttpCode.NOT_FOUND, `Super User role not found`)
|
||||
);
|
||||
}
|
||||
|
||||
await db.insert(roleSites).values({
|
||||
roleId: superuserRole[0].roleId,
|
||||
roleId: superUserRole[0].roleId,
|
||||
siteId: newSite.siteId,
|
||||
});
|
||||
|
||||
if (req.userOrgRoleId != superuserRole[0].roleId) {
|
||||
if (req.userOrgRoleId != superUserRole[0].roleId) {
|
||||
// make sure the user can access the site
|
||||
db.insert(userSites).values({
|
||||
userId: req.user?.userId!,
|
||||
|
||||
@@ -51,7 +51,7 @@ export async function listSiteRoles(
|
||||
roleId: roles.roleId,
|
||||
name: roles.name,
|
||||
description: roles.description,
|
||||
isSuperuserRole: roles.isSuperuserRole,
|
||||
isSuperUserRole: roles.isSuperUserRole,
|
||||
})
|
||||
.from(roleSites)
|
||||
.innerJoin(roles, eq(roleSites.roleId, roles.roleId))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { z } from "zod";
|
||||
import { db } from "@server/db";
|
||||
import { userInvites, userOrgs, users } from "@server/db/schema";
|
||||
import { orgs, userInvites, userOrgs, users } from "@server/db/schema";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import response from "@server/utils/response";
|
||||
import HttpCode from "@server/types/HttpCode";
|
||||
@@ -13,6 +13,8 @@ import { createDate, TimeSpan } from "oslo";
|
||||
import config from "@server/config";
|
||||
import { hashPassword } from "@server/auth/password";
|
||||
import { fromError } from "zod-validation-error";
|
||||
import { sendEmail } from "@server/emails";
|
||||
import SendInviteLink from "@server/emails/templates/SendInviteLink";
|
||||
|
||||
const inviteUserParamsSchema = z.object({
|
||||
orgId: z.string(),
|
||||
@@ -31,6 +33,8 @@ export type InviteUserResponse = {
|
||||
expiresAt: number;
|
||||
};
|
||||
|
||||
const inviteTracker: Record<string, { timestamps: number[] }> = {};
|
||||
|
||||
export async function inviteUser(
|
||||
req: Request,
|
||||
res: Response,
|
||||
@@ -73,6 +77,39 @@ export async function inviteUser(
|
||||
);
|
||||
}
|
||||
|
||||
const currentTime = Date.now();
|
||||
const oneHourAgo = currentTime - 3600000;
|
||||
|
||||
if (!inviteTracker[email]) {
|
||||
inviteTracker[email] = { timestamps: [] };
|
||||
}
|
||||
|
||||
inviteTracker[email].timestamps = inviteTracker[
|
||||
email
|
||||
].timestamps.filter((timestamp) => timestamp > oneHourAgo);
|
||||
|
||||
if (inviteTracker[email].timestamps.length >= 3) {
|
||||
return next(
|
||||
createHttpError(
|
||||
HttpCode.TOO_MANY_REQUESTS,
|
||||
"User has already been invited 3 times in the last hour"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
inviteTracker[email].timestamps.push(currentTime);
|
||||
|
||||
const org = await db
|
||||
.select()
|
||||
.from(orgs)
|
||||
.where(eq(orgs.orgId, orgId))
|
||||
.limit(1);
|
||||
if (!org.length) {
|
||||
return next(
|
||||
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
|
||||
);
|
||||
}
|
||||
|
||||
const existingUser = await db
|
||||
.select()
|
||||
.from(users)
|
||||
@@ -116,6 +153,21 @@ export async function inviteUser(
|
||||
|
||||
const inviteLink = `${config.app.base_url}/invite?token=${inviteId}-${token}`;
|
||||
|
||||
await sendEmail(
|
||||
SendInviteLink({
|
||||
email,
|
||||
inviteLink,
|
||||
expiresInDays: (validHours / 24).toString(),
|
||||
orgName: orgId,
|
||||
inviterName: req.user?.email,
|
||||
}),
|
||||
{
|
||||
to: email,
|
||||
from: config.email?.no_reply,
|
||||
subject: "You're invited to join a Fossorial organization",
|
||||
}
|
||||
);
|
||||
|
||||
return response<InviteUserResponse>(res, {
|
||||
data: {
|
||||
inviteLink,
|
||||
|
||||
@@ -11,7 +11,7 @@ import logger from "@server/logger";
|
||||
import { fromError } from "zod-validation-error";
|
||||
|
||||
const removeUserSchema = z.object({
|
||||
userId: z.string().uuid(),
|
||||
userId: z.string(),
|
||||
orgId: z.string(),
|
||||
});
|
||||
|
||||
@@ -33,7 +33,6 @@ export async function removeUserOrg(
|
||||
|
||||
const { userId, orgId } = parsedParams.data;
|
||||
|
||||
// Check if the user has permission to list sites
|
||||
const hasPermission = await checkUserActionPermission(
|
||||
ActionsEnum.removeUser,
|
||||
req
|
||||
@@ -56,7 +55,7 @@ export async function removeUserOrg(
|
||||
data: null,
|
||||
success: true,
|
||||
error: false,
|
||||
message: "User deleted successfully",
|
||||
message: "User remove from org successfully",
|
||||
status: HttpCode.OK,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user