@m5kdev/backend 0.6.0 → 0.7.0

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.
Files changed (45) hide show
  1. package/dist/src/modules/ai/ai.service.d.ts +11 -13
  2. package/dist/src/modules/ai/ai.service.js +6 -6
  3. package/dist/src/modules/ai/ai.trpc.d.ts +1 -1
  4. package/dist/src/modules/auth/auth.lib.d.ts +4 -8
  5. package/dist/src/modules/auth/auth.lib.js +2 -2
  6. package/dist/src/modules/auth/auth.service.d.ts +17 -47
  7. package/dist/src/modules/auth/auth.service.js +79 -66
  8. package/dist/src/modules/auth/auth.trpc.d.ts +1 -1
  9. package/dist/src/modules/base/base.actor.d.ts +68 -0
  10. package/dist/src/modules/base/base.actor.js +99 -0
  11. package/dist/src/modules/base/base.actor.test.d.ts +1 -0
  12. package/dist/src/modules/base/base.actor.test.js +58 -0
  13. package/dist/src/modules/base/base.grants.d.ts +3 -7
  14. package/dist/src/modules/base/base.grants.js +22 -10
  15. package/dist/src/modules/base/base.grants.test.js +16 -45
  16. package/dist/src/modules/base/base.procedure.d.ts +17 -20
  17. package/dist/src/modules/base/base.procedure.js +36 -24
  18. package/dist/src/modules/base/base.service.d.ts +7 -19
  19. package/dist/src/modules/base/base.service.js +19 -12
  20. package/dist/src/modules/base/base.service.test.js +89 -61
  21. package/dist/src/modules/billing/billing.service.d.ts +4 -25
  22. package/dist/src/modules/billing/billing.service.js +6 -6
  23. package/dist/src/modules/billing/billing.trpc.d.ts +2 -2
  24. package/dist/src/modules/billing/billing.trpc.js +4 -6
  25. package/dist/src/modules/connect/connect.service.d.ts +19 -11
  26. package/dist/src/modules/connect/connect.service.js +10 -8
  27. package/dist/src/modules/connect/connect.trpc.d.ts +2 -2
  28. package/dist/src/modules/recurrence/recurrence.service.d.ts +36 -6
  29. package/dist/src/modules/recurrence/recurrence.service.js +13 -10
  30. package/dist/src/modules/recurrence/recurrence.trpc.d.ts +1 -1
  31. package/dist/src/modules/social/social.service.d.ts +3 -4
  32. package/dist/src/modules/social/social.service.js +3 -3
  33. package/dist/src/modules/tag/tag.service.d.ts +16 -12
  34. package/dist/src/modules/tag/tag.service.js +4 -4
  35. package/dist/src/modules/tag/tag.trpc.d.ts +1 -1
  36. package/dist/src/modules/workflow/workflow.service.d.ts +48 -8
  37. package/dist/src/modules/workflow/workflow.service.js +6 -6
  38. package/dist/src/modules/workflow/workflow.trpc.d.ts +2 -2
  39. package/dist/src/types.d.ts +4 -4
  40. package/dist/src/utils/trpc.d.ts +31 -41
  41. package/dist/src/utils/trpc.js +95 -0
  42. package/dist/src/utils/trpc.test.d.ts +1 -0
  43. package/dist/src/utils/trpc.test.js +154 -0
  44. package/dist/tsconfig.tsbuildinfo +1 -1
  45. package/package.json +3 -3
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createActorFromContext = createActorFromContext;
4
+ exports.validateActor = validateActor;
5
+ exports.createServiceActor = createServiceActor;
6
+ exports.getServiceActorScope = getServiceActorScope;
7
+ exports.hasServiceActorScope = hasServiceActorScope;
8
+ const errors_1 = require("../../utils/errors");
9
+ function createActorFromContext(context, scope) {
10
+ if (!context.user.role) {
11
+ throw new errors_1.ServerError({
12
+ code: "BAD_REQUEST",
13
+ message: "User role not found in context",
14
+ layer: "controller",
15
+ layerName: "ActorValidation",
16
+ });
17
+ }
18
+ if (scope === "organization") {
19
+ if (!context.session.activeOrganizationId || !context.session.activeOrganizationRole) {
20
+ throw new errors_1.ServerError({
21
+ code: "FORBIDDEN",
22
+ message: "Active organization context required",
23
+ layer: "controller",
24
+ layerName: "ActorValidation",
25
+ });
26
+ }
27
+ }
28
+ if (scope === "team") {
29
+ if (!context.session.activeOrganizationId || !context.session.activeOrganizationRole) {
30
+ throw new errors_1.ServerError({
31
+ code: "FORBIDDEN",
32
+ message: "Active organization context required for team scope",
33
+ layer: "controller",
34
+ layerName: "ActorValidation",
35
+ });
36
+ }
37
+ if (!context.session.activeTeamId || !context.session.activeTeamRole) {
38
+ throw new errors_1.ServerError({
39
+ code: "FORBIDDEN",
40
+ message: "Active team context required",
41
+ layer: "controller",
42
+ layerName: "ActorValidation",
43
+ });
44
+ }
45
+ }
46
+ return {
47
+ userId: context.user.id,
48
+ userRole: context.user.role,
49
+ organizationId: context.session.activeOrganizationId,
50
+ organizationRole: context.session.activeOrganizationRole,
51
+ teamId: context.session.activeTeamId,
52
+ teamRole: context.session.activeTeamRole,
53
+ };
54
+ }
55
+ function validateActor(actor, scope) {
56
+ if (!actor.userId || !actor.userRole)
57
+ return false;
58
+ if (scope === "user")
59
+ return true;
60
+ if (scope === "organization") {
61
+ return Boolean(actor.organizationId && actor.organizationRole);
62
+ }
63
+ return Boolean(actor.organizationId && actor.organizationRole && actor.teamId && actor.teamRole);
64
+ }
65
+ /**
66
+ * Builds a flat actor for tests / grants without session. Validates that team scope implies organization.
67
+ */
68
+ function createServiceActor(claims) {
69
+ const organizationId = claims.organizationId ?? null;
70
+ const organizationRole = claims.organizationRole ?? null;
71
+ const teamId = claims.teamId ?? null;
72
+ const teamRole = claims.teamRole ?? null;
73
+ if ((teamId || teamRole) && (!organizationId || !organizationRole)) {
74
+ throw new Error("organization access before team access");
75
+ }
76
+ return {
77
+ userId: claims.userId,
78
+ userRole: claims.userRole,
79
+ organizationId,
80
+ organizationRole,
81
+ teamId,
82
+ teamRole,
83
+ };
84
+ }
85
+ function getServiceActorScope(actor) {
86
+ if (validateActor(actor, "team"))
87
+ return "team";
88
+ if (validateActor(actor, "organization"))
89
+ return "organization";
90
+ return "user";
91
+ }
92
+ function hasServiceActorScope(actor, scope) {
93
+ if (scope === "team")
94
+ return validateActor(actor, "team");
95
+ if (scope === "organization") {
96
+ return validateActor(actor, "organization") || validateActor(actor, "team");
97
+ }
98
+ return validateActor(actor, "user");
99
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const base_actor_1 = require("./base.actor");
4
+ describe("base.actor", () => {
5
+ it("creates a user actor when only user claims are present", () => {
6
+ const actor = (0, base_actor_1.createServiceActor)({
7
+ userId: "user-1",
8
+ userRole: "member",
9
+ });
10
+ expect(actor).toEqual({
11
+ userId: "user-1",
12
+ userRole: "member",
13
+ organizationId: null,
14
+ organizationRole: null,
15
+ teamId: null,
16
+ teamRole: null,
17
+ });
18
+ });
19
+ it("derives the highest available scope", () => {
20
+ expect((0, base_actor_1.getServiceActorScope)({
21
+ userId: "user-1",
22
+ userRole: "member",
23
+ organizationId: "org-1",
24
+ organizationRole: "owner",
25
+ teamId: null,
26
+ teamRole: null,
27
+ })).toBe("organization");
28
+ expect((0, base_actor_1.getServiceActorScope)({
29
+ userId: "user-1",
30
+ userRole: "member",
31
+ organizationId: "org-1",
32
+ organizationRole: "owner",
33
+ teamId: "team-1",
34
+ teamRole: "manager",
35
+ })).toBe("team");
36
+ });
37
+ it("validates hierarchy before building a team actor", () => {
38
+ expect(() => (0, base_actor_1.createServiceActor)({
39
+ userId: "user-1",
40
+ userRole: "member",
41
+ teamId: "team-1",
42
+ teamRole: "manager",
43
+ })).toThrow("organization access before team access");
44
+ });
45
+ it("checks required scope against broader actors", () => {
46
+ const actor = (0, base_actor_1.createServiceActor)({
47
+ userId: "user-1",
48
+ userRole: "member",
49
+ organizationId: "org-1",
50
+ organizationRole: "owner",
51
+ teamId: "team-1",
52
+ teamRole: "manager",
53
+ });
54
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "user")).toBe(true);
55
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "organization")).toBe(true);
56
+ expect((0, base_actor_1.hasServiceActorScope)(actor, "team")).toBe(true);
57
+ });
58
+ });
@@ -1,4 +1,4 @@
1
- import type { Session, User } from "../auth/auth.lib";
1
+ import type { ServiceActor } from "./base.actor";
2
2
  import type { ServerResultAsync } from "./base.dto";
