@owlmeans/server-auth-token 0.1.18-rc.31 → 0.1.18-rc.32

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 (74) hide show
  1. package/README.md +1 -1
  2. package/agent-meta/manifest.json +2 -2
  3. package/agent-meta/skills/server-auth-token/SKILL.md +6 -6
  4. package/build/guard.d.ts +19 -3
  5. package/build/guard.d.ts.map +1 -1
  6. package/build/guard.js +19 -20
  7. package/build/guard.js.map +1 -1
  8. package/build/handlers/create.d.ts +5 -0
  9. package/build/handlers/create.d.ts.map +1 -0
  10. package/build/handlers/create.js +18 -0
  11. package/build/handlers/create.js.map +1 -0
  12. package/build/handlers/index.d.ts +8 -35
  13. package/build/handlers/index.d.ts.map +1 -1
  14. package/build/handlers/index.js +8 -109
  15. package/build/handlers/index.js.map +1 -1
  16. package/build/handlers/issue.d.ts +6 -0
  17. package/build/handlers/issue.d.ts.map +1 -0
  18. package/build/handlers/issue.js +42 -0
  19. package/build/handlers/issue.js.map +1 -0
  20. package/build/handlers/list.d.ts +3 -0
  21. package/build/handlers/list.d.ts.map +1 -0
  22. package/build/handlers/list.js +18 -0
  23. package/build/handlers/list.js.map +1 -0
  24. package/build/handlers/refuse.d.ts +19 -0
  25. package/build/handlers/refuse.d.ts.map +1 -0
  26. package/build/handlers/refuse.js +20 -0
  27. package/build/handlers/refuse.js.map +1 -0
  28. package/build/handlers/revoke.d.ts +7 -0
  29. package/build/handlers/revoke.d.ts.map +1 -0
  30. package/build/handlers/revoke.js +25 -0
  31. package/build/handlers/revoke.js.map +1 -0
  32. package/build/handlers/types.d.ts +11 -1
  33. package/build/handlers/types.d.ts.map +1 -1
  34. package/build/handlers/utils/types.d.ts +10 -0
  35. package/build/handlers/utils/types.d.ts.map +1 -0
  36. package/build/handlers/utils/types.js +2 -0
  37. package/build/handlers/utils/types.js.map +1 -0
  38. package/build/handlers/utils.d.ts +4 -0
  39. package/build/handlers/utils.d.ts.map +1 -0
  40. package/build/handlers/utils.js +11 -0
  41. package/build/handlers/utils.js.map +1 -0
  42. package/build/hash/types.d.ts +16 -0
  43. package/build/hash/types.d.ts.map +1 -0
  44. package/build/hash/types.js +2 -0
  45. package/build/hash/types.js.map +1 -0
  46. package/build/hash.d.ts +4 -11
  47. package/build/hash.d.ts.map +1 -1
  48. package/build/hash.js +11 -14
  49. package/build/hash.js.map +1 -1
  50. package/build/index.d.ts +2 -0
  51. package/build/index.d.ts.map +1 -1
  52. package/build/index.js +1 -0
  53. package/build/index.js.map +1 -1
  54. package/build/prefix.d.ts +4 -0
  55. package/build/prefix.d.ts.map +1 -0
  56. package/build/prefix.js +4 -0
  57. package/build/prefix.js.map +1 -0
  58. package/package.json +12 -12
  59. package/src/guard.ts +21 -24
  60. package/src/handlers/create.ts +21 -0
  61. package/src/handlers/index.ts +11 -125
  62. package/src/handlers/issue.ts +50 -0
  63. package/src/handlers/list.ts +21 -0
  64. package/src/handlers/refuse.ts +20 -0
  65. package/src/handlers/revoke.ts +30 -0
  66. package/src/handlers/types.ts +12 -1
  67. package/src/handlers/utils/types.ts +10 -0
  68. package/src/handlers/utils.ts +18 -0
  69. package/src/hash/types.ts +16 -0
  70. package/src/hash.ts +15 -14
  71. package/src/index.ts +2 -0
  72. package/src/prefix.ts +6 -0
  73. package/tests/guard.spec.ts +14 -14
  74. package/tests/handlers.spec.ts +13 -13
