@gate-forge/pack-auth 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @gate-forge/pack-auth — Authorization discovery pack.
3
+ *
4
+ * Detects role-guard + tenant-isolation patterns in TypeScript / JavaScript
5
+ * HTTP route definitions and emits one `auth.resource` per guarded
6
+ * endpoint. Each resource carries `attributes.roleRequirement` (string[]
7
+ * of accepted roles) and `attributes.tenancy` (`'tenant-bound' | 'none'`),
8
+ * which the policy engine maps onto the auth obligation vocabulary.
9
+ *
10
+ * Vocabulary:
11
+ *
12
+ * - `auth:role-allowed` — the principal with the required role reaches
13
+ * the handler and produces the handler's success status.
14
+ * - `auth:role-denied` — the authenticated principal WITHOUT the
15
+ * required role is rejected with 401/403 BEFORE state mutation.
16
+ * - `auth:tenant-isolated` — a principal of a different tenant than
17
+ * the target entity is rejected (cross-tenant guard).
18
+ * - `auth:denied-no-side-effect` — a denied request leaves the
19
+ * persisted entity UNCHANGED (verified by a follow-up GET).
20
+ * - `auth:forged-token-rejected` — a token whose signature does not
21
+ * validate is rejected with 401 and never reaches the handler.
22
+ *
23
+ * Limitations (documented in the README):
24
+ *
25
+ * - Python/FastAPI `Depends()` patterns are NOT covered by this pack —
26
+ * the brief restricts the detector to TS/JS. The FastAPI equivalent
27
+ * is deferred to a future pack.
28
+ * - The detector does NOT cross-reference role identity providers; a
29
+ * `requireRole('admin')` call site is treated as authoritative for
30
+ * classification. Production users MUST classify role lists in the
31
+ * project config to bind them to a known taxonomy.
32
+ */
33
+ import { createAuthDetector } from './detector.js';
34
+ export { PACK_PLUGIN_ID, PACK_VERSION } from './version.js';
35
+ export { createAuthDetector, } from './detector.js';
36
+ export { AuthEntityAdapterSchema, validateAuthEntityAdapter, } from './adapter-schema.js';
37
+ export { AUTH_OBLIGATION_CONTRACTS, obligationId, } from './obligations.js';
38
+ /** The default CLI in-process plugin module: `{ discover(paths) }`. */
39
+ export default createAuthDetector();
40
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,kBAAkB,GAKnB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,uBAAuB,EACvB,yBAAyB,GAK1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,yBAAyB,EACzB,YAAY,GAEb,MAAM,kBAAkB,CAAC;AAE1B,uEAAuE;AACvE,eAAe,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The auth pack obligation vocabulary.
3
+ *
4
+ * Every obligation id is `<resourceId>:<contract>` where `<resourceId>` is
5
+ * the stable id the detector emits for one guarded endpoint
6
+ * (`auth.<method>.<path>`) and `<contract>` is one of the names below.
7
+ *
8
+ * Contract grammar: `auth:<verb>` where interior colons are NOT legal
9
+ * (these contracts do not carry their own namespace segment). Trailing
10
+ * colons are rejected by `ContractNameSchema` upstream.
11
+ *
12
+ * Vocabulary rationale (per the pack brief):
13
+ *
14
+ * - `auth:role-allowed` — A request with an authenticated principal whose
15
+ * role satisfies the endpoint's `roleRequirement` reaches the handler.
16
+ * Verified by issuing the request with a valid token carrying the
17
+ * required role; the handler MUST produce its declared success status
18
+ * (200 / 303 / 201).
19
+ *
20
+ * - `auth:role-denied` — A request whose principal is authenticated but
21
+ * LACKS the required role is rejected with 401/403 BEFORE any state
22
+ * mutation. Verified by issuing the same request with a role that does
23
+ * not satisfy `roleRequirement`; the response MUST be a denial status
24
+ * and MUST NOT mutate persisted state (this obligation AND
25
+ * `auth:denied-no-side-effect` together prove "deny is deny").
26
+ *
27
+ * - `auth:tenant-isolated` — A request whose tenant claim differs from
28
+ * the tenant bound to the target entity is rejected (the endpoint's
29
+ * `attributes.tenancy` is `tenant-bound`; the example server enforces
30
+ * `req.user.tenantId === target.tenantId`). Verified by replaying the
31
+ * admin request with the token of a different tenant; response is
32
+ * denial status, no read or write succeeds.
33
+ *
34
+ * - `auth:denied-no-side-effect` — A denied request produces no persisted
35
+ * side effect. Verified by: (a) asserting the denial status, then
36
+ * (b) issuing a GET on the resource — the entity is unchanged from the
37
+ * pre-denied snapshot. This is the "deny is deny, not pretend" check
38
+ * that catches "200 OK with empty body" fake-greens.
39
+ *
40
+ * - `auth:forged-token-rejected` — A request carrying a token whose
41
+ * signature does not validate (or whose payload fails verification)
42
+ * is rejected with 401 and never reaches the handler. Verified by
43
+ * mutating one byte of a valid token and asserting 401; the witness
44
+ * GET shows the entity is untouched.
45
+ */
46
+ export declare const AUTH_OBLIGATION_CONTRACTS: readonly ["auth:role-allowed", "auth:role-denied", "auth:tenant-isolated", "auth:denied-no-side-effect", "auth:forged-token-rejected"];
47
+ /** One auth obligation contract name. */
48
+ export type AuthObligationContract = (typeof AUTH_OBLIGATION_CONTRACTS)[number];
49
+ /**
50
+ * Builds the canonical `<resourceId>:<contract>` obligation id for one
51
+ * guarded endpoint. Stable across runs (no clock, no randomness).
52
+ *
53
+ * Args:
54
+ * resourceId: The detector-emitted resource id (`auth.<method>.<path>`).
55
+ * contract: One of {@link AUTH_OBLIGATION_CONTRACTS}.
56
+ *
57
+ * Returns:
58
+ * string: The `<resourceId>:<contract>` obligation id.
59
+ */
60
+ export declare function obligationId(resourceId: string, contract: AuthObligationContract): string;
61
+ //# sourceMappingURL=obligations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obligations.d.ts","sourceRoot":"","sources":["../src/obligations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,eAAO,MAAM,yBAAyB,wIAM5B,CAAC;AAEX,yCAAyC;AACzC,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,yBAAyB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhF;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAEzF"}
@@ -0,0 +1,67 @@
1
+ /**
2
+ * The auth pack obligation vocabulary.
3
+ *
4
+ * Every obligation id is `<resourceId>:<contract>` where `<resourceId>` is
5
+ * the stable id the detector emits for one guarded endpoint
6
+ * (`auth.<method>.<path>`) and `<contract>` is one of the names below.
7
+ *
8
+ * Contract grammar: `auth:<verb>` where interior colons are NOT legal
9
+ * (these contracts do not carry their own namespace segment). Trailing
10
+ * colons are rejected by `ContractNameSchema` upstream.
11
+ *
12
+ * Vocabulary rationale (per the pack brief):
13
+ *
14
+ * - `auth:role-allowed` — A request with an authenticated principal whose
15
+ * role satisfies the endpoint's `roleRequirement` reaches the handler.
16
+ * Verified by issuing the request with a valid token carrying the
17
+ * required role; the handler MUST produce its declared success status
18
+ * (200 / 303 / 201).
19
+ *
20
+ * - `auth:role-denied` — A request whose principal is authenticated but
21
+ * LACKS the required role is rejected with 401/403 BEFORE any state
22
+ * mutation. Verified by issuing the same request with a role that does
23
+ * not satisfy `roleRequirement`; the response MUST be a denial status
24
+ * and MUST NOT mutate persisted state (this obligation AND
25
+ * `auth:denied-no-side-effect` together prove "deny is deny").
26
+ *
27
+ * - `auth:tenant-isolated` — A request whose tenant claim differs from
28
+ * the tenant bound to the target entity is rejected (the endpoint's
29
+ * `attributes.tenancy` is `tenant-bound`; the example server enforces
30
+ * `req.user.tenantId === target.tenantId`). Verified by replaying the
31
+ * admin request with the token of a different tenant; response is
32
+ * denial status, no read or write succeeds.
33
+ *
34
+ * - `auth:denied-no-side-effect` — A denied request produces no persisted
35
+ * side effect. Verified by: (a) asserting the denial status, then
36
+ * (b) issuing a GET on the resource — the entity is unchanged from the
37
+ * pre-denied snapshot. This is the "deny is deny, not pretend" check
38
+ * that catches "200 OK with empty body" fake-greens.
39
+ *
40
+ * - `auth:forged-token-rejected` — A request carrying a token whose
41
+ * signature does not validate (or whose payload fails verification)
42
+ * is rejected with 401 and never reaches the handler. Verified by
43
+ * mutating one byte of a valid token and asserting 401; the witness
44
+ * GET shows the entity is untouched.
45
+ */
46
+ export const AUTH_OBLIGATION_CONTRACTS = [
47
+ 'auth:role-allowed',
48
+ 'auth:role-denied',
49
+ 'auth:tenant-isolated',
50
+ 'auth:denied-no-side-effect',
51
+ 'auth:forged-token-rejected',
52
+ ];
53
+ /**
54
+ * Builds the canonical `<resourceId>:<contract>` obligation id for one
55
+ * guarded endpoint. Stable across runs (no clock, no randomness).
56
+ *
57
+ * Args:
58
+ * resourceId: The detector-emitted resource id (`auth.<method>.<path>`).
59
+ * contract: One of {@link AUTH_OBLIGATION_CONTRACTS}.
60
+ *
61
+ * Returns:
62
+ * string: The `<resourceId>:<contract>` obligation id.
63
+ */
64
+ export function obligationId(resourceId, contract) {
65
+ return `${resourceId}:${contract}`;
66
+ }
67
+ //# sourceMappingURL=obligations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"obligations.js","sourceRoot":"","sources":["../src/obligations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,mBAAmB;IACnB,kBAAkB;IAClB,sBAAsB;IACtB,4BAA4B;IAC5B,4BAA4B;CACpB,CAAC;AAKX;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,UAAkB,EAAE,QAAgC;IAC/E,OAAO,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Pack identity constants. The GPP/3 handshake pins these values: the
3
+ * `.gateforge.yml` plugin entry MUST declare the same `id` and `version`.
4
+ */
5
+ /** Plugin id every auth pack contribution is pinned to. */
6
+ export declare const PACK_PLUGIN_ID = "gateforge.pack-auth";
7
+ /** Detector/pack version; bumped on schema-changing changes. */
8
+ export declare const PACK_VERSION = "0.1.0";
9
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2DAA2D;AAC3D,eAAO,MAAM,cAAc,wBAAwB,CAAC;AAEpD,gEAAgE;AAChE,eAAO,MAAM,YAAY,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Pack identity constants. The GPP/3 handshake pins these values: the
3
+ * `.gateforge.yml` plugin entry MUST declare the same `id` and `version`.
4
+ */
5
+ /** Plugin id every auth pack contribution is pinned to. */
6
+ export const PACK_PLUGIN_ID = 'gateforge.pack-auth';
7
+ /** Detector/pack version; bumped on schema-changing changes. */
8
+ export const PACK_VERSION = '0.1.0';
9
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,2DAA2D;AAC3D,MAAM,CAAC,MAAM,cAAc,GAAG,qBAAqB,CAAC;AAEpD,gEAAgE;AAChE,MAAM,CAAC,MAAM,YAAY,GAAG,OAAO,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Type declaration for the shipped sample adapter. The .mjs file is
3
+ * dynamically loaded by the witness; for static type checking in the
4
+ * test suite, we declare its default export here.
5
+ */
6
+ declare const sample: import('../src/adapter-schema.js').AuthEntityAdapter;
7
+ export default sample;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Sample entity adapter for the gateforge auth example app
3
+ * (`example/auth/`): the `example.billing.refund` resource, read via
4
+ * `GET /api/billing/refund/:id`.
5
+ *
6
+ * DOCUMENTATION + SAMPLE ONLY — actual adapter loading is the engine
7
+ * side (witness registry, ADR 0002): adapters live in the project's
8
+ * `.gateforge/adapters/` directory as `<resourceId>.mjs` and are
9
+ * executed ENGINE-SIDE with GET-only transport. Copy this file to
10
+ * `.gateforge/adapters/example.billing.refund.mjs` in a project that
11
+ * runs the example auth app and the witness service can prove
12
+ * `example.billing.refund` persistence evidence for the
13
+ * `auth:denied-no-side-effect` obligation.
14
+ *
15
+ * Adapter contract (interface pin #8; the pack's
16
+ * `AuthEntityAdapterSchema`):
17
+ * - `read(ctx, id)` — GET-only fetch of one entity; never mutates.
18
+ * - `normalize(body)` — project the raw body onto
19
+ * `{ entityId, fields }` (fields carry the classified primaryKey
20
+ * and the status / amount that prove "no side effect on deny").
21
+ * - `deletion: 'hard'` — the auth example does NOT soft-delete
22
+ * refunds; the only state change is the create itself.
23
+ * - `environmentFingerprint` — the target-environment marker the
24
+ * witness compares against a probe GET (GF-13): the example app
25
+ * serves `x-gateforge-env: auth-loopback-v1` on every route once
26
+ * wired for gateforge.
27
+ * - `resourceId` — registry identity; must match the file name.
28
+ */
29
+
30
+ /** Registry identity of the example billing-refund resource. */
31
+ const RESOURCE_ID = 'example.billing.refund';
32
+
33
+ /**
34
+ * The marker the witness expects on any probe/read response: the
35
+ * example app serves `x-gateforge-env: auth-loopback-v1` on every
36
+ * route.
37
+ */
38
+ const ENVIRONMENT_FINGERPRINT = 'auth-loopback-v1';
39
+
40
+ /**
41
+ * GET-only entity read against the example app's read API.
42
+ *
43
+ * Args:
44
+ * ctx: Witness-provided context (loopback base URL; optional headers).
45
+ * id: The entity id (`rfn-1`, ...).
46
+ *
47
+ * Returns:
48
+ * Promise<unknown>: The raw refund object
49
+ * `{ id, amount_cents, tenant_id, requested_by, status, created_at }`.
50
+ *
51
+ * Throws:
52
+ * Error: On a fingerprint mismatch (environment attestation) or a
53
+ * non-200/404 status. 404 surfaces as `null` — the entity is gone.
54
+ */
55
+ async function read(ctx, id) {
56
+ const response = await fetch(`${ctx.baseUrl}/api/billing/refund/${encodeURIComponent(id)}`, {
57
+ headers: ctx.headers ?? {},
58
+ });
59
+ if (response.status === 404) {
60
+ return null;
61
+ }
62
+ if (!response.ok) {
63
+ throw new Error(`example billing adapter: GET /api/billing/refund/${id} -> ${response.status}`);
64
+ }
65
+ const marker = response.headers.get('x-gateforge-env');
66
+ if (marker !== ENVIRONMENT_FINGERPRINT) {
67
+ throw new Error(
68
+ `example billing adapter: environment fingerprint mismatch ` +
69
+ `(expected ${ENVIRONMENT_FINGERPRINT}, got ${JSON.stringify(marker)})`,
70
+ );
71
+ }
72
+ return response.json();
73
+ }
74
+
75
+ /**
76
+ * Projects the raw refund body onto the evidence shape.
77
+ *
78
+ * Args:
79
+ * body: The raw refund object from `read`.
80
+ *
81
+ * Returns:
82
+ * { entityId, fields }: The entity id plus the fields the
83
+ * `auth:denied-no-side-effect` obligation compares against the
84
+ * pre-deny snapshot.
85
+ */
86
+ function normalize(body) {
87
+ return {
88
+ entityId: String(body.id),
89
+ fields: {
90
+ id: body.id,
91
+ amount_cents: body.amount_cents,
92
+ tenant_id: body.tenant_id,
93
+ status: body.status,
94
+ },
95
+ };
96
+ }
97
+
98
+ export default {
99
+ resourceId: RESOURCE_ID,
100
+ read,
101
+ normalize,
102
+ deletion: 'hard',
103
+ environmentFingerprint: ENVIRONMENT_FINGERPRINT,
104
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@gate-forge/pack-auth",
3
+ "version": "0.1.0",
4
+ "license": "Apache-2.0",
5
+ "description": "Authorization discovery pack: AST/heuristic detection of role guards, tenant scoping, and authentication enforcement in TypeScript/JavaScript HTTP routes; emits auth resource per guarded endpoint with roleRequirement + tenancy attributes; ships the auth obligation vocabulary (role-allowed/role-denied/tenant-isolated/denied-no-side-effect/forged-token-rejected).",
6
+ "type": "module",
7
+ "engines": {
8
+ "node": ">=20"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "README.md",
18
+ "dist",
19
+ "examples"
20
+ ],
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.build.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "vitest run"
25
+ },
26
+ "dependencies": {
27
+ "@gate-forge/core": "^0.1.0",
28
+ "@gate-forge/plugin-protocol": "^0.1.0",
29
+ "zod": "^4.1.5"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/umiddey/gateforge.git",
37
+ "directory": "packages/pack-auth"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/umiddey/gateforge/issues"
41
+ },
42
+ "homepage": "https://github.com/umiddey/gateforge#readme"
43
+ }