3
3
  type Level = "user" | "team" | "organization";
4
4
  type Access = "all" | "own";
@@ -19,10 +19,6 @@ export type NestedGrants = Record<string, Partial<Record<Level, Record<string, R
19
19
  export type ResourceGrant = Omit<Grant, "resource">;
20
20
  export type ResourceActionGrant = Omit<ResourceGrant, "action">;
21
21
  export declare function flattenNestedGrants(nestedGrants: NestedGrants): Grant[];
22
- interface PermissionContext {
23
- session: Session;
24
- user: User;
25
- }
26
- export declare function checkPermissionSync<T extends Entity>(ctx: PermissionContext, grants: ResourceActionGrant[], entities?: T | T[]): boolean;
27
- export declare function checkPermissionAsync<T extends Entity>(ctx: PermissionContext, grants: ResourceActionGrant[], getEntities: () => ServerResultAsync<T | T[] | undefined>): ServerResultAsync<boolean>;
22
+ export declare function checkPermissionSync<T extends Entity>(actor: ServiceActor, grants: ResourceActionGrant[], entities?: T | T[]): boolean;
23
+ export declare function checkPermissionAsync<T extends Entity>(actor: ServiceActor, grants: ResourceActionGrant[], getEntities: () => ServerResultAsync<T | T[] | undefined>): ServerResultAsync<boolean>;
28
24
  export {};