@@ -0,0 +1 @@
1
+ {"version":3,"file":"revoke.js","sourceRoot":"","sources":["../../src/handlers/revoke.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAE/C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAE7C,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,QAAwC,EAAE,EAAE,CAAC,QAAQ,EAAoB,CAAC,MAAM,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE;IAC3J,MAAM,GAAG,GAAG,OAA2B,CAAA;IACvC,eAAe,CAAC,GAAG,EAAE,cAAc,CAAC,CAAA;IAEpC,MAAM,QAAQ,GAAG,eAAe,CAAC,GAAG,CAAC,CAAC,gBAAgB,EAAE,CAAA;IACxD,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,EAAE,SAAS,CAAA;IACrC,IAAI,SAAS,IAAI,IAAI;QAAE,MAAM,IAAI,aAAa,CAAC,SAAS,CAAC,CAAA;IAEzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;IAClE,6FAA6F;IAC7F,8DAA8D;IAC9D,IAAI,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACrF,MAAM,IAAI,aAAa,CAAC,OAAO,CAAC,CAAA;IAClC,CAAC;IAED,yDAAyD;IACzD,IAAI,MAAM,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;QAC7B,MAAM,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,EAAE,CAAC,CAAA;IACtG,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,CAAA;AAC3B,CAAC,CAAC,CAAA"}
@@ -1,5 +1,5 @@
1
1
  import { AuthRole } from '@owlmeans/auth';
2
- import type { CreateAccessToken } from '@owlmeans/auth-token';
2
+ import type { CreateAccessToken, IssuedAccessToken } from '@owlmeans/auth-token';
3
3
  /** Whoever is minting the token — read once, by whichever caller resolved the session. */
4
4
  export interface IssueAccessTokenSubject {
5
5
  entityId: string;
@@ -16,4 +16,14 @@ export interface IssueAccessTokenRequest extends CreateAccessToken {
16
16
  */
17
17
  audience?: string[];
18
18
  }
19
+ /** Token issuance over one context's store — the one path every minting caller goes through. */
20
+ export interface AccessTokenIssuer {
21
+ /**
22
+ * Mint one token for `subject`, exactly what `createAccessToken` does over an HTTP body — pulled
23
+ * out so a second caller (an OAuth token endpoint) can mint the SAME shape of record over a
24
+ * session it authenticated its own way, without a duplicate copy of the narrowing rule or the
25
+ * TTL clamp silently drifting from this one.
26
+ */
27
+ issueAccessToken: (subject: IssueAccessTokenSubject, payload: IssueAccessTokenRequest) => Promise<IssuedAccessToken>;
28
+ }
19
29
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/handlers/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAE7D,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;IACd,iFAAiF;IACjF,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/handlers/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAEhF,0FAA0F;AAC1F,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,QAAQ,CAAA;IACd,iFAAiF;IACjF,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED,MAAM,WAAW,uBAAwB,SAAQ,iBAAiB;IAChE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAED,gGAAgG;AAChG,MAAM,WAAW,iBAAiB;IAChC;;;;;OAKG;IACH,gBAAgB,EAAE,CAAC,OAAO,EAAE,uBAAuB,EAAE,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAA;CACrH"}
@@ -0,0 +1,10 @@
1
+ import type { AccessTokenRecord, AccessTokenView } from '@owlmeans/auth-token';
2
+ import type { AccessTokenResource, AuthTokenContext } from '../../types.js';
3
+ /** What every token handler shares: the store and the view a caller may see. */
4
+ export interface TokenRecordUtils {
5
+ /** A record without its hash — the hash never leaves the server. */
6
+ view: (record: AccessTokenRecord) => AccessTokenView;
7
+ /** The token store of a context. */
8
+ tokens: (ctx: AuthTokenContext) => AccessTokenResource;
9
+ }
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/handlers/utils/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAE3E,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,CAAC,MAAM,EAAE,iBAAiB,KAAK,eAAe,CAAA;IACpD,oCAAoC;IACpC,MAAM,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,mBAAmB,CAAA;CACvD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/handlers/utils/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,4 @@
1
+ import type { TokenRecordUtils } from './utils/types.js';
2
+ export declare const createTokenRecordUtils: () => TokenRecordUtils;
3
+ export declare const tokenRecordUtils: TokenRecordUtils;
4
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/handlers/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAExD,eAAO,MAAM,sBAAsB,QAAO,gBAWzC,CAAA;AAED,eAAO,MAAM,gBAAgB,kBAA2B,CAAA"}
@@ -0,0 +1,11 @@
1
+ import { AUTH_TOKEN_RESOURCE } from '@owlmeans/auth-token';
2
+ export const createTokenRecordUtils = () => {
3
+ const view = (record) => {
4
+ const { hash, ...rest } = record;
5
+ return rest;
6
+ };
7
+ const tokens = (ctx) => ctx.resource(AUTH_TOKEN_RESOURCE);
8
+ return { view, tokens };
9
+ };
10
+ export const tokenRecordUtils = createTokenRecordUtils();
11
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/handlers/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAgD,MAAM,sBAAsB,CAAA;AAIxG,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAqB,EAAE;IAC3D,MAAM,IAAI,GAAG,CAAC,MAAyB,EAAmB,EAAE;QAC1D,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,GAAG,MAAM,CAAA;QAEhC,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;IAED,MAAM,MAAM,GAAG,CAAC,GAAqB,EAAuB,EAAE,CAC5D,GAAG,CAAC,QAAQ,CAAsB,mBAAmB,CAAC,CAAA;IAExD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACzB,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAG,sBAAsB,EAAE,CAAA"}
@@ -0,0 +1,16 @@
1
+ import type { MintedToken } from '../types.js';
2
+ /** Access tokens minted and stored: the plaintext once, the hash forever. */
3
+ export interface TokenHashHelper {
4
+ /**
5
+ * The stored form of a token.
6
+ *
7
+ * SHA-256 rather than a password hash on purpose: the input is 192 bits of machine-generated
8
+ * randomness, so there is no dictionary to slow down, and the hash is computed on every single
9
+ * API request — a deliberately slow function here would be a deliberate rate limit on the
10
+ * deployment's whole authenticated surface.
11
+ */
12
+ hashAccessToken: (token: string) => string;
13
+ /** Mint one token: the plaintext (returned once), its hash (stored) and its display form. */
14
+ mintAccessToken: (prefix: string) => MintedToken;
15
+ }
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/hash/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAC1C,6FAA6F;IAC7F,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,WAAW,CAAA;CACjD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/hash/types.ts"],"names":[],"mappings":""}
package/build/hash.d.ts CHANGED
@@ -1,13 +1,6 @@
1
- import type { MintedToken } from './types.js';
2
- /**
3
- * The stored form of a token.
4
- *
5
- * SHA-256 rather than a password hash on purpose: the input is 192 bits of machine-generated
6
- * randomness, so there is no dictionary to slow down, and the hash is computed on every single
7
- * API request — a deliberately slow function here would be a deliberate rate limit on the
8
- * deployment's whole authenticated surface.
9
- */
1
+ import type { TokenHashHelper } from './hash/types.js';
2
+ export declare const createTokenHashHelper: () => TokenHashHelper;
3
+ export declare const tokenHashHelper: TokenHashHelper;
4
+ /** @deprecated compat:factory-refactor — use `tokenHashHelper.hashAccessToken(…)` */
10
5
  export declare const hashAccessToken: (token: string) => string;
11
- /** Mint one token: the plaintext (returned once), its hash (stored) and its display form. */
12
- export declare const mintAccessToken: (prefix: string) => MintedToken;
13
6
  //# sourceMappingURL=hash.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;;GAOG;AACH,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,MAA6D,CAAA;AAE7G,6FAA6F;AAC7F,eAAO,MAAM,eAAe,WAAY,MAAM,KAAG,WAIhD,CAAA"}
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAEtD,eAAO,MAAM,qBAAqB,QAAO,eAUxC,CAAA;AAED,eAAO,MAAM,eAAe,iBAA0B,CAAA;AAEtD,qFAAqF;AACrF,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,MAAgD,CAAA"}
package/build/hash.js CHANGED
@@ -1,19 +1,16 @@
1
- import { AUTH_TOKEN_SECRET_BYTES, displayOf } from '@owlmeans/auth-token';
1
+ import { AUTH_TOKEN_SECRET_BYTES, tokenFormatHelper } from '@owlmeans/auth-token';
2
2
  import { base58 } from '@scure/base';
3
3
  import { sha256 } from '@noble/hashes/sha2.js';
4
4
  import { bytesToHex, randomBytes } from '@noble/hashes/utils.js';
5
- /**
6
- * The stored form of a token.
7
- *
8
- * SHA-256 rather than a password hash on purpose: the input is 192 bits of machine-generated
9
- * randomness, so there is no dictionary to slow down, and the hash is computed on every single
10
- * API request — a deliberately slow function here would be a deliberate rate limit on the
11
- * deployment's whole authenticated surface.
12
- */
13
- export const hashAccessToken = (token) => bytesToHex(sha256(new TextEncoder().encode(token)));
14
- /** Mint one token: the plaintext (returned once), its hash (stored) and its display form. */
15
- export const mintAccessToken = (prefix) => {
16
- const token = `${prefix}${base58.encode(randomBytes(AUTH_TOKEN_SECRET_BYTES))}`;
17
- return { token, hash: hashAccessToken(token), display: displayOf(token, prefix) };
5
+ export const createTokenHashHelper = () => {
6
+ const hashAccessToken = (token) => bytesToHex(sha256(new TextEncoder().encode(token)));
7
+ const mintAccessToken = (prefix) => {
8
+ const token = `${prefix}${base58.encode(randomBytes(AUTH_TOKEN_SECRET_BYTES))}`;
9
+ return { token, hash: hashAccessToken(token), display: tokenFormatHelper.displayOf(token, prefix) };
10
+ };
11
+ return { hashAccessToken, mintAccessToken };
18
12
  };
13
+ export const tokenHashHelper = createTokenHashHelper();
14
+ /** @deprecated compat:factory-refactor — use `tokenHashHelper.hashAccessToken(…)` */
15
+ export const hashAccessToken = (token) => tokenHashHelper.hashAccessToken(token);
19
16
  //# sourceMappingURL=hash.js.map
package/build/hash.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AACzE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGhE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAE7G,6FAA6F;AAC7F,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAc,EAAe,EAAE;IAC7D,MAAM,KAAK,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAA;IAE/E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAA;AACnF,CAAC,CAAA"}
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AACjF,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAIhE,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAoB,EAAE;IACzD,MAAM,eAAe,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IAEtG,MAAM,eAAe,GAAG,CAAC,MAAc,EAAe,EAAE;QACtD,MAAM,KAAK,GAAG,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,uBAAuB,CAAC,CAAC,EAAE,CAAA;QAE/E,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAA;IACrG,CAAC,CAAA;IAED,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,CAAA;AAC7C,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,eAAe,GAAG,qBAAqB,EAAE,CAAA;AAEtD,qFAAqF;AACrF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,eAAe,CAAC,eAAe,CAAC,KAAK,CAAC,CAAA"}
package/build/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './consts.js';
2
2
  export * from './resource.js';
3
+ export * from './prefix.js';
3
4
  export * from './guard.js';
4
5
  export * from './hash.js';
5
6
  export * from './coguard.js';
@@ -7,4 +8,5 @@ export * from './append.js';
7
8
  export * from './handlers/index.js';
8
9
  export type * from './handlers/types.js';
9
10
  export type * from './types.js';
11
+ export type * from './hash/types.js';
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,mBAAmB,qBAAqB,CAAA;AACxC,mBAAmB,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA;AACnC,mBAAmB,qBAAqB,CAAA;AACxC,mBAAmB,YAAY,CAAA;AAC/B,mBAAmB,iBAAiB,CAAA"}
package/build/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './consts.js';
2
2
  export * from './resource.js';
3
+ export * from './prefix.js';
3
4
  export * from './guard.js';
4
5
  export * from './hash.js';
5
6
  export * from './coguard.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,aAAa,CAAA;AAC3B,cAAc,qBAAqB,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { AuthTokenContext, AuthTokenGuardOptions } from './types.js';
2
+ /** Where the deployment's prefix comes from: the guard's own options, then config, then default. */
3
+ export declare const prefixOf: (context: AuthTokenContext, opts?: AuthTokenGuardOptions) => string;
4
+ //# sourceMappingURL=prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefix.d.ts","sourceRoot":"","sources":["../src/prefix.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAE1F,oGAAoG;AACpG,eAAO,MAAM,QAAQ,YAAa,gBAAgB,SAAS,qBAAqB,KAAG,MACc,CAAA"}
@@ -0,0 +1,4 @@
1
+ import { AUTH_TOKEN_DEFAULT_PREFIX } from '@owlmeans/auth-token';
2
+ /** Where the deployment's prefix comes from: the guard's own options, then config, then default. */
3
+ export const prefixOf = (context, opts) => opts?.prefix ?? context.cfg.authToken?.prefix ?? AUTH_TOKEN_DEFAULT_PREFIX;
4
+ //# sourceMappingURL=prefix.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefix.js","sourceRoot":"","sources":["../src/prefix.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAA;AAGhE,oGAAoG;AACpG,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAyB,EAAE,IAA4B,EAAU,EAAE,CAC1F,IAAI,EAAE,MAAM,IAAK,OAAO,CAAC,GAAuB,CAAC,SAAS,EAAE,MAAM,IAAI,yBAAyB,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@owlmeans/server-auth-token",
3
- "version": "0.1.18-rc.31",
3
+ "version": "0.1.18-rc.32",
4
4
  "license": "MIT",
5
5
  "description": "The server half of OwlMeans access tokens — the store, the guard that verifies a presented token, the management handlers and the coguard that admits one on an existing route.",
6
6
  "type": "module",
@@ -24,17 +24,17 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@noble/hashes": "^2.4.0",
27
- "@owlmeans/auth": "^0.1.18-rc.39",
28
- "@owlmeans/auth-common": "^0.1.18-rc.46",
29
- "@owlmeans/auth-token": "^0.1.18-rc.31",
30
- "@owlmeans/context": "^0.1.18-rc.33",
31
- "@owlmeans/entrypoint": "^0.1.18-rc.40",
32
- "@owlmeans/log": "^0.1.18-rc.1",
33
- "@owlmeans/mongo-resource": "^0.1.18-rc.42",
34
- "@owlmeans/resource": "^0.1.18-rc.39",
35
- "@owlmeans/server-api": "^0.1.18-rc.50",
36
- "@owlmeans/server-auth-identity": "^0.1.18-rc.46",
37
- "@owlmeans/server-context": "^0.1.18-rc.42",
27
+ "@owlmeans/auth": "^0.1.18-rc.40",
28
+ "@owlmeans/auth-common": "^0.1.18-rc.47",
29
+ "@owlmeans/auth-token": "^0.1.18-rc.32",
30
+ "@owlmeans/context": "^0.1.18-rc.34",
31
+ "@owlmeans/entrypoint": "^0.1.18-rc.41",
32
+ "@owlmeans/log": "^0.1.18-rc.2",
33
+ "@owlmeans/mongo-resource": "^0.1.18-rc.43",
34
+ "@owlmeans/resource": "^0.1.18-rc.40",
35
+ "@owlmeans/server-api": "^0.1.18-rc.51",
36
+ "@owlmeans/server-auth-identity": "^0.1.18-rc.47",
37
+ "@owlmeans/server-context": "^0.1.18-rc.43",
38
38
  "@scure/base": "^2.4.0"
39
39
  },
40
40
  "devDependencies": {
package/src/guard.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import type { Auth } from '@owlmeans/auth'
2
2
  import { ALL_SCOPES, AuthroizationType } from '@owlmeans/auth'
3
3
  import {
4
- AUTH_TOKEN_DEFAULT_PREFIX, AUTH_TOKEN_SCHEME, AUTH_TOKEN_RESOURCE, AUTH_TOKEN_TOUCH_INTERVAL,
5
- BEARER_SCHEME, GUARD_AUTH_TOKEN, isAccessToken, parseAuthorizationHeader
4
+ AUTH_TOKEN_SCHEME, AUTH_TOKEN_RESOURCE, AUTH_TOKEN_TOUCH_INTERVAL,
5
+ BEARER_SCHEME, GUARD_AUTH_TOKEN, tokenFormatHelper
6
6
  } from '@owlmeans/auth-token'
7
7
  import { AUTH_HEADER } from '@owlmeans/auth'
8
8
  import { ENTITY_RESOLVER } from '@owlmeans/auth-common'
@@ -11,13 +11,12 @@ import { createService } from '@owlmeans/context'
11
11
  import type { AbstractRequest, AbstractResponse, GuardService } from '@owlmeans/entrypoint'
12
12
  import { AUTH_IDENTITY_PROFILE } from '@owlmeans/server-auth-identity'
13
13
  import type { IdentityProfile, IdentityProfileResource } from '@owlmeans/server-auth-identity'
14
- import { hashAccessToken } from './hash.js'
14
+ import { tokenHashHelper } from './hash.js'
15
+ import { prefixOf } from './prefix.js'
15
16
  import type { AccessTokenResource, AuthTokenConfig, AuthTokenContext, AuthTokenGuardOptions } from './types.js'
16
17
  import { logger, logThrottle } from '@owlmeans/log'
17
18
 
18
- /** Where the deployment's prefix comes from: the guard's own options, then config, then default. */
19
- export const prefixOf = (context: AuthTokenContext, opts?: AuthTokenGuardOptions): string =>
20
- opts?.prefix ?? (context.cfg as AuthTokenConfig).authToken?.prefix ?? AUTH_TOKEN_DEFAULT_PREFIX
19
+ const log = logger('server-auth-token')
21
20
 
22
21
  /**
23
22
  * The guard that verifies a long-lived access token.
@@ -37,32 +36,30 @@ export const prefixOf = (context: AuthTokenContext, opts?: AuthTokenGuardOptions
37
36
  * - it intersects the token's scopes with its profile's on every request, so a token can never
38
37
  * outlive or outrank the person who minted it.
39
38
  */
40
- const log = logger('server-auth-token')
41
-
42
- /**
43
- * A presented access token the guard did not accept. Never the token, not even its display form:
44
- * its record id only. Once a minute per token and reason — a client retrying a revoked token calls every second.
45
- */
46
- const refuse = (reason: string, record?: { id?: string, entityId?: string }): false => {
47
- if (logThrottle(`auth-token:${reason}:${record?.id ?? ''}`, 60_000)) {
48
- log.warn('Access token refused', {
49
- reason, tokenId: record?.id, entityId: record?.entityId,
50
- }, { event: 'auth.refused' })
51
- }
52
- return false
53
- }
54
-
55
39
  export const makeAuthTokenGuard = (
56
40
  alias: string = GUARD_AUTH_TOKEN, opts: AuthTokenGuardOptions = {}
57
41
  ): GuardService => {
42
+ /**
43
+ * A presented access token the guard did not accept. Never the token, not even its display form:
44
+ * its record id only. Once a minute per token and reason — a client retrying a revoked token calls every second.
45
+ */
46
+ const refuse = (reason: string, record?: { id?: string, entityId?: string }): false => {
47
+ if (logThrottle(`auth-token:${reason}:${record?.id ?? ''}`, 60_000)) {
48
+ log.warn('Access token refused', {
49
+ reason, tokenId: record?.id, entityId: record?.entityId,
50
+ }, { event: 'auth.refused' })
51
+ }
52
+ return false
53
+ }
54
+
58
55
  const touchInterval = opts.touchInterval ?? AUTH_TOKEN_TOUCH_INTERVAL
59
56
 
60
57
  const present = (req: Partial<AbstractRequest>, prefix: string): string | null => {
61
- const parsed = parseAuthorizationHeader(req.headers?.[AUTH_HEADER])
58
+ const parsed = tokenFormatHelper.parseAuthorizationHeader(req.headers?.[AUTH_HEADER])
62
59
  if (parsed == null) return null
63
60
  if (parsed.scheme !== AUTH_TOKEN_SCHEME && parsed.scheme !== BEARER_SCHEME) return null
64
61
 
65
- return isAccessToken(parsed.value, prefix) ? parsed.value : null
62
+ return tokenFormatHelper.isAccessToken(parsed.value, prefix) ? parsed.value : null
66
63
  }
67
64
 
68
65
  const service: GuardService = createService<GuardService>(alias, {
@@ -87,7 +84,7 @@ export const makeAuthTokenGuard = (
87
84
  if (presented == null) return false as T
88
85
 
89
86
  const tokens = context.resource<AccessTokenResource>(opts.resourceAlias ?? AUTH_TOKEN_RESOURCE)
90
- const record = await tokens.load({ hash: hashAccessToken(presented) })
87
+ const record = await tokens.load({ hash: tokenHashHelper.hashAccessToken(presented) })
91
88
  if (record == null) return refuse('unknown') as T
92
89
  if (record.revokedAt != null) return refuse('revoked', record) as T
93
90
  if (record.expiresAt != null && new Date(record.expiresAt) < new Date()) return refuse('expired', record) as T
@@ -0,0 +1,21 @@
1
+ import { AuthForbidden, AuthRole } from '@owlmeans/auth'
2
+ import type { AuthTokenEntrypoints } from '@owlmeans/auth-token'
3
+ import { makeEntityScope } from '@owlmeans/auth-common'
4
+ import { handlers } from '@owlmeans/server-api'
5
+ import type { AuthTokenContext } from '../types.js'
6
+ import { accessTokenIssuerOf } from './issue.js'
7
+ import { refuseTokenAuth } from './refuse.js'
8
+
9
+ export const createAccessToken = (protocol: AuthTokenEntrypoints['create']) => handlers<AuthTokenContext>().body(protocol, async (payload, context, req) => {
10
+ const ctx = context as AuthTokenContext
11
+ refuseTokenAuth(req, 'token-mint')
12
+
13
+ const entityId = makeEntityScope(req).requireEntityKey()
14
+ const profileId = req.auth?.profileId
15
+ const userId = req.auth?.userId
16
+ if (profileId == null || userId == null) throw new AuthForbidden('profile')
17
+
18
+ return accessTokenIssuerOf(ctx).issueAccessToken({
19
+ entityId, profileId, userId, role: req.auth?.role ?? AuthRole.User, scopes: req.auth?.scopes ?? [],
20
+ }, payload)
21
+ })
@@ -1,131 +1,17 @@
1
- import { AuthForbidden, AuthroizationType, ALL_SCOPES, AuthRole } from '@owlmeans/auth'
2
- import { AUTH_TOKEN_MAX_TTL, AUTH_TOKEN_RESOURCE, type AccessTokenList, type AccessTokenRecord, type AccessTokenView, type IssuedAccessToken, type AuthTokenEntrypoints } from '@owlmeans/auth-token'
3
- import { requireEntityKey } from '@owlmeans/auth-common'
4
- import { handlers } from '@owlmeans/server-api'
5
- import { mintAccessToken } from '../hash.js'
6
- import { prefixOf } from '../guard.js'
7
- import type { AccessTokenResource, AuthTokenContext } from '../types.js'
1
+ import type { IssuedAccessToken } from '@owlmeans/auth-token'
2
+ import type { AuthTokenContext } from '../types.js'
3
+ import { accessTokenIssuerOf } from './issue.js'
8
4
  import type { IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js'
9
5
 
10
- const view = (record: AccessTokenRecord): AccessTokenView => {
11
- const { hash, ...rest } = record
6
+ export * from './refuse.js'
7
+ export * from './issue.js'
8
+ export * from './list.js'
9
+ export * from './create.js'
10
+ export * from './revoke.js'
12
11
 
13
- return rest
14
- }
15
-
16
- const tokens = (ctx: AuthTokenContext): AccessTokenResource =>
17
- ctx.resource<AccessTokenResource>(AUTH_TOKEN_RESOURCE)
18
-
19
- /**
20
- * A token may never mint another token.
21
- *
22
- * Minting is the one operation that turns a stolen credential into a permanent one: a token that
23
- * can create tokens survives the revocation of the token that leaked. Creating and revoking
24
- * therefore require a credential a person produced in a browser — which is also why the deployment
25
- * names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
26
- * a check every future handler has to remember.
27
- *
28
- * Exported because an OAuth authorization server's consent-approval handler
29
- * (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
30
- * verified itself rather than a route this package bound.
31
- */
32
- export const refuseTokenAuth = (req: { auth?: { type?: string } }, what: string): void => {
33
- if (req.auth?.type === AuthroizationType.AuthToken) {
34
- throw new AuthForbidden(what)
35
- }
36
- }
12
+ export type { IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js'
37
13
 
38
- /**
39
- * Mint one token for `subject`, exactly what `createAccessToken` does over an HTTP body — pulled
40
- * out so a second caller (an OAuth token endpoint) can mint the SAME shape of record over a
41
- * session it authenticated its own way, without a duplicate copy of the narrowing rule or the
42
- * TTL clamp silently drifting from this one.
43
- */
14
+ /** @deprecated compat:factory-refactor — use `accessTokenIssuerOf(ctx).issueAccessToken(…)` */
44
15
  export const issueAccessToken = async (
45
16
  ctx: AuthTokenContext, subject: IssueAccessTokenSubject, payload: IssueAccessTokenRequest
46
- ): Promise<IssuedAccessToken> => {
47
- const own = subject.scopes
48
- const scopes = payload.scopes ?? own
49
- // A token is a delegation, so it can only ever narrow. Asking for a scope the caller does not
50
- // hold is refused rather than silently dropped: a token that quietly grants less than it was
51
- // asked for fails later, somewhere else, with an error about the wrong thing.
52
- if (!own.includes(ALL_SCOPES) && scopes.some(scope => !own.includes(scope))) {
53
- throw new AuthForbidden('scope')
54
- }
55
-
56
- // The SAME resolver the guard uses, so a minted token is one this deployment's guard claims.
57
- const minted = mintAccessToken(prefixOf(ctx))
58
- const now = new Date()
59
- const record = await tokens(ctx).create({
60
- hash: minted.hash,
61
- display: minted.display,
62
- name: payload.name,
63
- userId: subject.userId,
64
- profileId: subject.profileId,
65
- entityId: subject.entityId,
66
- scopes,
67
- role: subject.role,
68
- createdAt: now,
69
- ...(payload.audience != null ? { audience: payload.audience } : {}),
70
- ...(payload.expiresIn != null
71
- ? { expiresAt: new Date(now.getTime() + Math.min(payload.expiresIn * 1000, AUTH_TOKEN_MAX_TTL)) }
72
- : {}),
73
- } as AccessTokenRecord)
74
-
75
- // The only moment the plaintext exists outside the caller's own machine.
76
- return { token: minted.token, record: view(record) } satisfies IssuedAccessToken
77
- }
78
-
79
- export const listAccessTokens = (protocol: AuthTokenEntrypoints['list']) => handlers<AuthTokenContext>().request(protocol, async (req, context) => {
80
- const ctx = context as AuthTokenContext
81
- const entityId = requireEntityKey(req)
82
- const profileId = req.auth?.profileId
83
- if (profileId == null) throw new AuthForbidden('profile')
84
-
85
- const result = await tokens(ctx).list({ entityId, profileId }, { size: 0 })
86
-
87
- return {
88
- items: result.items
89
- .map(view)
90
- .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
91
- } satisfies AccessTokenList
92
- })
93
-
94
- export const createAccessToken = (protocol: AuthTokenEntrypoints['create']) => handlers<AuthTokenContext>().body(protocol, async (payload, context, req) => {
95
- const ctx = context as AuthTokenContext
96
- refuseTokenAuth(req, 'token-mint')
97
-
98
- const entityId = requireEntityKey(req)
99
- const profileId = req.auth?.profileId
100
- const userId = req.auth?.userId
101
- if (profileId == null || userId == null) throw new AuthForbidden('profile')
102
-
103
- return issueAccessToken(ctx, {
104
- entityId, profileId, userId, role: req.auth?.role ?? AuthRole.User, scopes: req.auth?.scopes ?? [],
105
- }, payload)
106
- })
107
-
108
- export const revokeAccessToken = (protocol: AuthTokenEntrypoints['revoke']) => handlers<AuthTokenContext>().params(protocol, async (payload, context, req) => {
109
- const ctx = context as AuthTokenContext
110
- refuseTokenAuth(req, 'token-revoke')
111
-
112
- const entityId = requireEntityKey(req)
113
- const profileId = req.auth?.profileId
114
- if (profileId == null) throw new AuthForbidden('profile')
115
-
116
- const record = await tokens(ctx).load(payload.id)
117
- // A token of another profile answers exactly as an unknown id does — an owner learns nothing
118
- // about tokens that are not theirs, not even that one exists.
119
- if (record == null || record.entityId !== entityId || record.profileId !== profileId) {
120
- throw new AuthForbidden('token')
121
- }
122
-
123
- // Idempotent: revoking twice is what a retry looks like.
124
- if (record.revokedAt == null) {
125
- await tokens(ctx).save({ ...record, revokedAt: new Date(), updatedAt: new Date() })
126
- }
127
-
128
- return { id: payload.id }
129
- })
130
-
131
- export type { IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js'
17
+ ): Promise<IssuedAccessToken> => await accessTokenIssuerOf(ctx).issueAccessToken(subject, payload)
@@ -0,0 +1,50 @@
1
+ import { AuthForbidden, ALL_SCOPES } from '@owlmeans/auth'
2
+ import { AUTH_TOKEN_MAX_TTL, type AccessTokenRecord, type IssuedAccessToken } from '@owlmeans/auth-token'
3
+ import { memoHelper } from '@owlmeans/context'
4
+ import { tokenHashHelper } from '../hash.js'
5
+ import { prefixOf } from '../prefix.js'
6
+ import type { AuthTokenContext } from '../types.js'
7
+ import type { AccessTokenIssuer, IssueAccessTokenRequest, IssueAccessTokenSubject } from './types.js'
8
+ import { tokenRecordUtils } from './utils.js'
9
+
10
+ export const makeAccessTokenIssuer = (ctx: AuthTokenContext): AccessTokenIssuer => {
11
+ const issueAccessToken = async (
12
+ subject: IssueAccessTokenSubject, payload: IssueAccessTokenRequest
13
+ ): Promise<IssuedAccessToken> => {
14
+ const own = subject.scopes
15
+ const scopes = payload.scopes ?? own
16
+ // A token is a delegation, so it can only ever narrow. Asking for a scope the caller does not
17
+ // hold is refused rather than silently dropped: a token that quietly grants less than it was
18
+ // asked for fails later, somewhere else, with an error about the wrong thing.
19
+ if (!own.includes(ALL_SCOPES) && scopes.some(scope => !own.includes(scope))) {
20
+ throw new AuthForbidden('scope')
21
+ }
22
+
23
+ // The SAME resolver the guard uses, so a minted token is one this deployment's guard claims.
24
+ const minted = tokenHashHelper.mintAccessToken(prefixOf(ctx))
25
+ const now = new Date()
26
+ const record = await tokenRecordUtils.tokens(ctx).create({
27
+ hash: minted.hash,
28
+ display: minted.display,
29
+ name: payload.name,
30
+ userId: subject.userId,
31
+ profileId: subject.profileId,
32
+ entityId: subject.entityId,
33
+ scopes,
34
+ role: subject.role,
35
+ createdAt: now,
36
+ ...(payload.audience != null ? { audience: payload.audience } : {}),
37
+ ...(payload.expiresIn != null
38
+ ? { expiresAt: new Date(now.getTime() + Math.min(payload.expiresIn * 1000, AUTH_TOKEN_MAX_TTL)) }
39
+ : {}),
40
+ } as AccessTokenRecord)
41
+
42
+ // The only moment the plaintext exists outside the caller's own machine.
43
+ return { token: minted.token, record: tokenRecordUtils.view(record) } satisfies IssuedAccessToken
44
+ }
45
+
46
+ return { issueAccessToken }
47
+ }
48
+
49
+ /** The token issuer of a context — one per context. */
50
+ export const accessTokenIssuerOf = memoHelper.oncePer(makeAccessTokenIssuer)
@@ -0,0 +1,21 @@
1
+ import { AuthForbidden } from '@owlmeans/auth'
2
+ import type { AccessTokenList, AuthTokenEntrypoints } from '@owlmeans/auth-token'
3
+ import { makeEntityScope } from '@owlmeans/auth-common'
4
+ import { handlers } from '@owlmeans/server-api'
5
+ import type { AuthTokenContext } from '../types.js'
6
+ import { tokenRecordUtils } from './utils.js'
7
+
8
+ export const listAccessTokens = (protocol: AuthTokenEntrypoints['list']) => handlers<AuthTokenContext>().request(protocol, async (req, context) => {
9
+ const ctx = context as AuthTokenContext
10
+ const entityId = makeEntityScope(req).requireEntityKey()
11
+ const profileId = req.auth?.profileId
12
+ if (profileId == null) throw new AuthForbidden('profile')
13
+
14
+ const result = await tokenRecordUtils.tokens(ctx).list({ entityId, profileId }, { size: 0 })
15
+
16
+ return {
17
+ items: result.items
18
+ .map(tokenRecordUtils.view)
19
+ .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()),
20
+ } satisfies AccessTokenList
21
+ })
@@ -0,0 +1,20 @@
1
+ import { AuthForbidden, AuthroizationType } from '@owlmeans/auth'
2
+
3
+ /**
4
+ * A token may never mint another token.
5
+ *
6
+ * Minting is the one operation that turns a stolen credential into a permanent one: a token that
7
+ * can create tokens survives the revocation of the token that leaked. Creating and revoking
8
+ * therefore require a credential a person produced in a browser — which is also why the deployment
9
+ * names these routes in the guard's deny list, so the refusal is a 401 at the boundary rather than
10
+ * a check every future handler has to remember.
11
+ *
12
+ * Exported because an OAuth authorization server's consent-approval handler
13
+ * (`@owlmeans/server-oauth`) mints tokens too and needs the exact same guard, over a session it
14
+ * verified itself rather than a route this package bound.
15
+ */
16
+ export const refuseTokenAuth = (req: { auth?: { type?: string } }, what: string): void => {
17
+ if (req.auth?.type === AuthroizationType.AuthToken) {
18
+ throw new AuthForbidden(what)
19
+ }
20
+ }