@entifix/authz 0.1.3 → 0.2.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.
@@ -27,7 +27,7 @@ export interface PolicyRequest {
27
27
  export interface PolicyDecision {
28
28
  decide(request: PolicyRequest): boolean;
29
29
  }
30
- declare const PolicyDecisionTag_base: Context.TagClass<PolicyDecisionTag, "PolicyDecisionTag", PolicyDecision>;
30
+ declare const PolicyDecisionTag_base: Context.ServiceClass<PolicyDecisionTag, "PolicyDecisionTag", PolicyDecision>;
31
31
  /** DI tag the composition root binds to a concrete {@link PolicyDecision}. */
32
32
  export declare class PolicyDecisionTag extends PolicyDecisionTag_base {
33
33
  }
@@ -1 +1 @@
1
- {"version":3,"file":"policy-decision.d.ts","sourceRoot":"","sources":["../../src/policy/policy-decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAO,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAEhD,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC;CACzC;;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,sBAGpC;CAAG;AAEN;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,UAAU,KACjB,cAGD,CAAC"}
1
+ {"version":3,"file":"policy-decision.d.ts","sourceRoot":"","sources":["../../src/policy/policy-decision.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,OAAO,EACL,KAAK,gBAAgB,EAErB,KAAK,QAAQ,EACd,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAO,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAEhD,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CACtD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC;CACzC;;AAED,8EAA8E;AAC9E,qBAAa,iBAAkB,SAAQ,sBAGf;CAAG;AAE3B;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,wBAAwB,GACnC,QAAQ,UAAU,KACjB,cAGD,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { Context } from 'effect';
2
2
  import { permissionOf } from '../values/permission.js';
3
3
  import { can } from './can.js';
4
- /** DI tag the composition root binds to a concrete {@link PolicyDecision}. */ export class PolicyDecisionTag extends Context.Tag('PolicyDecisionTag')() {
4
+ /** DI tag the composition root binds to a concrete {@link PolicyDecision}. */ export class PolicyDecisionTag extends Context.Service()('PolicyDecisionTag') {
5
5
  }
6
6
  /**
7
7
  * The v1 implementation: role-derived grants from a table the host supplies,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/policy/policy-decision.ts"],"sourcesContent":["import { Context } from 'effect';\n\nimport {\n type PermissionAction,\n permissionOf,\n type Resource,\n} from '../values/permission.js';\nimport { can, type GrantTable } from './can.js';\n\n/** The subject half of a policy request — a `Principal` structurally satisfies it. */\nexport interface PolicySubject {\n readonly roles: readonly string[];\n readonly attributes?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * What a caller wants to do, in attribute-based terms. `resource`, `attributes`\n * and `context` are the ABAC seam: the v1 implementation reads only\n * `subject.roles` and `resource`/`action`, but the shape is already the one a\n * rule engine needs, so adopting one never touches a call site.\n */\nexport interface PolicyRequest {\n readonly subject: PolicySubject;\n /** `<domain>:<entityKey>` — what is being acted on. */\n readonly resource: Resource;\n readonly action: PermissionAction;\n /** Request-time facts a future policy may read (tenant, time, ownership). */\n readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * The authorization port. Synchronous on purpose: a decision must be cheap\n * enough to take on every request, and the v1 answer is a table lookup.\n */\nexport interface PolicyDecision {\n decide(request: PolicyRequest): boolean;\n}\n\n/** DI tag the composition root binds to a concrete {@link PolicyDecision}. */\nexport class PolicyDecisionTag extends Context.Tag('PolicyDecisionTag')<\n PolicyDecisionTag,\n PolicyDecision\n>() {}\n\n/**\n * The v1 implementation: role-derived grants from a table the host supplies,\n * ignoring resource attributes and context. Swapping in an attribute-aware\n * engine is a change of the `Layer` that provides {@link PolicyDecisionTag},\n * nothing more.\n *\n * ⚠️ **The table is an argument now, and that is the whole seam.** It used to be\n * imported, which meant this framework shipped the sentence \"a `user` may ring\n * up a counter sale\" to everyone who installed it. Passing it in costs one\n * argument at each composition root and is what lets two applications with\n * different roles share a policy engine.\n */\nexport const makeStaticPolicyDecision = (\n grants: GrantTable,\n): PolicyDecision => ({\n decide: ({ subject, resource, action }) =>\n can(grants, subject.roles, permissionOf(resource, action)),\n});\n"],"names":["Context","permissionOf","can","PolicyDecisionTag","Tag","makeStaticPolicyDecision","grants","decide","subject","resource","action","roles"],"mappings":"AAAA,SAASA,OAAO,QAAQ,SAAS;AAEjC,SAEEC,YAAY,QAEP,0BAA0B;AACjC,SAASC,GAAG,QAAyB,WAAW;AA+BhD,4EAA4E,GAC5E,OAAO,MAAMC,0BAA0BH,QAAQI,GAAG,CAAC;AAG9C;AAEL;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,2BAA2B,CACtCC,SACoB,CAAA;QACpBC,QAAQ,CAAC,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,MAAM,EAAE,GACpCR,IAAII,QAAQE,QAAQG,KAAK,EAAEV,aAAaQ,UAAUC;IACtD,CAAA,EAAG"}
1
+ {"version":3,"sources":["../../src/policy/policy-decision.ts"],"sourcesContent":["import { Context } from 'effect';\n\nimport {\n type PermissionAction,\n permissionOf,\n type Resource,\n} from '../values/permission.js';\nimport { can, type GrantTable } from './can.js';\n\n/** The subject half of a policy request — a `Principal` structurally satisfies it. */\nexport interface PolicySubject {\n readonly roles: readonly string[];\n readonly attributes?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * What a caller wants to do, in attribute-based terms. `resource`, `attributes`\n * and `context` are the ABAC seam: the v1 implementation reads only\n * `subject.roles` and `resource`/`action`, but the shape is already the one a\n * rule engine needs, so adopting one never touches a call site.\n */\nexport interface PolicyRequest {\n readonly subject: PolicySubject;\n /** `<domain>:<entityKey>` — what is being acted on. */\n readonly resource: Resource;\n readonly action: PermissionAction;\n /** Request-time facts a future policy may read (tenant, time, ownership). */\n readonly context?: Readonly<Record<string, unknown>>;\n}\n\n/**\n * The authorization port. Synchronous on purpose: a decision must be cheap\n * enough to take on every request, and the v1 answer is a table lookup.\n */\nexport interface PolicyDecision {\n decide(request: PolicyRequest): boolean;\n}\n\n/** DI tag the composition root binds to a concrete {@link PolicyDecision}. */\nexport class PolicyDecisionTag extends Context.Service<\n PolicyDecisionTag,\n PolicyDecision\n>()('PolicyDecisionTag') {}\n\n/**\n * The v1 implementation: role-derived grants from a table the host supplies,\n * ignoring resource attributes and context. Swapping in an attribute-aware\n * engine is a change of the `Layer` that provides {@link PolicyDecisionTag},\n * nothing more.\n *\n * ⚠️ **The table is an argument now, and that is the whole seam.** It used to be\n * imported, which meant this framework shipped the sentence \"a `user` may ring\n * up a counter sale\" to everyone who installed it. Passing it in costs one\n * argument at each composition root and is what lets two applications with\n * different roles share a policy engine.\n */\nexport const makeStaticPolicyDecision = (\n grants: GrantTable,\n): PolicyDecision => ({\n decide: ({ subject, resource, action }) =>\n can(grants, subject.roles, permissionOf(resource, action)),\n});\n"],"names":["Context","permissionOf","can","PolicyDecisionTag","Service","makeStaticPolicyDecision","grants","decide","subject","resource","action","roles"],"mappings":"AAAA,SAASA,OAAO,QAAQ,SAAS;AAEjC,SAEEC,YAAY,QAEP,0BAA0B;AACjC,SAASC,GAAG,QAAyB,WAAW;AA+BhD,4EAA4E,GAC5E,OAAO,MAAMC,0BAA0BH,QAAQI,OAAO,GAGlD;AAAsB;AAE1B;;;;;;;;;;;CAWC,GACD,OAAO,MAAMC,2BAA2B,CACtCC,SACoB,CAAA;QACpBC,QAAQ,CAAC,EAAEC,OAAO,EAAEC,QAAQ,EAAEC,MAAM,EAAE,GACpCR,IAAII,QAAQE,QAAQG,KAAK,EAAEV,aAAaQ,UAAUC;IACtD,CAAA,EAAG"}
@@ -14,7 +14,7 @@ import { type Permission } from '../values/permission.js';
14
14
  export interface ServiceCrossingPolicy {
15
15
  allows(required: Permission): boolean;
16
16
  }
17
- declare const ServiceCrossingPolicyTag_base: Context.TagClass<ServiceCrossingPolicyTag, "ServiceCrossingPolicyTag", ServiceCrossingPolicy>;
17
+ declare const ServiceCrossingPolicyTag_base: Context.ServiceClass<ServiceCrossingPolicyTag, "ServiceCrossingPolicyTag", ServiceCrossingPolicy>;
18
18
  /** DI tag the composition root binds to a concrete {@link ServiceCrossingPolicy}. */
19
19
  export declare class ServiceCrossingPolicyTag extends ServiceCrossingPolicyTag_base {
20
20
  }
@@ -1 +1 @@
1
- {"version":3,"file":"service-crossing-policy.d.ts","sourceRoot":"","sources":["../../src/policy/service-crossing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC;CACvC;;AAED,qFAAqF;AACrF,qBAAa,wBAAyB,SAAQ,6BAEM;CAAG;AAEvD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,GAC1C,SAAS,SAAS,UAAU,EAAE,KAC7B,qBAGD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,qBAEhC,CAAC"}
1
+ {"version":3,"file":"service-crossing-policy.d.ts","sourceRoot":"","sources":["../../src/policy/service-crossing-policy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAEjC,OAAO,EAAE,KAAK,UAAU,EAAqB,MAAM,yBAAyB,CAAC;AAE7E;;;;;;;;;;GAUG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,QAAQ,EAAE,UAAU,GAAG,OAAO,CAAC;CACvC;;AAED,qFAAqF;AACrF,qBAAa,wBAAyB,SAAQ,6BAGf;CAAG;AAElC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,+BAA+B,GAC1C,SAAS,SAAS,UAAU,EAAE,KAC7B,qBAGD,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,kBAAkB,EAAE,qBAEhC,CAAC"}
@@ -1,6 +1,6 @@
1
1
  import { Context } from 'effect';
2
2
  import { permissionMatches } from '../values/permission.js';
3
- /** DI tag the composition root binds to a concrete {@link ServiceCrossingPolicy}. */ export class ServiceCrossingPolicyTag extends Context.Tag('ServiceCrossingPolicyTag')() {
3
+ /** DI tag the composition root binds to a concrete {@link ServiceCrossingPolicy}. */ export class ServiceCrossingPolicyTag extends Context.Service()('ServiceCrossingPolicyTag') {
4
4
  }
5
5
  /**
6
6
  * The allowlist implementation: a host hands over the whole of what its fleet
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/policy/service-crossing-policy.ts"],"sourcesContent":["import { Context } from 'effect';\n\nimport { type Permission, permissionMatches } from '../values/permission.js';\n\n/**\n * What a **service** may do when it crosses into another party's tenant storage.\n *\n * ⚠️ **A second port rather than a second method on {@link PolicyDecision}, and\n * the separation is the security property.** `permissionsOf` expands grants by\n * looking a *role string* up in the grant table, so a crossing grant living\n * there would be inherited by any access token carrying the matching string —\n * turning a session claim into a cross-organization write. Two tables, two\n * lookups, and nothing a session guard calls can reach this one\n * ([ADR 0023](../../../../../../docs/adr/0023-service-to-service-tenant-crossing.md)).\n */\nexport interface ServiceCrossingPolicy {\n allows(required: Permission): boolean;\n}\n\n/** DI tag the composition root binds to a concrete {@link ServiceCrossingPolicy}. */\nexport class ServiceCrossingPolicyTag extends Context.Tag(\n 'ServiceCrossingPolicyTag',\n)<ServiceCrossingPolicyTag, ServiceCrossingPolicy>() {}\n\n/**\n * The allowlist implementation: a host hands over the whole of what its fleet\n * may do, in one list.\n *\n * The comparison goes through `permissionMatches` rather than `includes`, so a\n * required permission is matched by the same rule everywhere in the system;\n * what differs is only which list is consulted.\n *\n * ⚠️ **A host must not put a wildcard in this list.** A `*` segment would make\n * fleet membership itself the capability, which is the distinction the\n * token/permission split exists to draw: the token proves the caller is the\n * fleet, the list says what the fleet may do.\n */\nexport const makeStaticServiceCrossingPolicy = (\n allowed: readonly Permission[],\n): ServiceCrossingPolicy => ({\n allows: required =>\n allowed.some(granted => permissionMatches(granted, required)),\n});\n\n/**\n * Refuses every crossing.\n *\n * The right default for an application that has none — an example app, a single\n * service, anything with no saga reaching across a tenant boundary. Supplied\n * explicitly rather than left as a missing Layer, because a missing Layer is a\n * compile error at the composition root and \"we do no crossings\" deserves to be\n * sayable.\n */\nexport const NoServiceCrossings: ServiceCrossingPolicy = {\n allows: () => false,\n};\n"],"names":["Context","permissionMatches","ServiceCrossingPolicyTag","Tag","makeStaticServiceCrossingPolicy","allowed","allows","required","some","granted","NoServiceCrossings"],"mappings":"AAAA,SAASA,OAAO,QAAQ,SAAS;AAEjC,SAA0BC,iBAAiB,QAAQ,0BAA0B;AAiB7E,mFAAmF,GACnF,OAAO,MAAMC,iCAAiCF,QAAQG,GAAG,CACvD;AACoD;AAEtD;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,kCAAkC,CAC7CC,UAC2B,CAAA;QAC3BC,QAAQC,CAAAA,WACNF,QAAQG,IAAI,CAACC,CAAAA,UAAWR,kBAAkBQ,SAASF;IACvD,CAAA,EAAG;AAEH;;;;;;;;CAQC,GACD,OAAO,MAAMG,qBAA4C;IACvDJ,QAAQ,IAAM;AAChB,EAAE"}
1
+ {"version":3,"sources":["../../src/policy/service-crossing-policy.ts"],"sourcesContent":["import { Context } from 'effect';\n\nimport { type Permission, permissionMatches } from '../values/permission.js';\n\n/**\n * What a **service** may do when it crosses into another party's tenant storage.\n *\n * ⚠️ **A second port rather than a second method on {@link PolicyDecision}, and\n * the separation is the security property.** `permissionsOf` expands grants by\n * looking a *role string* up in the grant table, so a crossing grant living\n * there would be inherited by any access token carrying the matching string —\n * turning a session claim into a cross-organization write. Two tables, two\n * lookups, and nothing a session guard calls can reach this one\n * ([ADR 0023](../../../../../../docs/adr/0023-service-to-service-tenant-crossing.md)).\n */\nexport interface ServiceCrossingPolicy {\n allows(required: Permission): boolean;\n}\n\n/** DI tag the composition root binds to a concrete {@link ServiceCrossingPolicy}. */\nexport class ServiceCrossingPolicyTag extends Context.Service<\n ServiceCrossingPolicyTag,\n ServiceCrossingPolicy\n>()('ServiceCrossingPolicyTag') {}\n\n/**\n * The allowlist implementation: a host hands over the whole of what its fleet\n * may do, in one list.\n *\n * The comparison goes through `permissionMatches` rather than `includes`, so a\n * required permission is matched by the same rule everywhere in the system;\n * what differs is only which list is consulted.\n *\n * ⚠️ **A host must not put a wildcard in this list.** A `*` segment would make\n * fleet membership itself the capability, which is the distinction the\n * token/permission split exists to draw: the token proves the caller is the\n * fleet, the list says what the fleet may do.\n */\nexport const makeStaticServiceCrossingPolicy = (\n allowed: readonly Permission[],\n): ServiceCrossingPolicy => ({\n allows: required =>\n allowed.some(granted => permissionMatches(granted, required)),\n});\n\n/**\n * Refuses every crossing.\n *\n * The right default for an application that has none — an example app, a single\n * service, anything with no saga reaching across a tenant boundary. Supplied\n * explicitly rather than left as a missing Layer, because a missing Layer is a\n * compile error at the composition root and \"we do no crossings\" deserves to be\n * sayable.\n */\nexport const NoServiceCrossings: ServiceCrossingPolicy = {\n allows: () => false,\n};\n"],"names":["Context","permissionMatches","ServiceCrossingPolicyTag","Service","makeStaticServiceCrossingPolicy","allowed","allows","required","some","granted","NoServiceCrossings"],"mappings":"AAAA,SAASA,OAAO,QAAQ,SAAS;AAEjC,SAA0BC,iBAAiB,QAAQ,0BAA0B;AAiB7E,mFAAmF,GACnF,OAAO,MAAMC,iCAAiCF,QAAQG,OAAO,GAGzD;AAA6B;AAEjC;;;;;;;;;;;;CAYC,GACD,OAAO,MAAMC,kCAAkC,CAC7CC,UAC2B,CAAA;QAC3BC,QAAQC,CAAAA,WACNF,QAAQG,IAAI,CAACC,CAAAA,UAAWR,kBAAkBQ,SAASF;IACvD,CAAA,EAAG;AAEH;;;;;;;;CAQC,GACD,OAAO,MAAMG,qBAA4C;IACvDJ,QAAQ,IAAM;AAChB,EAAE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/authz",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Authorization vocabulary: permissions derived from entities, roles, a grant table, and a PolicyDecision port.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -52,12 +52,12 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@swc/helpers": "0.5.23",
55
- "@entifix/core": "0.1.3"
55
+ "@entifix/core": "0.2.0"
56
56
  },
57
57
  "peerDependencies": {
58
- "effect": ">=3.22 <4"
58
+ "effect": "^4.0.5"
59
59
  },
60
60
  "devDependencies": {
61
- "effect": "3.22.1"
61
+ "effect": "4.0.5"
62
62
  }
63
63
  }