@pablo2410/core-server 0.9.3 → 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,86 @@
1
+ import * as _trpc_server from '@trpc/server';
2
+
3
+ /** The user fields isolation needs. Each app's session user must have these. */
4
+ interface IsolationUser {
5
+ role: string;
6
+ enterpriseId?: number | null;
7
+ }
8
+ /** The context fields isolation needs. Each app's tRPC context must have these. */
9
+ interface IsolationContext<TUser extends IsolationUser = IsolationUser> {
10
+ user: TUser | null;
11
+ }
12
+ /** Anything owned by one enterprise (a row from the DB or the Portal API). */
13
+ interface EnterpriseOwned {
14
+ enterpriseId?: number | null;
15
+ }
16
+ /**
17
+ * The fleet's one admin rule: enterprise_admin and above (enterprise_admin,
18
+ * platform_admin). Paul, 5 Oct 2026 (core-server#74). Every admin check in
19
+ * every app uses this; no app sets its own admin threshold, and superuser is
20
+ * not an admin level.
21
+ */
22
+ declare const ADMIN_MIN_ROLE: "enterprise_admin";
23
+ /** True when `role` passes the fleet admin rule (ADMIN_MIN_ROLE and above). */
24
+ declare function isAdminRole(role: string | null | undefined): boolean;
25
+ /**
26
+ * The caller's enterprise, or null. Never a default tenant: a missing,
27
+ * zero, negative or non-integer enterpriseId is null.
28
+ */
29
+ declare function resolveEnterpriseId(user: IsolationUser | null | undefined): number | null;
30
+ /** Like resolveEnterpriseId, but throws UNAUTHORIZED / FORBIDDEN instead of null. */
31
+ declare function requireEnterpriseId(user: IsolationUser | null | undefined): number;
32
+ /**
33
+ * Keep only the rows owned by `enterpriseId`. A null/invalid enterprise gets no
34
+ * rows; a row with no enterpriseId is never included.
35
+ */
36
+ declare function scopeToEnterprise<T extends EnterpriseOwned>(rows: readonly T[], enterpriseId: number | null | undefined): T[];
37
+ interface OwnershipMessages {
38
+ /** Message when the record does not exist. Default "Not found". */
39
+ notFound?: string;
40
+ /** Message when the record belongs to another enterprise. */
41
+ forbidden?: string;
42
+ }
43
+ /**
44
+ * Return `record` if it belongs to `enterpriseId`; otherwise throw.
45
+ * Missing record → NOT_FOUND. Other enterprise, a record with no enterpriseId,
46
+ * or a caller with no enterprise → FORBIDDEN.
47
+ */
48
+ declare function assertEnterpriseOwnership<T extends EnterpriseOwned>(record: T | null | undefined, enterpriseId: number | null | undefined, messages?: OwnershipMessages): T;
49
+ /**
50
+ * Build the standard procedures for an app whose session user is `TUser`.
51
+ * Attach each one to the app's own tRPC instance with `t.procedure.concat(...)`.
52
+ *
53
+ * | Procedure | Auth | Enterprise | Min role |
54
+ * |--------------------------|:----:|:----------:|-----------------------------|
55
+ * | protectedProcedure | Yes | No | any |
56
+ * | enterpriseProcedure | Yes | Yes | any |
57
+ * | adminProcedure | Yes | No | ADMIN_MIN_ROLE (admin gate) |
58
+ * | enterpriseAdminProcedure | Yes | Yes | ADMIN_MIN_ROLE (admin gate) |
59
+ * | platformAdminProcedure | Yes | No | platform_admin |
60
+ *
61
+ * Both admin procedures share one admin gate built from ADMIN_MIN_ROLE.
62
+ * Enterprise procedures add `ctx.enterpriseId: number` and narrow `ctx.user`
63
+ * to non-null. Checks run in order: user, then enterprise, then role.
64
+ */
65
+ declare function createIsolationProcedures<TUser extends IsolationUser = IsolationUser>(): {
66
+ protectedProcedure: _trpc_server.TRPCProcedureBuilder<IsolationContext<TUser>, object, {
67
+ user: TUser;
68
+ }, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, false>;
69
+ enterpriseProcedure: _trpc_server.TRPCProcedureBuilder<IsolationContext<TUser>, object, {
70
+ enterpriseId: number;
71
+ user: TUser;
72
+ }, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, false>;
73
+ adminProcedure: _trpc_server.TRPCProcedureBuilder<IsolationContext<TUser>, object, {
74
+ user: TUser;
75
+ }, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, false>;
76
+ enterpriseAdminProcedure: _trpc_server.TRPCProcedureBuilder<IsolationContext<TUser>, object, {
77
+ enterpriseId: number;
78
+ user: TUser;
79
+ }, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, false>;
80
+ platformAdminProcedure: _trpc_server.TRPCProcedureBuilder<IsolationContext<TUser>, object, {
81
+ user: TUser;
82
+ }, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, _trpc_server.TRPCUnsetMarker, false>;
83
+ };
84
+ type IsolationProcedures<TUser extends IsolationUser = IsolationUser> = ReturnType<typeof createIsolationProcedures<TUser>>;
85
+
86
+ export { ADMIN_MIN_ROLE, type EnterpriseOwned, type IsolationContext, type IsolationProcedures, type IsolationUser, type OwnershipMessages, assertEnterpriseOwnership, createIsolationProcedures, isAdminRole, requireEnterpriseId, resolveEnterpriseId, scopeToEnterprise };
@@ -0,0 +1,106 @@
1
+ // src/isolation.ts
2
+ import { initTRPC, TRPCError } from "@trpc/server";
3
+ import {
4
+ NO_ENTERPRISE_ERR_MSG,
5
+ NOT_ADMIN_ERR_MSG,
6
+ NOT_PLATFORM_ADMIN_ERR_MSG,
7
+ ROLE_HIERARCHY,
8
+ UNAUTHED_ERR_MSG
9
+ } from "@pablo2410/shared-ui/rbac";
10
+ function isEnterpriseId(value) {
11
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
12
+ }
13
+ function meetsMinimumRole(userRole, minimumRole) {
14
+ const user = ROLE_HIERARCHY.indexOf(userRole);
15
+ const min = ROLE_HIERARCHY.indexOf(minimumRole);
16
+ return user >= 0 && min >= 0 && user >= min;
17
+ }
18
+ var ADMIN_MIN_ROLE = "enterprise_admin";
19
+ function isAdminRole(role) {
20
+ return meetsMinimumRole(role, ADMIN_MIN_ROLE);
21
+ }
22
+ function resolveEnterpriseId(user) {
23
+ const id = user?.enterpriseId;
24
+ return isEnterpriseId(id) ? id : null;
25
+ }
26
+ function requireEnterpriseId(user) {
27
+ if (!user) {
28
+ throw new TRPCError({ code: "UNAUTHORIZED", message: UNAUTHED_ERR_MSG });
29
+ }
30
+ const id = resolveEnterpriseId(user);
31
+ if (id === null) {
32
+ throw new TRPCError({ code: "FORBIDDEN", message: NO_ENTERPRISE_ERR_MSG });
33
+ }
34
+ return id;
35
+ }
36
+ function scopeToEnterprise(rows, enterpriseId) {
37
+ if (!isEnterpriseId(enterpriseId)) return [];
38
+ return rows.filter((row) => row.enterpriseId === enterpriseId);
39
+ }
40
+ function assertEnterpriseOwnership(record, enterpriseId, messages = {}) {
41
+ if (record === null || record === void 0) {
42
+ throw new TRPCError({
43
+ code: "NOT_FOUND",
44
+ message: messages.notFound ?? "Not found"
45
+ });
46
+ }
47
+ if (!isEnterpriseId(enterpriseId) || record.enterpriseId !== enterpriseId) {
48
+ throw new TRPCError({
49
+ code: "FORBIDDEN",
50
+ message: messages.forbidden ?? "Access denied: this record belongs to another enterprise"
51
+ });
52
+ }
53
+ return record;
54
+ }
55
+ function createIsolationProcedures() {
56
+ const t = initTRPC.context().create();
57
+ const requireUser = t.middleware(({ ctx, next }) => {
58
+ if (!ctx.user) {
59
+ throw new TRPCError({ code: "UNAUTHORIZED", message: UNAUTHED_ERR_MSG });
60
+ }
61
+ return next({ ctx: { user: ctx.user } });
62
+ });
63
+ const requireEnterprise = t.middleware(({ ctx, next }) => {
64
+ const enterpriseId = requireEnterpriseId(ctx.user);
65
+ return next({ ctx: { user: ctx.user, enterpriseId } });
66
+ });
67
+ function requireRole(minimumRole, message) {
68
+ if (!ROLE_HIERARCHY.includes(minimumRole)) {
69
+ throw new Error(`Unknown role for isolation check: ${minimumRole}`);
70
+ }
71
+ return t.middleware(({ ctx, next }) => {
72
+ if (!ctx.user) {
73
+ throw new TRPCError({
74
+ code: "UNAUTHORIZED",
75
+ message: UNAUTHED_ERR_MSG
76
+ });
77
+ }
78
+ if (!meetsMinimumRole(ctx.user.role, minimumRole)) {
79
+ throw new TRPCError({ code: "FORBIDDEN", message });
80
+ }
81
+ return next();
82
+ });
83
+ }
84
+ const requireAdmin = requireRole(ADMIN_MIN_ROLE, NOT_ADMIN_ERR_MSG);
85
+ const protectedProcedure = t.procedure.use(requireUser);
86
+ const enterpriseProcedure = t.procedure.use(requireEnterprise);
87
+ return {
88
+ protectedProcedure,
89
+ enterpriseProcedure,
90
+ adminProcedure: protectedProcedure.use(requireAdmin),
91
+ enterpriseAdminProcedure: enterpriseProcedure.use(requireAdmin),
92
+ platformAdminProcedure: protectedProcedure.use(
93
+ requireRole("platform_admin", NOT_PLATFORM_ADMIN_ERR_MSG)
94
+ )
95
+ };
96
+ }
97
+ export {
98
+ ADMIN_MIN_ROLE,
99
+ assertEnterpriseOwnership,
100
+ createIsolationProcedures,
101
+ isAdminRole,
102
+ requireEnterpriseId,
103
+ resolveEnterpriseId,
104
+ scopeToEnterprise
105
+ };
106
+ //# sourceMappingURL=isolation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/isolation.ts"],"sourcesContent":["/**\n * Enterprise Isolation — the one enforced tenant-scoping path for every service.\n *\n * Replaces the per-app copies of `requireEnterprise` / `enterpriseProcedure` and\n * the hand-written `rows.filter(r => r.enterpriseId === id)` and \"does this\n * record belong to the caller?\" checks. Roles and error messages come from\n * `@pablo2410/shared-ui/rbac`, the platform's single role model.\n *\n * Every check fails closed: no user → UNAUTHORIZED; no (valid) enterprise →\n * FORBIDDEN / no rows; an unknown role never passes a role check; a record with\n * no enterpriseId never matches, even against a caller with none.\n *\n * Quick example (each app keeps its own `t`; the procedures are concatenated\n * onto it, so the app's transformer, context and error formatter still apply):\n *\n * import { createIsolationProcedures } from \"@pablo2410/core-server/isolation\";\n * const iso = createIsolationProcedures<SessionUser>();\n * export const enterpriseProcedure = t.procedure.concat(iso.enterpriseProcedure);\n * // in a resolver: ctx.enterpriseId is a number, ctx.user is non-null\n * listPlans: enterpriseProcedure.query(async ({ ctx }) =>\n * scopeToEnterprise(await getPlans(), ctx.enterpriseId)),\n */\nimport { initTRPC, TRPCError } from \"@trpc/server\";\nimport {\n NO_ENTERPRISE_ERR_MSG,\n NOT_ADMIN_ERR_MSG,\n NOT_PLATFORM_ADMIN_ERR_MSG,\n ROLE_HIERARCHY,\n UNAUTHED_ERR_MSG,\n type RoleType,\n} from \"@pablo2410/shared-ui/rbac\";\n\n/** The user fields isolation needs. Each app's session user must have these. */\nexport interface IsolationUser {\n role: string;\n enterpriseId?: number | null;\n}\n\n/** The context fields isolation needs. Each app's tRPC context must have these. */\nexport interface IsolationContext<TUser extends IsolationUser = IsolationUser> {\n user: TUser | null;\n}\n\n/** Anything owned by one enterprise (a row from the DB or the Portal API). */\nexport interface EnterpriseOwned {\n enterpriseId?: number | null;\n}\n\n/** Only positive integers are real enterprise IDs. */\nfunction isEnterpriseId(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value > 0;\n}\n\n/**\n * True when `userRole` is a known role at or above `minimumRole`.\n * Unlike a bare indexOf comparison, an unknown role on either side is never\n * treated as meeting the bar.\n */\nfunction meetsMinimumRole(\n userRole: string | null | undefined,\n minimumRole: RoleType\n): boolean {\n const user = ROLE_HIERARCHY.indexOf(userRole as RoleType);\n const min = ROLE_HIERARCHY.indexOf(minimumRole);\n return user >= 0 && min >= 0 && user >= min;\n}\n\n/**\n * The fleet's one admin rule: enterprise_admin and above (enterprise_admin,\n * platform_admin). Paul, 5 Oct 2026 (core-server#74). Every admin check in\n * every app uses this; no app sets its own admin threshold, and superuser is\n * not an admin level.\n */\nexport const ADMIN_MIN_ROLE = \"enterprise_admin\" as const satisfies RoleType;\n\n/** True when `role` passes the fleet admin rule (ADMIN_MIN_ROLE and above). */\nexport function isAdminRole(role: string | null | undefined): boolean {\n return meetsMinimumRole(role, ADMIN_MIN_ROLE);\n}\n\n/**\n * The caller's enterprise, or null. Never a default tenant: a missing,\n * zero, negative or non-integer enterpriseId is null.\n */\nexport function resolveEnterpriseId(\n user: IsolationUser | null | undefined\n): number | null {\n const id = user?.enterpriseId;\n return isEnterpriseId(id) ? id : null;\n}\n\n/** Like resolveEnterpriseId, but throws UNAUTHORIZED / FORBIDDEN instead of null. */\nexport function requireEnterpriseId(\n user: IsolationUser | null | undefined\n): number {\n if (!user) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: UNAUTHED_ERR_MSG });\n }\n const id = resolveEnterpriseId(user);\n if (id === null) {\n throw new TRPCError({ code: \"FORBIDDEN\", message: NO_ENTERPRISE_ERR_MSG });\n }\n return id;\n}\n\n/**\n * Keep only the rows owned by `enterpriseId`. A null/invalid enterprise gets no\n * rows; a row with no enterpriseId is never included.\n */\nexport function scopeToEnterprise<T extends EnterpriseOwned>(\n rows: readonly T[],\n enterpriseId: number | null | undefined\n): T[] {\n if (!isEnterpriseId(enterpriseId)) return [];\n return rows.filter(row => row.enterpriseId === enterpriseId);\n}\n\nexport interface OwnershipMessages {\n /** Message when the record does not exist. Default \"Not found\". */\n notFound?: string;\n /** Message when the record belongs to another enterprise. */\n forbidden?: string;\n}\n\n/**\n * Return `record` if it belongs to `enterpriseId`; otherwise throw.\n * Missing record → NOT_FOUND. Other enterprise, a record with no enterpriseId,\n * or a caller with no enterprise → FORBIDDEN.\n */\nexport function assertEnterpriseOwnership<T extends EnterpriseOwned>(\n record: T | null | undefined,\n enterpriseId: number | null | undefined,\n messages: OwnershipMessages = {}\n): T {\n if (record === null || record === undefined) {\n throw new TRPCError({\n code: \"NOT_FOUND\",\n message: messages.notFound ?? \"Not found\",\n });\n }\n if (!isEnterpriseId(enterpriseId) || record.enterpriseId !== enterpriseId) {\n throw new TRPCError({\n code: \"FORBIDDEN\",\n message:\n messages.forbidden ??\n \"Access denied: this record belongs to another enterprise\",\n });\n }\n return record;\n}\n\n/**\n * Build the standard procedures for an app whose session user is `TUser`.\n * Attach each one to the app's own tRPC instance with `t.procedure.concat(...)`.\n *\n * | Procedure | Auth | Enterprise | Min role |\n * |--------------------------|:----:|:----------:|-----------------------------|\n * | protectedProcedure | Yes | No | any |\n * | enterpriseProcedure | Yes | Yes | any |\n * | adminProcedure | Yes | No | ADMIN_MIN_ROLE (admin gate) |\n * | enterpriseAdminProcedure | Yes | Yes | ADMIN_MIN_ROLE (admin gate) |\n * | platformAdminProcedure | Yes | No | platform_admin |\n *\n * Both admin procedures share one admin gate built from ADMIN_MIN_ROLE.\n * Enterprise procedures add `ctx.enterpriseId: number` and narrow `ctx.user`\n * to non-null. Checks run in order: user, then enterprise, then role.\n */\nexport function createIsolationProcedures<\n TUser extends IsolationUser = IsolationUser,\n>() {\n const t = initTRPC.context<IsolationContext<TUser>>().create();\n\n const requireUser = t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({ code: \"UNAUTHORIZED\", message: UNAUTHED_ERR_MSG });\n }\n return next({ ctx: { user: ctx.user } });\n });\n\n const requireEnterprise = t.middleware(({ ctx, next }) => {\n const enterpriseId = requireEnterpriseId(ctx.user);\n return next({ ctx: { user: ctx.user as TUser, enterpriseId } });\n });\n\n function requireRole(minimumRole: RoleType, message: string) {\n if (!ROLE_HIERARCHY.includes(minimumRole)) {\n throw new Error(`Unknown role for isolation check: ${minimumRole}`);\n }\n return t.middleware(({ ctx, next }) => {\n if (!ctx.user) {\n throw new TRPCError({\n code: \"UNAUTHORIZED\",\n message: UNAUTHED_ERR_MSG,\n });\n }\n if (!meetsMinimumRole(ctx.user.role, minimumRole)) {\n throw new TRPCError({ code: \"FORBIDDEN\", message });\n }\n return next();\n });\n }\n\n /** The one admin gate (ADMIN_MIN_ROLE). */\n const requireAdmin = requireRole(ADMIN_MIN_ROLE, NOT_ADMIN_ERR_MSG);\n\n const protectedProcedure = t.procedure.use(requireUser);\n const enterpriseProcedure = t.procedure.use(requireEnterprise);\n\n return {\n protectedProcedure,\n enterpriseProcedure,\n adminProcedure: protectedProcedure.use(requireAdmin),\n enterpriseAdminProcedure: enterpriseProcedure.use(requireAdmin),\n platformAdminProcedure: protectedProcedure.use(\n requireRole(\"platform_admin\", NOT_PLATFORM_ADMIN_ERR_MSG)\n ),\n };\n}\n\nexport type IsolationProcedures<TUser extends IsolationUser = IsolationUser> =\n ReturnType<typeof createIsolationProcedures<TUser>>;\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pablo2410/core-server",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Shared server utilities for Oplytics.digital subdomains",
5
5
  "type": "module",
6
6
  "exports": {
@@ -15,6 +15,10 @@
15
15
  "./hierarchy": {
16
16
  "types": "./dist/hierarchy.d.ts",
17
17
  "import": "./dist/hierarchy.js"
18
+ },
19
+ "./isolation": {
20
+ "types": "./dist/isolation.d.ts",
21
+ "import": "./dist/isolation.js"
18
22
  }
19
23
  },
20
24
  "files": [