create, delete, and update idp org policies

This commit is contained in:
miloschwartz 2025-04-18 15:38:50 -04:00
parent 3bab90891f
commit 99188233db
No known key found for this signature in database
22 changed files with 1036 additions and 108 deletions

View file

@ -14,4 +14,5 @@ export * from "./verifyAdmin";
export * from "./verifySetResourceUsers";
export * from "./verifyUserInRole";
export * from "./verifyAccessTokenAccess";
export * from "./verifyUserIsServerAdmin";
export * from "./verifyUserIsServerAdmin";
export * from "./verifyIsLoggedInUser";

View file

@ -0,0 +1,44 @@
import { Request, Response, NextFunction } from "express";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyIsLoggedInUser(
req: Request,
res: Response,
next: NextFunction
) {
try {
const userId = req.user!.userId;
const reqUserId =
req.params.userId || req.body.userId || req.query.userId;
if (!userId) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
}
// allow server admins to access any user
if (req.user?.serverAdmin) {
return next();
}
if (reqUserId !== userId) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User only has access to their own account"
)
);
}
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error checking if user has access to this user"
)
);
}
}

View file

@ -25,7 +25,8 @@ import {
verifySetResourceUsers,
verifyUserAccess,
getUserOrgs,
verifyUserIsServerAdmin
verifyUserIsServerAdmin,
verifyIsLoggedInUser
} from "@server/middlewares";
import { verifyUserHasAction } from "../middlewares/verifyUserHasAction";
import { ActionsEnum } from "@server/auth/actions";
@ -47,7 +48,10 @@ authenticated.use(verifySessionUserMiddleware);
authenticated.get("/org/checkId", org.checkId);
authenticated.put("/org", getUserOrgs, org.createOrg);
authenticated.get("/orgs", getUserOrgs, org.listOrgs); // TODO we need to check the orgs here
authenticated.get("/orgs", verifyUserIsServerAdmin, org.listOrgs);
authenticated.get("/user/:userId/orgs", verifyIsLoggedInUser, org.listUserOrgs);
authenticated.get(
"/org/:orgId",
verifyOrgAccess,
@ -507,23 +511,11 @@ authenticated.post(
idp.updateOidcIdp
);
authenticated.delete(
"/idp/:idpId",
verifyUserIsServerAdmin,
idp.deleteIdp
);
authenticated.delete("/idp/:idpId", verifyUserIsServerAdmin, idp.deleteIdp);
authenticated.get(
"/idp",
verifyUserIsServerAdmin,
idp.listIdps
);
authenticated.get("/idp", verifyUserIsServerAdmin, idp.listIdps);
authenticated.get(
"/idp/:idpId",
verifyUserIsServerAdmin,
idp.getIdp
);
authenticated.get("/idp/:idpId", verifyUserIsServerAdmin, idp.getIdp);
authenticated.put(
"/idp/:idpId/org/:orgId",
@ -531,6 +523,12 @@ authenticated.put(
idp.createIdpOrgPolicy
);
authenticated.post(
"/idp/:idpId/org/:orgId",
verifyUserIsServerAdmin,
idp.updateIdpOrgPolicy
);
authenticated.delete(
"/idp/:idpId/org/:orgId",
verifyUserIsServerAdmin,
@ -631,17 +629,8 @@ authRouter.post(
resource.authWithAccessToken
);
authRouter.post(
"/access-token",
resource.authWithAccessToken
);
authRouter.post("/access-token", resource.authWithAccessToken);
authRouter.post(
"/idp/:idpId/oidc/generate-url",
idp.generateOidcUrl
);
authRouter.post("/idp/:idpId/oidc/generate-url", idp.generateOidcUrl);
authRouter.post(
"/idp/:idpId/oidc/validate-callback",
idp.validateOidcCallback
);
authRouter.post("/idp/:idpId/oidc/validate-callback", idp.validateOidcCallback);

View file

@ -77,10 +77,13 @@ export async function createIdpOrgPolicy(
const [existing] = await db
.select()
.from(idp)
.leftJoin(idpOrg, eq(idpOrg.orgId, orgId))
.where(and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId)));
.leftJoin(
idpOrg,
and(eq(idpOrg.orgId, orgId), eq(idpOrg.idpId, idpId))
)
.where(eq(idp.idpId, idpId));
if (!existing.idp) {
if (!existing?.idp) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,

View file

@ -8,3 +8,4 @@ export * from "./getIdp";
export * from "./createIdpOrgPolicy";
export * from "./deleteIdpOrgPolicy";
export * from "./listIdpOrgPolicies";
export * from "./updateIdpOrgPolicy";

View file

@ -0,0 +1,126 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { eq, and } from "drizzle-orm";
import { idp, idpOrg } from "@server/db/schemas";
const paramsSchema = z
.object({
idpId: z.coerce.number(),
orgId: z.string()
})
.strict();
const bodySchema = z
.object({
roleMapping: z.string().optional(),
orgMapping: z.string().optional()
})
.strict();
export type UpdateIdpOrgPolicyResponse = {};
registry.registerPath({
method: "post",
path: "/idp/{idpId}/org/{orgId}",
description: "Update an IDP org policy.",
tags: [OpenAPITags.Idp],
request: {
params: paramsSchema,
body: {
content: {
"application/json": {
schema: bodySchema
}
}
}
},
responses: {}
});
export async function updateIdpOrgPolicy(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const parsedBody = bodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { idpId, orgId } = parsedParams.data;
const { roleMapping, orgMapping } = parsedBody.data;
// Check if IDP and policy exist
const [existing] = await db
.select()
.from(idp)
.leftJoin(
idpOrg,
and(eq(idpOrg.orgId, orgId), eq(idpOrg.idpId, idpId))
)
.where(eq(idp.idpId, idpId));
if (!existing?.idp) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"An IDP with this ID does not exist."
)
);
}
if (!existing.idpOrg) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"A policy for this IDP and org does not exist."
)
);
}
// Update the policy
await db
.update(idpOrg)
.set({
roleMapping,
orgMapping
})
.where(and(eq(idpOrg.idpId, idpId), eq(idpOrg.orgId, orgId)));
return response<UpdateIdpOrgPolicyResponse>(res, {
data: {},
success: true,
error: false,
message: "Policy updated successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View file

@ -2,6 +2,7 @@ export * from "./getOrg";
export * from "./createOrg";
export * from "./deleteOrg";
export * from "./updateOrg";
export * from "./listOrgs";
export * from "./listUserOrgs";
export * from "./checkId";
export * from "./getOrgOverview";
export * from "./listOrgs";

View file

@ -1,11 +1,11 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { Org, orgs } from "@server/db/schemas";
import { Org, orgs, userOrgs } from "@server/db/schemas";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { sql, inArray } from "drizzle-orm";
import { sql, inArray, eq } from "drizzle-orm";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
@ -27,8 +27,8 @@ const listOrgsSchema = z.object({
registry.registerPath({
method: "get",
path: "/orgs",
description: "List all organizations in the system",
path: "/user/:userId/orgs",
description: "List all organizations in the system.",
tags: [OpenAPITags.Org],
request: {
query: listOrgsSchema
@ -59,37 +59,15 @@ export async function listOrgs(
const { limit, offset } = parsedQuery.data;
// Use the userOrgs passed from the middleware
const userOrgIds = req.userOrgIds;
if (!userOrgIds || userOrgIds.length === 0) {
return response<ListOrgsResponse>(res, {
data: {
orgs: [],
pagination: {
total: 0,
limit,
offset
}
},
success: true,
error: false,
message: "No organizations found for the user",
status: HttpCode.OK
});
}
const organizations = await db
.select()
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds))
.limit(limit)
.offset(offset);
const totalCountResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds));
.from(orgs);
const totalCount = totalCountResult[0].count;
return response<ListOrgsResponse>(res, {

View file

@ -0,0 +1,141 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { Org, orgs, userOrgs } from "@server/db/schemas";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { sql, inArray, eq } from "drizzle-orm";
import logger from "@server/logger";
import { fromZodError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const listOrgsParamsSchema = z.object({
userId: z.string()
});
const listOrgsSchema = z.object({
limit: z
.string()
.optional()
.default("1000")
.transform(Number)
.pipe(z.number().int().positive()),
offset: z
.string()
.optional()
.default("0")
.transform(Number)
.pipe(z.number().int().nonnegative())
});
registry.registerPath({
method: "get",
path: "/user/:userId/orgs",
description: "List all organizations for a user.",
tags: [OpenAPITags.Org, OpenAPITags.User],
request: {
query: listOrgsSchema
},
responses: {}
});
export type ListUserOrgsResponse = {
orgs: Org[];
pagination: { total: number; limit: number; offset: number };
};
export async function listUserOrgs(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedQuery = listOrgsSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromZodError(parsedQuery.error)
)
);
}
const { limit, offset } = parsedQuery.data;
const parsedParams = listOrgsParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromZodError(parsedParams.error)
)
);
}
const { userId } = parsedParams.data;
const userOrganizations = await db
.select({
orgId: userOrgs.orgId,
roleId: userOrgs.roleId
})
.from(userOrgs)
.where(eq(userOrgs.userId, userId));
const userOrgIds = userOrganizations.map((org) => org.orgId);
if (!userOrgIds || userOrgIds.length === 0) {
return response<ListUserOrgsResponse>(res, {
data: {
orgs: [],
pagination: {
total: 0,
limit,
offset
}
},
success: true,
error: false,
message: "No organizations found for the user",
status: HttpCode.OK
});
}
const organizations = await db
.select()
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds))
.limit(limit)
.offset(offset);
const totalCountResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds));
const totalCount = totalCountResult[0].count;
return response<ListUserOrgsResponse>(res, {
data: {
orgs: organizations,
pagination: {
total: totalCount,
limit,
offset
}
},
success: true,
error: false,
message: "Organizations retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"An error occurred..."
)
);
}
}