first pass

This commit is contained in:
miloschwartz
2026-02-24 17:58:11 -08:00
parent 848d4d91e6
commit 20e547a0f6
60 changed files with 1023 additions and 399 deletions

View File

@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, idp, idpOidcConfig } from "@server/db";
import { roles, userOrgs, users } from "@server/db";
import { roles, userOrgRoles, userOrgs, users } from "@server/db";
import { and, eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
@@ -14,7 +14,7 @@ import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { CheckOrgAccessPolicyResult } from "@server/lib/checkOrgAccessPolicy";
async function queryUser(orgId: string, userId: string) {
const [user] = await db
const [userRow] = await db
.select({
orgId: userOrgs.orgId,
userId: users.userId,
@@ -22,10 +22,7 @@ async function queryUser(orgId: string, userId: string) {
username: users.username,
name: users.name,
type: users.type,
roleId: userOrgs.roleId,
roleName: roles.name,
isOwner: userOrgs.isOwner,
isAdmin: roles.isAdmin,
twoFactorEnabled: users.twoFactorEnabled,
autoProvisioned: userOrgs.autoProvisioned,
idpId: users.idpId,
@@ -35,13 +32,40 @@ async function queryUser(orgId: string, userId: string) {
idpAutoProvision: idp.autoProvision
})
.from(userOrgs)
.leftJoin(roles, eq(userOrgs.roleId, roles.roleId))
.leftJoin(users, eq(userOrgs.userId, users.userId))
.leftJoin(idp, eq(users.idpId, idp.idpId))
.leftJoin(idpOidcConfig, eq(idp.idpId, idpOidcConfig.idpId))
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
.limit(1);
return user;
if (!userRow) return undefined;
const roleRows = await db
.select({
roleId: userOrgRoles.roleId,
roleName: roles.name,
isAdmin: roles.isAdmin
})
.from(userOrgRoles)
.leftJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
.where(
and(
eq(userOrgRoles.userId, userId),
eq(userOrgRoles.orgId, orgId)
)
);
const isAdmin = roleRows.some((r) => r.isAdmin);
return {
...userRow,
isAdmin,
roleIds: roleRows.map((r) => r.roleId),
roles: roleRows.map((r) => ({
roleId: r.roleId,
name: r.roleName ?? ""
}))
};
}
export type CheckOrgUserAccessResponse = CheckOrgAccessPolicyResult;

View File

@@ -9,6 +9,7 @@ import {
orgs,
roleActions,
roles,
userOrgRoles,
userOrgs,
users,
actions
@@ -312,9 +313,13 @@ export async function createOrg(
await trx.insert(userOrgs).values({
userId: req.user!.userId,
orgId: newOrg[0].orgId,
roleId: roleId,
isOwner: true
});
await trx.insert(userOrgRoles).values({
userId: req.user!.userId,
orgId: newOrg[0].orgId,
roleId
});
ownerUserId = req.user!.userId;
} else {
// if org created by root api key, set the server admin as the owner
@@ -332,9 +337,13 @@ export async function createOrg(
await trx.insert(userOrgs).values({
userId: serverAdmin.userId,
orgId: newOrg[0].orgId,
roleId: roleId,
isOwner: true
});
await trx.insert(userOrgRoles).values({
userId: serverAdmin.userId,
orgId: newOrg[0].orgId,
roleId
});
ownerUserId = serverAdmin.userId;
}

View File

@@ -117,20 +117,26 @@ export async function getOrgOverview(
.from(userOrgs)
.where(eq(userOrgs.orgId, orgId));
const [role] = await db
.select()
.from(roles)
.where(eq(roles.roleId, req.userOrg.roleId));
const roleIds = req.userOrgRoleIds ?? [];
const roleRows =
roleIds.length > 0
? await db
.select({ name: roles.name, isAdmin: roles.isAdmin })
.from(roles)
.where(inArray(roles.roleId, roleIds))
: [];
const userRoleName = roleRows.map((r) => r.name ?? "").join(", ") ?? "";
const isAdmin = roleRows.some((r) => r.isAdmin === true);
return response<GetOrgOverviewResponse>(res, {
data: {
orgName: org[0].name,
orgId: org[0].orgId,
userRoleName: role.name,
userRoleName,
numSites,
numUsers,
numResources,
isAdmin: role.isAdmin || false,
isAdmin,
isOwner: req.userOrg?.isOwner || false
},
success: true,

View File

@@ -1,7 +1,7 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db, roles } from "@server/db";
import { Org, orgs, userOrgs } from "@server/db";
import { Org, orgs, userOrgRoles, userOrgs } from "@server/db";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
@@ -82,10 +82,7 @@ export async function listUserOrgs(
const { userId } = parsedParams.data;
const userOrganizations = await db
.select({
orgId: userOrgs.orgId,
roleId: userOrgs.roleId
})
.select({ orgId: userOrgs.orgId })
.from(userOrgs)
.where(eq(userOrgs.userId, userId));
@@ -116,10 +113,27 @@ export async function listUserOrgs(
userOrgs,
and(eq(userOrgs.orgId, orgs.orgId), eq(userOrgs.userId, userId))
)
.leftJoin(roles, eq(userOrgs.roleId, roles.roleId))
.limit(limit)
.offset(offset);
const roleRows = await db
.select({
orgId: userOrgRoles.orgId,
isAdmin: roles.isAdmin
})
.from(userOrgRoles)
.leftJoin(roles, eq(userOrgRoles.roleId, roles.roleId))
.where(
and(
eq(userOrgRoles.userId, userId),
inArray(userOrgRoles.orgId, userOrgIds)
)
);
const orgHasAdmin = new Set(
roleRows.filter((r) => r.isAdmin).map((r) => r.orgId)
);
const totalCountResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(orgs)
@@ -133,8 +147,8 @@ export async function listUserOrgs(
if (val.userOrgs && val.userOrgs.isOwner) {
res.isOwner = val.userOrgs.isOwner;
}
if (val.roles && val.roles.isAdmin) {
res.isAdmin = val.roles.isAdmin;
if (val.orgs && orgHasAdmin.has(val.orgs.orgId)) {
res.isAdmin = true;
}
if (val.userOrgs?.isOwner && val.orgs?.isBillingOrg) {
res.isPrimaryOrg = val.orgs.isBillingOrg;