@iveri/contracts 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 (30) hide show
  1. package/README.md +8 -2
  2. package/dist/cjs/api/identity/access-token-payload.interface.d.ts +53 -0
  3. package/dist/cjs/api/identity/access-token-payload.interface.d.ts.map +1 -0
  4. package/dist/cjs/api/identity/access-token-payload.interface.js +3 -0
  5. package/dist/cjs/api/identity/access-token-payload.interface.js.map +1 -0
  6. package/dist/cjs/api/identity/auth.type.d.ts +21 -0
  7. package/dist/cjs/api/identity/auth.type.d.ts.map +1 -1
  8. package/dist/cjs/api/identity/index.d.ts +2 -0
  9. package/dist/cjs/api/identity/index.d.ts.map +1 -1
  10. package/dist/cjs/api/identity/index.js +2 -0
  11. package/dist/cjs/api/identity/index.js.map +1 -1
  12. package/dist/cjs/api/identity/principal-type.enum.d.ts +19 -0
  13. package/dist/cjs/api/identity/principal-type.enum.d.ts.map +1 -0
  14. package/dist/cjs/api/identity/principal-type.enum.js +23 -0
  15. package/dist/cjs/api/identity/principal-type.enum.js.map +1 -0
  16. package/dist/esm/api/identity/access-token-payload.interface.d.ts +53 -0
  17. package/dist/esm/api/identity/access-token-payload.interface.d.ts.map +1 -0
  18. package/dist/esm/api/identity/access-token-payload.interface.js +2 -0
  19. package/dist/esm/api/identity/access-token-payload.interface.js.map +1 -0
  20. package/dist/esm/api/identity/auth.type.d.ts +21 -0
  21. package/dist/esm/api/identity/auth.type.d.ts.map +1 -1
  22. package/dist/esm/api/identity/index.d.ts +2 -0
  23. package/dist/esm/api/identity/index.d.ts.map +1 -1
  24. package/dist/esm/api/identity/index.js +2 -0
  25. package/dist/esm/api/identity/index.js.map +1 -1
  26. package/dist/esm/api/identity/principal-type.enum.d.ts +19 -0
  27. package/dist/esm/api/identity/principal-type.enum.d.ts.map +1 -0
  28. package/dist/esm/api/identity/principal-type.enum.js +20 -0
  29. package/dist/esm/api/identity/principal-type.enum.js.map +1 -0
  30. package/package.json +1 -1
package/README.md CHANGED
@@ -69,8 +69,14 @@ catalogue and are already embedded in issued tokens and stored role rows.
69
69
  ### `api/identity/` → `@iveri/contracts/identity`
70
70
 
71
71
  `iveri-identity-api`'s authentication surface: `SignupBody`, `LoginBody`, `RefreshTokenBody`,
72
- `AcceptInvitationBody`, `AuthTokens`, `Principal`, `AuthSession`, plus `TenantStatus` and
73
- `MembershipStatus`.
72
+ `AcceptInvitationBody`, `AuthTokens`, `ServiceToken`, `Principal`, `AuthSession`, plus
73
+ `TenantStatus`, `MembershipStatus` and `PrincipalType`.
74
+
75
+ It also holds **`AccessTokenPayload`** — the claims inside the JWT itself, rather than a request
76
+ or response body. It belongs here for a stronger reason than the rest: identity is the only
77
+ service that _issues_ a token and every other service _verifies_ one, so both sides must agree
78
+ claim for claim. `conduit-api` kept a hand-copied duplicate until the `pt` claim had to be added
79
+ to both at once — the drift a copy invites, arriving exactly when it is most expensive.
74
80
 
75
81
  Auth only. Identity also exposes tenant, user, membership, role and API-key routes; those have
76
82
  one consumer each and stay in the service until a second one appears.