@@ -92,26 +92,38 @@ function checkOwnAccess(grants, roles, contextValues, entities) {
92
92
  }
93
93
  return false;
94
94
  }
95
- function checkPermissionSync(ctx, grants, entities) {
95
+ function checkPermissionSync(actor, grants, entities) {
96
96
  if (!grants || grants.length === 0)
97
97
  return false;
98
- const { id: userId, role: userRole } = ctx.user;
99
- const { activeOrganizationRole: organizationRole, activeTeamRole: teamRole, activeOrganizationId: organizationId, activeTeamId: teamId, } = ctx.session;
100
- const roles = { userRole, teamRole, organizationRole };
101
- const contextValues = { userId, teamId, organizationId };
98
+ const roles = {
99
+ userRole: actor.userRole,
100
+ teamRole: actor.teamRole,
101
+ organizationRole: actor.organizationRole,
102
+ };
103
+ const contextValues = {
104
+ userId: actor.userId,
105
+ teamId: actor.teamId,
106
+ organizationId: actor.organizationId,
107
+ };
102
108
  // Pass 1: Check for "all" access first (no ownership check needed)
103
109
  if (hasAllAccess(grants, roles))
104
110
  return true;
105
111
  // Pass 2: Check "own" access with ownership validation
106
112
  return checkOwnAccess(grants, roles, contextValues, entities);
107
113
  }
108
- async function checkPermissionAsync(ctx, grants, getEntities) {
114
+ async function checkPermissionAsync(actor, grants, getEntities) {
109
115
  if (!grants || grants.length === 0)
110
116
  return (0, neverthrow_1.ok)(false);
111
- const { id: userId, role: userRole } = ctx.user;
112
- const { activeOrganizationRole: organizationRole, activeTeamRole: teamRole, activeOrganizationId: organizationId, activeTeamId: teamId, } = ctx.session;
113
- const roles = { userRole, teamRole, organizationRole };
114
- const contextValues = { userId, teamId, organizationId };
117
+ const roles = {
118
+ userRole: actor.userRole,
119
+ teamRole: actor.teamRole,
120
+ organizationRole: actor.organizationRole,
121
+ };
122
+ const contextValues = {
123
+ userId: actor.userId,
124
+ teamId: actor.teamId,
125
+ organizationId: actor.organizationId,
126
+ };
115
127
  // Pass 1: Check for "all" access first (no entity fetch needed)
116
128
  if (hasAllAccess(grants, roles))
117
129
  return (0, neverthrow_1.ok)(true);
@@ -1,53 +1,24 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const neverthrow_1 = require("neverthrow");
4
- const base_grants_1 = require("./base.grants");
5
4
  const errors_1 = require("../../utils/errors");
6
- // ============================================
7
- // Mock Factories
8
- // ============================================
9
- function createMockUser(overrides = {}) {
10
- return {
11
- id: "user-123",
12
- role: "member",
13
- email: "test@example.com",
14
- emailVerified: true,
15
- name: "Test User",
16
- createdAt: new Date(),
17
- updatedAt: new Date(),
18
- image: null,
19
- onboarding: null,
20
- preferences: null,
21
- flags: null,
22
- stripeCustomerId: null,
23
- paymentCustomerId: null,
24
- paymentPlanTier: null,
25
- paymentPlanExpiresAt: null,
26
- ...overrides,
27
- };
28
- }
29
- function createMockSession(overrides = {}) {
30
- return {
31
- id: "session-123",
32
- userId: "user-123",
33
- token: "token-123",
34
- expiresAt: new Date(Date.now() + 86400000),
35
- createdAt: new Date(),
36
- updatedAt: new Date(),
37
- ipAddress: null,
38
- userAgent: null,
39
- activeOrganizationId: null,
40
- activeTeamId: null,
41
- activeOrganizationRole: null,
42
- activeTeamRole: null,
43
- ...overrides,
44
- };
45
- }
5
+ const base_actor_1 = require("./base.actor");
6
+ const base_grants_1 = require("./base.grants");
46
7
  function createMockContext(userOverrides = {}, sessionOverrides = {}) {
47
- return {
48
- user: createMockUser(userOverrides),
49
- session: createMockSession(sessionOverrides),
50
- };
8
+ const organizationId = sessionOverrides.activeOrganizationId ?? (sessionOverrides.activeTeamId ? "org-123" : null);
9
+ const organizationRole = sessionOverrides.activeOrganizationRole ?? (sessionOverrides.activeTeamRole ? "member" : null);
10
+ const actor = (0, base_actor_1.createServiceActor)({
11
+ userId: userOverrides.id ?? "user-123",
12
+ userRole: userOverrides.role ?? "member",
13
+ organizationId,
14
+ organizationRole,
15
+ teamId: sessionOverrides.activeTeamId ?? null,
16
+ teamRole: sessionOverrides.activeTeamRole ?? null,
17
+ });
18
+ if (!actor) {
19
+ throw new Error("Expected actor");
20
+ }
21
+ return actor;
51
22
  }
52
23
  function createMockEntity(overrides = {}) {
53
24
  return {
@@ -2,22 +2,25 @@ import type { QueryInput } from "@m5kdev/commons/modules/schemas/query.schema";
2
2
  import type { TRPC_ERROR_CODE_KEY } from "@trpc/server";
3
3
  import type { ServerError } from "../../utils/errors";
4
4
  import type { logger } from "../../utils/logger";
5
- import type { Context, Session, User } from "../auth/auth.lib";
6
5
  import type { Base } from "./base.abstract";
6
+ import { type Actor, type ActorScope, type AuthenticatedActor } from "./base.actor";
7
7
  import type { ServerResult, ServerResultAsync } from "./base.dto";
8
8
  import type { Entity, ResourceActionGrant } from "./base.grants";
9
9
  type ServiceLogger = ReturnType<typeof logger.child>;
10
10
  type RepositoryMap = Record<string, Base>;
11
11
  type ServiceMap = Record<string, Base>;
12
12
  export type ServiceProcedureContext = {
13
- user?: User | null;
14
- session?: Session | null;
13
+ actor?: AuthenticatedActor | null;
15
14
  } & Record<string, unknown>;
16
15
  export type ServiceProcedureState = Record<string, unknown>;
17
16
  export type ServiceProcedureStoredValue<T> = [T] extends [undefined] ? undefined : Awaited<T>;
18
17
  export type ServiceProcedureResultLike<T> = T | ServerResult<T> | Promise<T | ServerResult<T>>;
19
- export type ServiceProcedureContextFilterScope = "user" | "organization" | "team";
18
+ export type ServiceProcedureContextFilterScope = ActorScope;
20
19
  export type ServiceProcedureContextFilteredInput<TInput> = Extract<NonNullable<TInput>, QueryInput>;
20
+ type ServiceProcedureAuthContext<Scope extends ActorScope> = {
21
+ actor: Actor[Scope];
22
+ };
23
+ type ServiceProcedureRequiredScopeFromFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined> = TInclude extends readonly ServiceProcedureContextFilterScope[] ? "team" extends TInclude[number] ? "team" : "organization" extends TInclude[number] ? "organization" : "user" : "user";
21
24
  export type ServiceProcedure<TInput, TCtx extends ServiceProcedureContext, TOutput> = (input: TInput, ctx: TCtx) => ServerResultAsync<TOutput>;
22
25
  export type ServiceProcedureArgs<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState> = {
23
26
  input: TInput;
@@ -50,24 +53,24 @@ export type ServiceProcedureAccessConfig<TInput, TCtx extends ServiceProcedureCo
50
53
  export interface ServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>> {
51
54
  use<StepName extends string, TOutput = void>(stepName: StepName, step: ServiceProcedureStep<TInput, TCtx, Repositories, Services, State, TOutput>): ServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TOutput>>>;
52
55
  mapInput<StepName extends string, TNextInput>(stepName: StepName, step: ServiceProcedureInputMapper<TInput, TCtx, Repositories, Services, State, TNextInput>): ServiceProcedureBuilder<ServiceProcedureStoredValue<TNextInput>, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TNextInput>>>;
53
- addContextFilter(include?: readonly ServiceProcedureContextFilterScope[]): ServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & Context, Repositories, Services, State & {
56
+ addContextFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined = undefined>(include?: TInclude): ServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & ServiceProcedureAuthContext<ServiceProcedureRequiredScopeFromFilter<TInclude>>, Repositories, Services, State & {
54
57
  contextFilter: ServiceProcedureContextFilteredInput<TInput>;
55
58
  }>;
56
- requireAuth(): ServiceProcedureBuilder<TInput, TCtx & Context, Repositories, Services, State>;
59
+ requireAuth<Scope extends ActorScope = "user">(scope?: Scope): ServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<Scope>, Repositories, Services, State>;
57
60
  handle<TOutput>(handler: ServiceProcedureHandler<TInput, TCtx, Repositories, Services, State, TOutput>): ServiceProcedure<TInput, TCtx, TOutput>;
58
61
  }
59
62
  export interface PermissionServiceProcedureBuilder<TInput, TCtx extends ServiceProcedureContext, Repositories extends RepositoryMap, Services extends ServiceMap, State extends ServiceProcedureState = Record<string, never>> extends ServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State> {
60
63
  use<StepName extends string, TOutput = void>(stepName: StepName, step: ServiceProcedureStep<TInput, TCtx, Repositories, Services, State, TOutput>): PermissionServiceProcedureBuilder<TInput, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TOutput>>>;
61
64
  mapInput<StepName extends string, TNextInput>(stepName: StepName, step: ServiceProcedureInputMapper<TInput, TCtx, Repositories, Services, State, TNextInput>): PermissionServiceProcedureBuilder<ServiceProcedureStoredValue<TNextInput>, TCtx, Repositories, Services, State & Record<StepName, ServiceProcedureStoredValue<TNextInput>>>;
62
- addContextFilter(include?: readonly ServiceProcedureContextFilterScope[]): PermissionServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & Context, Repositories, Services, State & {
65
+ addContextFilter<TInclude extends readonly ServiceProcedureContextFilterScope[] | undefined = undefined>(include?: TInclude): PermissionServiceProcedureBuilder<ServiceProcedureContextFilteredInput<TInput>, TCtx & ServiceProcedureAuthContext<ServiceProcedureRequiredScopeFromFilter<TInclude>>, Repositories, Services, State & {
63
66
  contextFilter: ServiceProcedureContextFilteredInput<TInput>;
64
67
  }>;
65
- requireAuth(): PermissionServiceProcedureBuilder<TInput, TCtx & Context, Repositories, Services, State>;
66
- access(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State>): PermissionServiceProcedureBuilder<TInput, TCtx & Context, Repositories, Services, State>;
67
- access<TEntities extends Entity | Entity[] | undefined>(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State, TEntities>): PermissionServiceProcedureBuilder<TInput, TCtx & Context, Repositories, Services, State & {
68
+ requireAuth<Scope extends ActorScope = "user">(scope?: Scope): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<Scope>, Repositories, Services, State>;
69
+ access(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State>;
70
+ access<TEntities extends Entity | Entity[] | undefined>(config: ServiceProcedureAccessEntitiesConfig<TInput, TCtx, Repositories, Services, State, TEntities>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State & {
68
71
  access: TEntities;
69
72
  }>;
70
- access<StepName extends ServiceProcedureEntityStepName<State>>(config: ServiceProcedureAccessStateConfig<State, StepName>): PermissionServiceProcedureBuilder<TInput, TCtx & Context, Repositories, Services, State & {
73
+ access<StepName extends ServiceProcedureEntityStepName<State>>(config: ServiceProcedureAccessStateConfig<State, StepName>): PermissionServiceProcedureBuilder<TInput, TCtx & ServiceProcedureAuthContext<"user">, Repositories, Services, State & {
71
74
  access: State[StepName];
72
75
  }>;
73
76
  }
@@ -75,7 +78,7 @@ type BaseServiceProcedureHost<Repositories extends RepositoryMap, Services exten
75
78
  repository: Repositories;
76
79
  service: Services;
77
80
  logger: ServiceLogger;
78
- addContextFilter(ctx: Context, include?: {
81
+ addContextFilter(actor: AuthenticatedActor, include?: {
79
82
  user?: boolean;
80
83
  organization?: boolean;
81
84
  team?: boolean;
@@ -89,14 +92,8 @@ type BaseServiceProcedureHost<Repositories extends RepositoryMap, Services exten
89
92
  handleUnknownError(error: unknown): ServerError;
90
93
  };
91
94
  type PermissionServiceProcedureHost<Repositories extends RepositoryMap, Services extends ServiceMap> = BaseServiceProcedureHost<Repositories, Services> & {
92
- checkPermission<T extends Entity>(ctx: {
93
- session: Session;
94
- user: User;
95
- }, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): boolean;
96
- checkPermissionAsync<T extends Entity>(ctx: {
97
- session: Session;
98
- user: User;
99
- }, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<boolean>;
95
+ checkPermission<T extends Entity>(actor: AuthenticatedActor, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): boolean;
96
+ checkPermissionAsync<T extends Entity>(actor: AuthenticatedActor, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<boolean>;
100
97
  };
101
98
  type ProcedureRuntimeStep<Repositories extends RepositoryMap, Services extends ServiceMap> = {
102
99
  stage: "use" | "input" | "auth" | "access";
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createServiceProcedureBuilder = createServiceProcedureBuilder;
4
4
  exports.createPermissionServiceProcedureBuilder = createPermissionServiceProcedureBuilder;
5
5
  const neverthrow_1 = require("neverthrow");
6
+ const base_actor_1 = require("./base.actor");
6
7
  const DEFAULT_CONTEXT_FILTER_INCLUDE = [
7
8
  "user",
8
9
  ];
@@ -43,19 +44,24 @@ function logProcedureStage(host, procedureName, ctx, stage, { stepName, duration
43
44
  stepName,
44
45
  durationMs,
45
46
  errorCode,
46
- hasUser: Boolean(ctx.user),
47
- hasSession: Boolean(ctx.session),
47
+ hasActor: Boolean(ctx.actor),
48
48
  });
49
49
  }
50
- function createRequireAuthStep(host) {
50
+ function requireProcedureActor(host, ctx, scope) {
51
+ if (!ctx.actor) {
52
+ return host.error("UNAUTHORIZED", "Unauthorized");
53
+ }
54
+ if (!(0, base_actor_1.validateActor)(ctx.actor, scope)) {
55
+ return host.error("FORBIDDEN", "Forbidden");
56
+ }
57
+ return (0, neverthrow_1.ok)(ctx.actor);
58
+ }
59
+ function createRequireAuthStep(host, scope = "user") {
51
60
  return {
52
61
  stage: "auth",
53
62
  stepName: "auth",
54
63
  run: async ({ ctx }) => {
55
- if (!ctx.user || !ctx.session) {
56
- return host.error("UNAUTHORIZED", "Unauthorized");
57
- }
58
- return (0, neverthrow_1.ok)(true);
64
+ return requireProcedureActor(host, ctx, scope);
59
65
  },
60
66
  };
61
67
  }
@@ -75,10 +81,20 @@ function createInputStep(stepName, step) {
75
81
  }
76
82
  function createContextFilterStep(host, include) {
77
83
  const contextInclude = getContextFilterInclude(include);
84
+ const requiredScope = contextInclude.team
85
+ ? "team"
86
+ : contextInclude.organization
87
+ ? "organization"
88
+ : "user";
78
89
  return {
79
90
  stage: "input",
80
91
  stepName: "contextFilter",
81
- run: async ({ input, ctx }) => (0, neverthrow_1.ok)(host.addContextFilter(ctx, contextInclude, input)),
92
+ run: async ({ input, ctx }) => {
93
+ const actor = requireProcedureActor(host, ctx, requiredScope);
94
+ if (actor.isErr())
95
+ return actor;
96
+ return (0, neverthrow_1.ok)(host.addContextFilter(actor.value, contextInclude, input));
97
+ },
82
98
  };
83
99
  }
84
100
  function createAccessStep(host, config) {
@@ -87,16 +103,12 @@ function createAccessStep(host, config) {
87
103
  stepName: "access",
88
104
  run: async (args) => {
89
105
  const typedArgs = args;
90
- if (!typedArgs.ctx.user || !typedArgs.ctx.session) {
91
- return host.error("UNAUTHORIZED", "Unauthorized");
92
- }
93
- const permissionContext = {
94
- user: typedArgs.ctx.user,
95
- session: typedArgs.ctx.session,
96
- };
106
+ const actor = requireProcedureActor(host, typedArgs.ctx, "user");
107
+ if (actor.isErr())
108
+ return actor;
97
109
  if ("entityStep" in config && typeof config.entityStep === "string") {
98
110
  const entities = typedArgs.state[config.entityStep];
99
- const hasPermission = host.checkPermission(permissionContext, config.action, entities, config.grants);
111
+ const hasPermission = host.checkPermission(actor.value, config.action, entities, config.grants);
100
112
  if (!hasPermission) {
101
113
  return host.error("FORBIDDEN");
102
114
  }
@@ -105,7 +117,7 @@ function createAccessStep(host, config) {
105
117
  if (typeof config.entities === "function") {
106
118
  const resolveEntities = config.entities;
107
119
  let loadedEntities;
108
- const permission = await host.checkPermissionAsync(permissionContext, config.action, async () => {
120
+ const permission = await host.checkPermissionAsync(actor.value, config.action, async () => {
109
121
  const entityResult = await normalizeProcedureResult(resolveEntities(typedArgs));
110
122
  if (entityResult.isOk()) {
111
123
  loadedEntities = entityResult.value;
@@ -121,7 +133,7 @@ function createAccessStep(host, config) {
121
133
  return (0, neverthrow_1.ok)(loadedEntities);
122
134
  }
123
135
  const entities = config.entities;
124
- const hasPermission = host.checkPermission(permissionContext, config.action, entities, config.grants);
136
+ const hasPermission = host.checkPermission(actor.value, config.action, entities, config.grants);
125
137
  if (!hasPermission) {
126
138
  return host.error("FORBIDDEN");
127
139
  }
@@ -203,7 +215,7 @@ function createServiceProcedureBuilder(host, config) {
203
215
  function addContextFilter(include) {
204
216
  const steps = hasStepName(config.steps, "auth")
205
217
  ? config.steps
206
- : [...config.steps, createRequireAuthStep(host)];
218
+ : [...config.steps, createRequireAuthStep(host, "user")];
207
219
  assertUniqueStepName(steps, "contextFilter");
208
220
  return createServiceProcedureBuilder(host, {
209
221
  ...config,
@@ -226,11 +238,11 @@ function createServiceProcedureBuilder(host, config) {
226
238
  });
227
239
  },
228
240
  addContextFilter,
229
- requireAuth() {
241
+ requireAuth(scope) {
230
242
  assertUniqueStepName(config.steps, "auth");
231
243
  return createServiceProcedureBuilder(host, {
232
244
  ...config,
233
- steps: [...config.steps, createRequireAuthStep(host)],
245
+ steps: [...config.steps, createRequireAuthStep(host, scope ?? "user")],
234
246
  });
235
247
  },
236
248
  handle(handler) {
@@ -243,7 +255,7 @@ function createPermissionServiceProcedureBuilder(host, config) {
243
255
  function addContextFilter(include) {
244
256
  const steps = hasStepName(config.steps, "auth")
245
257
  ? config.steps
246
- : [...config.steps, createRequireAuthStep(host)];
258
+ : [...config.steps, createRequireAuthStep(host, "user")];
247
259
  assertUniqueStepName(steps, "contextFilter");
248
260
  return createPermissionServiceProcedureBuilder(host, {
249
261
  ...config,
@@ -273,11 +285,11 @@ function createPermissionServiceProcedureBuilder(host, config) {
273
285
  });
274
286
  },
275
287
  addContextFilter,
276
- requireAuth() {
288
+ requireAuth(scope) {
277
289
  assertUniqueStepName(config.steps, "auth");
278
290
  return createPermissionServiceProcedureBuilder(host, {
279
291
  ...config,
280
- steps: [...config.steps, createRequireAuthStep(host)],
292
+ steps: [...config.steps, createRequireAuthStep(host, scope ?? "user")],
281
293
  });
282
294
  },
283
295
  access,
@@ -1,6 +1,6 @@
1
1
  import type { QueryFilter, QueryInput } from "@m5kdev/commons/modules/schemas/query.schema";
2
- import type { Context, Session, User } from "../auth/auth.lib";
3
2
  import { Base } from "./base.abstract";
3
+ import { type AuthenticatedActor } from "./base.actor";
4
4
  import type { ServerResult, ServerResultAsync } from "./base.dto";
5
5
  import { type Entity, type ResourceActionGrant, type ResourceGrant } from "./base.grants";
6
6
  import { type PermissionServiceProcedureBuilder, type ServiceProcedureBuilder, type ServiceProcedureContext } from "./base.procedure";
@@ -12,7 +12,7 @@ export declare class BaseService<Repositories extends Record<string, Base>, Serv
12
12
  addUserFilter(value: string, query?: undefined, columnId?: string, method?: QueryFilter["method"]): QueryInput;
13
13
  addUserFilter<TQuery extends QueryInput>(value: string, query: TQuery, columnId?: string, method?: QueryFilter["method"]): TQuery;
14
14
  protected procedure<TInput, TCtx extends ServiceProcedureContext = DefaultContext>(name: string): ServiceProcedureBuilder<TInput, TCtx, Repositories, Services>;
15
- addContextFilter(ctx: Context, include?: {
15
+ addContextFilter(actor: AuthenticatedActor, include?: {
16
16
  user?: boolean;
17
17
  organization?: boolean;
18
18
  team?: boolean;
@@ -20,7 +20,7 @@ export declare class BaseService<Repositories extends Record<string, Base>, Serv
20
20
  columnId: string;
21
21
  method: QueryFilter["method"];
22
22
  }>): QueryInput;
23
- addContextFilter<TQuery extends QueryInput>(ctx: Context, include: {
23
+ addContextFilter<TQuery extends QueryInput>(actor: AuthenticatedActor, include: {
24
24
  user?: boolean;
25
25
  organization?: boolean;
26
26
  team?: boolean;
@@ -32,21 +32,9 @@ export declare class BaseService<Repositories extends Record<string, Base>, Serv
32
32
  export declare class BasePermissionService<Repositories extends Record<string, Base>, Services extends Record<string, Base>, DefaultContext extends ServiceProcedureContext = ServiceProcedureContext> extends BaseService<Repositories, Services, DefaultContext> {
33
33
  grants: ResourceGrant[];
34
34
  constructor(repository: Repositories, service: Services, grants?: ResourceGrant[]);
35
- accessGuard<T extends Entity>(ctx: {
36
- session: Session;
37
- user: User;
38
- }, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): ServerResult<true>;
39
- accessGuardAsync<T extends Entity>(ctx: {
40
- session: Session;
41
- user: User;
42
- }, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<true>;
35
+ accessGuard<T extends Entity>(actor: AuthenticatedActor, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): ServerResult<true>;
36
+ accessGuardAsync<T extends Entity>(actor: AuthenticatedActor, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<true>;
43
37
  protected procedure<TInput, TCtx extends ServiceProcedureContext = DefaultContext>(name: string): PermissionServiceProcedureBuilder<TInput, TCtx, Repositories, Services>;
44
- checkPermission<T extends Entity>(ctx: {
45
- session: Session;
46
- user: User;
47
- }, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): boolean;
48
- checkPermissionAsync<T extends Entity>(ctx: {
49
- session: Session;
50
- user: User;
51
- }, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<boolean>;
38
+ checkPermission<T extends Entity>(actor: AuthenticatedActor, action: string, entities?: T | T[], grants?: ResourceActionGrant[]): boolean;
39
+ checkPermissionAsync<T extends Entity>(actor: AuthenticatedActor, action: string, getEntities: () => ServerResultAsync<T | T[] | undefined>, grants?: ResourceActionGrant[]): ServerResultAsync<boolean>;
52
40
  }