@@ -0,0 +1,53 @@
1
+ import type { UserPermission } from '../../enum/user-permission.enum.js';
2
+ import type { UUID } from '../../type/uuid.type.js';
3
+ import type { PrincipalType } from './principal-type.enum.js';
4
+ /**
5
+ * Claims carried by an access token.
6
+ *
7
+ * **`iveri-identity-api` issues these; every other service only verifies them.** The shape is
8
+ * therefore a contract owned by identity, and it lives here rather than in either repo because
9
+ * both sides have to agree byte for byte — a service whose idea of these claims has drifted
10
+ * does not fail loudly, it rejects every request or, worse, reads the wrong field and keeps
11
+ * going. `conduit-api` carried a hand-copied duplicate of this interface until the `pt` claim
12
+ * had to be added to both at once, which is exactly the drift the copy invited.
13
+ *
14
+ * Names are short and non-obvious (`tid`, `perms`) because this is JWT convention and because
15
+ * the token travels on every request — the registered claims are three characters for the same
16
+ * reason.
17
+ *
18
+ * **Permissions are embedded rather than resolved per request.** That is the whole point of a
19
+ * JWT here: any Iveri service verifies a token with the shared secret and never calls identity
20
+ * on the hot path. The cost is staleness — revoking a role does not invalidate access tokens
21
+ * already issued, so a removed permission survives up to identity's `JWT_ACCESS_TOKEN_TTL`
22
+ * (15 minutes by default), or `JWT_SERVICE_TOKEN_TTL` for a service token. Anything needing
23
+ * immediate revocation must check state at the point of use, not rely on the token.
24
+ */
25
+ export interface AccessTokenPayload {
26
+ /**
27
+ * Subject — **the principal**, which is a user id or an API key id depending on {@link pt}.
28
+ *
29
+ * Read it together with `pt` or not at all. Treating it as a user id unconditionally is the
30
+ * one mistake this shape is arranged to prevent.
31
+ */
32
+ sub: UUID;
33
+ /**
34
+ * Which kind of principal `sub` names.
35
+ *
36
+ * Required, so adding the service-token path could not silently leave a consumer reading a
37
+ * machine's `sub` as a person's id.
38
+ */
39
+ pt: PrincipalType;
40
+ /** Tenant the token is scoped to. Becomes `RequestContext.tenantId`; never trusted from a header. */
41
+ tid: UUID;
42
+ /** Permissions resolved from the principal's roles — or from the API key's grant — at issue time. */
43
+ perms: UserPermission[];
44
+ /** Issued-at, seconds since epoch. Set by the signer. */
45
+ iat?: number;
46
+ /** Expiry, seconds since epoch. Set by the signer. */
47
+ exp?: number;
48
+ /** Issuer, verified on every decode so a token from another Iveri environment is rejected. */
49
+ iss?: string;
50
+ /** Audience, verified alongside `iss`. */
51
+ aud?: string;
52
+ }
53
+ //# sourceMappingURL=access-token-payload.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-payload.interface.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/access-token-payload.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;OAKG;IACH,GAAG,EAAE,IAAI,CAAC;IAEV;;;;;OAKG;IACH,EAAE,EAAE,aAAa,CAAC;IAElB,qGAAqG;IACrG,GAAG,EAAE,IAAI,CAAC;IAEV,qGAAqG;IACrG,KAAK,EAAE,cAAc,EAAE,CAAC;IAExB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,8FAA8F;IAC9F,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=access-token-payload.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-payload.interface.js","sourceRoot":"","sources":["../../../../src/api/identity/access-token-payload.interface.ts"],"names":[],"mappings":""}
@@ -51,6 +51,27 @@ export interface AuthTokens {
51
51
  /** Lifetime of `accessToken` in seconds, not of `refreshToken`. */
52
52
  expiresInSeconds: number;
53
53
  }
54
+ /**
55
+ * Returned by `POST /auth/token` — the API-key-for-JWT exchange a service makes so that other
56
+ * Iveri services can authorize it **without calling identity**.
57
+ *
58
+ * **There is deliberately no refresh token.** The API key already is the long-lived credential,
59
+ * and it is revocable by a row. A refresh token here would be a second long-lived credential
60
+ * with its own independent lifetime, rotation rule and leak story, obtained from the first —
61
+ * so revoking the key would no longer be enough to stop the caller. A service re-exchanges
62
+ * instead, which costs one call per {@link expiresInSeconds}.
63
+ *
64
+ * The key travels as `x-api-key`, so this call has no request body.
65
+ */
66
+ export interface ServiceToken {
67
+ /** Send as `Authorization: Bearer <accessToken>`. Carries `pt: 'service'`. */
68
+ accessToken: string;
69
+ /**
70
+ * Lifetime in seconds. Shorter than a human session's: a service re-exchanges with nobody
71
+ * waiting, so the revocation window is cheap to shrink.
72
+ */
73
+ expiresInSeconds: number;
74
+ }
54
75
  /**
55
76
  * Who the caller is. Returned by `GET /auth/me` and nested inside {@link AuthSession}.
56
77
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.type.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/auth.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,0BAA0B;AAC1B,MAAM,WAAW,SAAS;IACtB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,iGAAiG;AACjG,MAAM,WAAW,oBAAoB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACtB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3B,QAAQ,EAAE,IAAI,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAE3B,qCAAqC;IACrC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,gEAAgE;IAChE,WAAW,EAAE,cAAc,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;CACtB"}
1
+ {"version":3,"file":"auth.type.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/auth.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,0BAA0B;AAC1B,MAAM,WAAW,SAAS;IACtB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,iGAAiG;AACjG,MAAM,WAAW,oBAAoB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IACzB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACtB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3B,QAAQ,EAAE,IAAI,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAE3B,qCAAqC;IACrC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,gEAAgE;IAChE,WAAW,EAAE,cAAc,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;CACtB"}
@@ -1,4 +1,6 @@
1
+ export * from './access-token-payload.interface.js';
1
2
  export * from './auth.type.js';
2
3
  export * from './membership-status.enum.js';
4
+ export * from './principal-type.enum.js';
3
5
  export * from './tenant-status.enum.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
@@ -14,7 +14,9 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./access-token-payload.interface.js"), exports);
17
18
  __exportStar(require("./auth.type.js"), exports);
18
19
  __exportStar(require("./membership-status.enum.js"), exports);
20
+ __exportStar(require("./principal-type.enum.js"), exports);
19
21
  __exportStar(require("./tenant-status.enum.js"), exports);
20
22
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAC/B,8DAA4C;AAC5C,0DAAwC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,sEAAoD;AACpD,iDAA+B;AAC/B,8DAA4C;AAC5C,2DAAyC;AACzC,0DAAwC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What kind of principal an access token was minted for.
3
+ *
4
+ * It exists because `sub` alone cannot answer the question. A JWT's subject is *the principal*,
5
+ * and identity issues tokens for two kinds of them: a person who logged in, and a service that
6
+ * exchanged an API key. Both put a UUID in `sub`, and the two UUIDs mean entirely different
7
+ * things — one names a row in `user`, the other a row in `api_key`.
8
+ *
9
+ * A consumer that reads `sub` as a user id without checking this claim attributes a machine's
10
+ * actions to a user who does not exist. That is why every guard branches on it, and why it is
11
+ * required rather than optional: `tsc` then makes the branch impossible to forget.
12
+ */
13
+ export declare enum PrincipalType {
14
+ /** A person, authenticated by password. `sub` is their `user.id`. */
15
+ USER = "user",
16
+ /** A service, authenticated by API key. `sub` is the `api_key.id`, and there is no user. */
17
+ SERVICE = "service"
18
+ }
19
+ //# sourceMappingURL=principal-type.enum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-type.enum.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/principal-type.enum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,oBAAY,aAAa;IACrB,qEAAqE;IACrE,IAAI,SAAS;IAEb,4FAA4F;IAC5F,OAAO,YAAY;CACtB"}
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrincipalType = void 0;
4
+ /**
5
+ * What kind of principal an access token was minted for.
6
+ *
7
+ * It exists because `sub` alone cannot answer the question. A JWT's subject is *the principal*,
8
+ * and identity issues tokens for two kinds of them: a person who logged in, and a service that
9
+ * exchanged an API key. Both put a UUID in `sub`, and the two UUIDs mean entirely different
10
+ * things — one names a row in `user`, the other a row in `api_key`.
11
+ *
12
+ * A consumer that reads `sub` as a user id without checking this claim attributes a machine's
13
+ * actions to a user who does not exist. That is why every guard branches on it, and why it is
14
+ * required rather than optional: `tsc` then makes the branch impossible to forget.
15
+ */
16
+ var PrincipalType;
17
+ (function (PrincipalType) {
18
+ /** A person, authenticated by password. `sub` is their `user.id`. */
19
+ PrincipalType["USER"] = "user";
20
+ /** A service, authenticated by API key. `sub` is the `api_key.id`, and there is no user. */
21
+ PrincipalType["SERVICE"] = "service";
22
+ })(PrincipalType || (exports.PrincipalType = PrincipalType = {}));
23
+ //# sourceMappingURL=principal-type.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-type.enum.js","sourceRoot":"","sources":["../../../../src/api/identity/principal-type.enum.ts"],"names":[],"mappings":";;;AAAA;;;;;;;;;;;GAWG;AACH,IAAY,aAMX;AAND,WAAY,aAAa;IACrB,qEAAqE;IACrE,8BAAa,CAAA;IAEb,4FAA4F;IAC5F,oCAAmB,CAAA;AACvB,CAAC,EANW,aAAa,6BAAb,aAAa,QAMxB"}
@@ -0,0 +1,53 @@
1
+ import type { UserPermission } from '../../enum/user-permission.enum.js';
2
+ import type { UUID } from '../../type/uuid.type.js';
3
+ import type { PrincipalType } from './principal-type.enum.js';
4
+ /**
5
+ * Claims carried by an access token.
6
+ *
7
+ * **`iveri-identity-api` issues these; every other service only verifies them.** The shape is
8
+ * therefore a contract owned by identity, and it lives here rather than in either repo because
9
+ * both sides have to agree byte for byte — a service whose idea of these claims has drifted
10
+ * does not fail loudly, it rejects every request or, worse, reads the wrong field and keeps
11
+ * going. `conduit-api` carried a hand-copied duplicate of this interface until the `pt` claim
12
+ * had to be added to both at once, which is exactly the drift the copy invited.
13
+ *
14
+ * Names are short and non-obvious (`tid`, `perms`) because this is JWT convention and because
15
+ * the token travels on every request — the registered claims are three characters for the same
16
+ * reason.
17
+ *
18
+ * **Permissions are embedded rather than resolved per request.** That is the whole point of a
19
+ * JWT here: any Iveri service verifies a token with the shared secret and never calls identity
20
+ * on the hot path. The cost is staleness — revoking a role does not invalidate access tokens
21
+ * already issued, so a removed permission survives up to identity's `JWT_ACCESS_TOKEN_TTL`
22
+ * (15 minutes by default), or `JWT_SERVICE_TOKEN_TTL` for a service token. Anything needing
23
+ * immediate revocation must check state at the point of use, not rely on the token.
24
+ */
25
+ export interface AccessTokenPayload {
26
+ /**
27
+ * Subject — **the principal**, which is a user id or an API key id depending on {@link pt}.
28
+ *
29
+ * Read it together with `pt` or not at all. Treating it as a user id unconditionally is the
30
+ * one mistake this shape is arranged to prevent.
31
+ */
32
+ sub: UUID;
33
+ /**
34
+ * Which kind of principal `sub` names.
35
+ *
36
+ * Required, so adding the service-token path could not silently leave a consumer reading a
37
+ * machine's `sub` as a person's id.
38
+ */
39
+ pt: PrincipalType;
40
+ /** Tenant the token is scoped to. Becomes `RequestContext.tenantId`; never trusted from a header. */
41
+ tid: UUID;
42
+ /** Permissions resolved from the principal's roles — or from the API key's grant — at issue time. */
43
+ perms: UserPermission[];
44
+ /** Issued-at, seconds since epoch. Set by the signer. */
45
+ iat?: number;
46
+ /** Expiry, seconds since epoch. Set by the signer. */
47
+ exp?: number;
48
+ /** Issuer, verified on every decode so a token from another Iveri environment is rejected. */
49
+ iss?: string;
50
+ /** Audience, verified alongside `iss`. */
51
+ aud?: string;
52
+ }
53
+ //# sourceMappingURL=access-token-payload.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-payload.interface.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/access-token-payload.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,kBAAkB;IAC/B;;;;;OAKG;IACH,GAAG,EAAE,IAAI,CAAC;IAEV;;;;;OAKG;IACH,EAAE,EAAE,aAAa,CAAC;IAElB,qGAAqG;IACrG,GAAG,EAAE,IAAI,CAAC;IAEV,qGAAqG;IACrG,KAAK,EAAE,cAAc,EAAE,CAAC;IAExB,yDAAyD;IACzD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,8FAA8F;IAC9F,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,0CAA0C;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;CAChB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=access-token-payload.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access-token-payload.interface.js","sourceRoot":"","sources":["../../../../src/api/identity/access-token-payload.interface.ts"],"names":[],"mappings":""}
@@ -51,6 +51,27 @@ export interface AuthTokens {
51
51
  /** Lifetime of `accessToken` in seconds, not of `refreshToken`. */
52
52
  expiresInSeconds: number;
53
53
  }
54
+ /**
55
+ * Returned by `POST /auth/token` — the API-key-for-JWT exchange a service makes so that other
56
+ * Iveri services can authorize it **without calling identity**.
57
+ *
58
+ * **There is deliberately no refresh token.** The API key already is the long-lived credential,
59
+ * and it is revocable by a row. A refresh token here would be a second long-lived credential
60
+ * with its own independent lifetime, rotation rule and leak story, obtained from the first —
61
+ * so revoking the key would no longer be enough to stop the caller. A service re-exchanges
62
+ * instead, which costs one call per {@link expiresInSeconds}.
63
+ *
64
+ * The key travels as `x-api-key`, so this call has no request body.
65
+ */
66
+ export interface ServiceToken {
67
+ /** Send as `Authorization: Bearer <accessToken>`. Carries `pt: 'service'`. */
68
+ accessToken: string;
69
+ /**
70
+ * Lifetime in seconds. Shorter than a human session's: a service re-exchanges with nobody
71
+ * waiting, so the revocation window is cheap to shrink.
72
+ */
73
+ expiresInSeconds: number;
74
+ }
54
75
  /**
55
76
  * Who the caller is. Returned by `GET /auth/me` and nested inside {@link AuthSession}.
56
77
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.type.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/auth.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,0BAA0B;AAC1B,MAAM,WAAW,SAAS;IACtB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,iGAAiG;AACjG,MAAM,WAAW,oBAAoB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACtB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3B,QAAQ,EAAE,IAAI,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAE3B,qCAAqC;IACrC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,gEAAgE;IAChE,WAAW,EAAE,cAAc,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;CACtB"}
1
+ {"version":3,"file":"auth.type.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/auth.type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAE5D,8EAA8E;AAC9E,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,0BAA0B;AAC1B,MAAM,WAAW,SAAS;IACtB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;IAEnB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,oDAAoD;AACpD,MAAM,WAAW,gBAAgB;IAC7B,YAAY,EAAE,MAAM,CAAC;CACxB;AAED,iGAAiG;AACjG,MAAM,WAAW,oBAAoB;IACjC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACvB,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAC;IAEpB;;;;;;;;OAQG;IACH,YAAY,EAAE,MAAM,CAAC;IAErB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,YAAY;IACzB,8EAA8E;IAC9E,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,gBAAgB,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,SAAS;IACtB,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IACxB,SAAS,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5B,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE3B,QAAQ,EAAE,IAAI,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,YAAY,CAAC;IAE3B,qCAAqC;IACrC,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;IAE7C,SAAS,EAAE,MAAM,EAAE,CAAC;IAEpB,gEAAgE;IAChE,WAAW,EAAE,cAAc,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,SAAS,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;CACtB"}
@@ -1,4 +1,6 @@
1
+ export * from './access-token-payload.interface.js';
1
2
  export * from './auth.type.js';
2
3
  export * from './membership-status.enum.js';
4
+ export * from './principal-type.enum.js';
3
5
  export * from './tenant-status.enum.js';
4
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
@@ -1,4 +1,6 @@
1
+ export * from './access-token-payload.interface.js';
1
2
  export * from './auth.type.js';
2
3
  export * from './membership-status.enum.js';
4
+ export * from './principal-type.enum.js';
3
5
  export * from './tenant-status.enum.js';
4
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/api/identity/index.ts"],"names":[],"mappings":"AAAA,cAAc,qCAAqC,CAAC;AACpD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What kind of principal an access token was minted for.
3
+ *
4
+ * It exists because `sub` alone cannot answer the question. A JWT's subject is *the principal*,
5
+ * and identity issues tokens for two kinds of them: a person who logged in, and a service that
6
+ * exchanged an API key. Both put a UUID in `sub`, and the two UUIDs mean entirely different
7
+ * things — one names a row in `user`, the other a row in `api_key`.
8
+ *
9
+ * A consumer that reads `sub` as a user id without checking this claim attributes a machine's
10
+ * actions to a user who does not exist. That is why every guard branches on it, and why it is
11
+ * required rather than optional: `tsc` then makes the branch impossible to forget.
12
+ */
13
+ export declare enum PrincipalType {
14
+ /** A person, authenticated by password. `sub` is their `user.id`. */
15
+ USER = "user",
16
+ /** A service, authenticated by API key. `sub` is the `api_key.id`, and there is no user. */
17
+ SERVICE = "service"
18
+ }
19
+ //# sourceMappingURL=principal-type.enum.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-type.enum.d.ts","sourceRoot":"","sources":["../../../../src/api/identity/principal-type.enum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,oBAAY,aAAa;IACrB,qEAAqE;IACrE,IAAI,SAAS;IAEb,4FAA4F;IAC5F,OAAO,YAAY;CACtB"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * What kind of principal an access token was minted for.
3
+ *
4
+ * It exists because `sub` alone cannot answer the question. A JWT's subject is *the principal*,
5
+ * and identity issues tokens for two kinds of them: a person who logged in, and a service that
6
+ * exchanged an API key. Both put a UUID in `sub`, and the two UUIDs mean entirely different
7
+ * things — one names a row in `user`, the other a row in `api_key`.
8
+ *
9
+ * A consumer that reads `sub` as a user id without checking this claim attributes a machine's
10
+ * actions to a user who does not exist. That is why every guard branches on it, and why it is
11
+ * required rather than optional: `tsc` then makes the branch impossible to forget.
12
+ */
13
+ export var PrincipalType;
14
+ (function (PrincipalType) {
15
+ /** A person, authenticated by password. `sub` is their `user.id`. */
16
+ PrincipalType["USER"] = "user";
17
+ /** A service, authenticated by API key. `sub` is the `api_key.id`, and there is no user. */
18
+ PrincipalType["SERVICE"] = "service";
19
+ })(PrincipalType || (PrincipalType = {}));
20
+ //# sourceMappingURL=principal-type.enum.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-type.enum.js","sourceRoot":"","sources":["../../../../src/api/identity/principal-type.enum.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,CAAN,IAAY,aAMX;AAND,WAAY,aAAa;IACrB,qEAAqE;IACrE,8BAAa,CAAA;IAEb,4FAA4F;IAC5F,oCAAmB,CAAA;AACvB,CAAC,EANW,aAAa,KAAb,aAAa,QAMxB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iveri/contracts",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Wire contracts shared by every Iveri backend and frontend. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "repository": {