@rt-tools/auth-server 0.1.0 → 0.2.1

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 (43) hide show
  1. package/README.md +15 -0
  2. package/cjs/index.js +1 -0
  3. package/cjs/index.js.map +1 -1
  4. package/cjs/lib/access-audit.js +2 -2
  5. package/cjs/lib/access-audit.js.map +1 -1
  6. package/cjs/lib/auth-server.module.js +12 -12
  7. package/cjs/lib/auth-server.module.js.map +1 -1
  8. package/cjs/lib/auth.guard.js +5 -5
  9. package/cjs/lib/auth.guard.js.map +1 -1
  10. package/cjs/lib/current-caller.decorator.js +2 -2
  11. package/cjs/lib/current-caller.decorator.js.map +1 -1
  12. package/cjs/lib/env-options.js +77 -0
  13. package/cjs/lib/env-options.js.map +1 -0
  14. package/cjs/lib/token-verifier.js +8 -3
  15. package/cjs/lib/token-verifier.js.map +1 -1
  16. package/esm/index.d.ts +1 -0
  17. package/esm/index.d.ts.map +1 -1
  18. package/esm/index.js +1 -0
  19. package/esm/index.js.map +1 -1
  20. package/esm/lib/access-audit.js +1 -1
  21. package/esm/lib/access-audit.js.map +1 -1
  22. package/esm/lib/auth-server.module.d.ts +2 -2
  23. package/esm/lib/auth-server.module.d.ts.map +1 -1
  24. package/esm/lib/auth-server.module.js +6 -6
  25. package/esm/lib/auth-server.module.js.map +1 -1
  26. package/esm/lib/auth.guard.d.ts +1 -1
  27. package/esm/lib/auth.guard.d.ts.map +1 -1
  28. package/esm/lib/auth.guard.js +2 -2
  29. package/esm/lib/auth.guard.js.map +1 -1
  30. package/esm/lib/connect-access.d.ts +2 -2
  31. package/esm/lib/connect-access.d.ts.map +1 -1
  32. package/esm/lib/connect-access.js.map +1 -1
  33. package/esm/lib/current-caller.decorator.js +1 -1
  34. package/esm/lib/current-caller.decorator.js.map +1 -1
  35. package/esm/lib/env-options.d.ts +44 -0
  36. package/esm/lib/env-options.d.ts.map +1 -0
  37. package/esm/lib/env-options.js +71 -0
  38. package/esm/lib/env-options.js.map +1 -0
  39. package/esm/lib/token-verifier.d.ts +13 -1
  40. package/esm/lib/token-verifier.d.ts.map +1 -1
  41. package/esm/lib/token-verifier.js +8 -3
  42. package/esm/lib/token-verifier.js.map +1 -1
  43. package/package.json +1 -6
package/README.md CHANGED
@@ -30,6 +30,21 @@ export class AppModule {}
30
30
  The module puts a guard on every route. At start it checks that every operation declares exactly
31
31
  one access. When `sync` is given, it also sends the catalog to Keycloak.
32
32
 
33
+ The same options are read from the environment by `authOptionsFromEnv`:
34
+
35
+ ```ts
36
+ AuthServerModule.forRoot(authOptionsFromEnv(process.env, RIGHTS));
37
+ ```
38
+
39
+ `AUTH_ISSUER` and `AUTH_CLIENT_ID` are required, and a missing one stops the start by name.
40
+ `AUTH_SYNC_SECRET` turns the catalog sync on; the Keycloak address and the realm are taken from
41
+ the issuer, and `AUTH_SYNC_CLIENT_ID` names the sync client when it is not `rt-catalog-sync`.
42
+ `AUTH_KEYS_URL` names where the keys of the realm are read when the server reaches Keycloak by
43
+ another address than the browser does — from a container or an inner network.
44
+ `AUTH_SERVICE_CLIENTS` is a comma list of service clients whose tokens the server accepts with the
45
+ rights of `AUTH_CLIENT_ID`. Such a client has a service account holding the roles of that client;
46
+ a command without a person signs in by it.
47
+
33
48
  ## Access
34
49
 
35
50
  ```ts
package/cjs/index.js CHANGED
@@ -9,5 +9,6 @@ tslib_1.__exportStar(require("./lib/auth.tokens.js"), exports);
9
9
  tslib_1.__exportStar(require("./lib/catalog-sync.js"), exports);
10
10
  tslib_1.__exportStar(require("./lib/connect-access.js"), exports);
11
11
  tslib_1.__exportStar(require("./lib/current-caller.decorator.js"), exports);
12
+ tslib_1.__exportStar(require("./lib/env-options.js"), exports);
12
13
  tslib_1.__exportStar(require("./lib/token-verifier.js"), exports);
13
14
  //# sourceMappingURL=index.js.map
package/cjs/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":";;;AAAA,0DAAgC;AAChC,gEAAsC;AACtC,sEAA4C;AAC5C,8DAAoC;AACpC,+DAAqC;AACrC,gEAAsC;AACtC,kEAAwC;AACxC,4EAAkD;AAClD,kEAAwC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":";;;AAAA,0DAAgC;AAChC,gEAAsC;AACtC,sEAA4C;AAC5C,8DAAoC;AACpC,+DAAqC;AACrC,gEAAsC;AACtC,kEAAwC;AACxC,4EAAkD;AAClD,+DAAqC;AACrC,kEAAwC"}
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.undeclaredAccess = undeclaredAccess;
4
4
  exports.accessAuditError = accessAuditError;
5
5
  const constants_1 = require("@nestjs/common/constants");
6
- const access_1 = require("./access");
6
+ const access_js_1 = require("./access.js");
7
7
  function isRoute(handler) {
8
8
  return Reflect.getMetadata(constants_1.PATH_METADATA, handler) !== undefined && Reflect.getMetadata(constants_1.METHOD_METADATA, handler) !== undefined;
9
9
  }
@@ -23,7 +23,7 @@ function undeclaredAccess(controllers) {
23
23
  if (name === 'constructor' || typeof handler !== 'function' || !isRoute(handler)) {
24
24
  continue;
25
25
  }
26
- const count = (0, access_1.accessDeclarationsOf)(handler).length;
26
+ const count = (0, access_js_1.accessDeclarationsOf)(handler).length;
27
27
  if (count !== 1) {
28
28
  faults.push(`${controller.name}.${name} — ${count === 0 ? 'none' : String(count)}`);
29
29
  }
@@ -1 +1 @@
1
- {"version":3,"file":"access-audit.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/access-audit.ts"],"names":[],"mappings":";;AAkBA,4CAgBC;AAGD,4CAGC;AAxCD,wDAA0E;AAE1E,qCAAgD;AAKhD,SAAS,OAAO,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,yBAAa,EAAE,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,2BAAe,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC;AACpI,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAAC,WAAwC;IACrE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YACvD,MAAM,OAAO,GAAY,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtD,IAAI,IAAI,KAAK,aAAa,IAAI,OAAO,OAAO,KAAK,UAAU,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/E,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAW,IAAA,6BAAoB,EAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC3D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,uEAAuE;AACvE,SAAgB,gBAAgB,CAAC,MAAyB;IACtD,MAAM,KAAK,GAAW,MAAM,CAAC,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,IAAI,KAAK,CAAC,+DAA+D,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC"}
1
+ {"version":3,"file":"access-audit.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/access-audit.ts"],"names":[],"mappings":";;AAkBA,4CAgBC;AAGD,4CAGC;AAxCD,wDAA0E;AAE1E,2CAAmD;AAKnD,SAAS,OAAO,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,yBAAa,EAAE,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,2BAAe,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC;AACpI,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAAC,WAAwC;IACrE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YACvD,MAAM,OAAO,GAAY,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtD,IAAI,IAAI,KAAK,aAAa,IAAI,OAAO,OAAO,KAAK,UAAU,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/E,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAW,IAAA,gCAAoB,EAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC3D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,uEAAuE;AACvE,SAAgB,gBAAgB,CAAC,MAAyB;IACtD,MAAM,KAAK,GAAW,MAAM,CAAC,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,IAAI,KAAK,CAAC,+DAA+D,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC"}
@@ -5,11 +5,11 @@ exports.AuthServerModule = exports.AuthStartCheck = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  const common_1 = require("@nestjs/common");
7
7
  const core_1 = require("@nestjs/core");
8
- const access_audit_1 = require("./access-audit");
9
- const auth_guard_1 = require("./auth.guard");
10
- const auth_tokens_1 = require("./auth.tokens");
11
- const catalog_sync_1 = require("./catalog-sync");
12
- const token_verifier_1 = require("./token-verifier");
8
+ const access_audit_js_1 = require("./access-audit.js");
9
+ const auth_guard_js_1 = require("./auth.guard.js");
10
+ const auth_tokens_js_1 = require("./auth.tokens.js");
11
+ const catalog_sync_js_1 = require("./catalog-sync.js");
12
+ const token_verifier_js_1 = require("./token-verifier.js");
13
13
  /**
14
14
  * At start: every operation declares exactly one access, and the catalog reaches Keycloak.
15
15
  *
@@ -29,12 +29,12 @@ class AuthStartCheck {
29
29
  .getControllers()
30
30
  .map((wrapper) => wrapper.metatype)
31
31
  .filter((metatype) => typeof metatype === 'function');
32
- const faults = (0, access_audit_1.undeclaredAccess)(controllers);
32
+ const faults = (0, access_audit_js_1.undeclaredAccess)(controllers);
33
33
  if (faults.length) {
34
- throw (0, access_audit_1.accessAuditError)(faults);
34
+ throw (0, access_audit_js_1.accessAuditError)(faults);
35
35
  }
36
36
  if (this.#options.sync) {
37
- const result = await (0, catalog_sync_1.syncPermissionCatalog)({ ...this.#options.sync, clientId: this.#options.clientId }, this.#options.catalog);
37
+ const result = await (0, catalog_sync_js_1.syncPermissionCatalog)({ ...this.#options.sync, clientId: this.#options.clientId }, this.#options.catalog);
38
38
  this.#logger.log(`catalog: ${this.#options.catalog.length} rights, created ${result.created.length}`);
39
39
  if (result.extra.length) {
40
40
  this.#logger.warn(`Keycloak holds rights the catalog lacks: ${result.extra.join(', ')}`);
@@ -51,16 +51,16 @@ let AuthServerModule = AuthServerModule_1 = class AuthServerModule {
51
51
  global: true,
52
52
  imports: [core_1.DiscoveryModule],
53
53
  providers: [
54
- { provide: auth_tokens_1.AUTH_SERVER_OPTIONS, useValue: options },
55
- { provide: auth_tokens_1.AUTH_TOKEN_VERIFIER, useValue: new token_verifier_1.KeycloakTokenVerifier(options) },
56
- { provide: core_1.APP_GUARD, useClass: auth_guard_1.AuthGuard },
54
+ { provide: auth_tokens_js_1.AUTH_SERVER_OPTIONS, useValue: options },
55
+ { provide: auth_tokens_js_1.AUTH_TOKEN_VERIFIER, useValue: new token_verifier_js_1.KeycloakTokenVerifier(options) },
56
+ { provide: core_1.APP_GUARD, useClass: auth_guard_js_1.AuthGuard },
57
57
  {
58
58
  provide: AuthStartCheck,
59
59
  useFactory: (discovery) => new AuthStartCheck(discovery, options),
60
60
  inject: [core_1.DiscoveryService],
61
61
  },
62
62
  ],
63
- exports: [auth_tokens_1.AUTH_TOKEN_VERIFIER],
63
+ exports: [auth_tokens_js_1.AUTH_SERVER_OPTIONS, auth_tokens_js_1.AUTH_TOKEN_VERIFIER],
64
64
  };
65
65
  }
66
66
  };
@@ -1 +1 @@
1
- {"version":3,"file":"auth-server.module.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":";;;;;AAAA,2CAAuF;AACvF,uCAA4E;AAK5E,iDAAsF;AACtF,6CAAyC;AACzC,+CAAyE;AACzE,iDAAgG;AAChG,qDAA6E;AAU7E;;;;;GAKG;AACH,MAAa,cAAc;IACd,UAAU,CAAmB;IAC7B,QAAQ,CAAqB;IAC7B,OAAO,GAAW,IAAI,eAAM,CAAC,YAAY,CAAC,CAAC;IAEpD,YAAY,SAA2B,EAAE,OAA2B;QAChE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC5B,CAAC;IAEM,KAAK,CAAC,sBAAsB;QAC/B,MAAM,WAAW,GAAuB,IAAI,CAAC,UAAU;aAClD,cAAc,EAAE;aAChB,GAAG,CAAC,CAAC,OAAwB,EAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;aAC5D,MAAM,CAAC,CAAC,QAAiB,EAAgC,EAAE,CAAC,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC;QACjG,MAAM,MAAM,GAAsB,IAAA,+BAAgB,EAAC,WAAW,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAA,+BAAgB,EAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,MAAM,GAAuB,MAAM,IAAA,oCAAqB,EAC1D,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAC3D,IAAI,CAAC,QAAQ,CAAC,OAAO,CACxB,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,oBAAoB,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,4CAA4C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7F,CAAC;QACL,CAAC;IACL,CAAC;CACJ;AA9BD,wCA8BC;AAED,2FAA2F;AAEpF,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAClB,MAAM,CAAC,OAAO,CAAC,OAA2B;QAC7C,OAAO;YACH,MAAM,EAAE,kBAAgB;YACxB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,CAAC,sBAAe,CAAC;YAC1B,SAAS,EAAE;gBACP,EAAE,OAAO,EAAE,iCAAmB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACnD,EAAE,OAAO,EAAE,iCAAmB,EAAE,QAAQ,EAAE,IAAI,sCAAqB,CAAC,OAAO,CAAC,EAAE;gBAC9E,EAAE,OAAO,EAAE,gBAAS,EAAE,QAAQ,EAAE,sBAAS,EAAE;gBAC3C;oBACI,OAAO,EAAE,cAAc;oBACvB,UAAU,EAAE,CAAC,SAA2B,EAAkB,EAAE,CAAC,IAAI,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC;oBACnG,MAAM,EAAE,CAAC,uBAAgB,CAAC;iBAC7B;aACJ;YACD,OAAO,EAAE,CAAC,iCAAmB,CAAC;SACjC,CAAC;IACN,CAAC;CACJ,CAAA;AAnBY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,gBAAgB,CAmB5B"}
1
+ {"version":3,"file":"auth-server.module.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":";;;;;AAAA,2CAAuF;AACvF,uCAA4E;AAK5E,uDAAyF;AACzF,mDAA4C;AAC5C,qDAA4E;AAC5E,uDAAmG;AACnG,2DAAgF;AAUhF;;;;;GAKG;AACH,MAAa,cAAc;IACd,UAAU,CAAmB;IAC7B,QAAQ,CAAqB;IAC7B,OAAO,GAAW,IAAI,eAAM,CAAC,YAAY,CAAC,CAAC;IAEpD,YAAY,SAA2B,EAAE,OAA2B;QAChE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC5B,CAAC;IAEM,KAAK,CAAC,sBAAsB;QAC/B,MAAM,WAAW,GAAuB,IAAI,CAAC,UAAU;aAClD,cAAc,EAAE;aAChB,GAAG,CAAC,CAAC,OAAwB,EAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;aAC5D,MAAM,CAAC,CAAC,QAAiB,EAAgC,EAAE,CAAC,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC;QACjG,MAAM,MAAM,GAAsB,IAAA,kCAAgB,EAAC,WAAW,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,IAAA,kCAAgB,EAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,MAAM,GAAuB,MAAM,IAAA,uCAAqB,EAC1D,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAC3D,IAAI,CAAC,QAAQ,CAAC,OAAO,CACxB,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,oBAAoB,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,4CAA4C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7F,CAAC;QACL,CAAC;IACL,CAAC;CACJ;AA9BD,wCA8BC;AAED,2FAA2F;AAEpF,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAClB,MAAM,CAAC,OAAO,CAAC,OAA2B;QAC7C,OAAO;YACH,MAAM,EAAE,kBAAgB;YACxB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,CAAC,sBAAe,CAAC;YAC1B,SAAS,EAAE;gBACP,EAAE,OAAO,EAAE,oCAAmB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACnD,EAAE,OAAO,EAAE,oCAAmB,EAAE,QAAQ,EAAE,IAAI,yCAAqB,CAAC,OAAO,CAAC,EAAE;gBAC9E,EAAE,OAAO,EAAE,gBAAS,EAAE,QAAQ,EAAE,yBAAS,EAAE;gBAC3C;oBACI,OAAO,EAAE,cAAc;oBACvB,UAAU,EAAE,CAAC,SAA2B,EAAkB,EAAE,CAAC,IAAI,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC;oBACnG,MAAM,EAAE,CAAC,uBAAgB,CAAC;iBAC7B;aACJ;YACD,OAAO,EAAE,CAAC,oCAAmB,EAAE,oCAAmB,CAAC;SACtD,CAAC;IACN,CAAC;CACJ,CAAA;AAnBY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,gBAAgB,CAmB5B"}
@@ -4,8 +4,8 @@ exports.AuthGuard = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const common_1 = require("@nestjs/common");
6
6
  const auth_contract_1 = require("@rt-tools/auth-contract");
7
- const access_1 = require("./access");
8
- const auth_tokens_1 = require("./auth.tokens");
7
+ const access_js_1 = require("./access.js");
8
+ const auth_tokens_js_1 = require("./auth.tokens.js");
9
9
  /**
10
10
  * The only access check of an admin server. **Closed by default.**
11
11
  *
@@ -19,7 +19,7 @@ let AuthGuard = class AuthGuard {
19
19
  this.#verifier = verifier;
20
20
  }
21
21
  async canActivate(context) {
22
- const declared = (0, access_1.accessDeclarationsOf)(context.getHandler());
22
+ const declared = (0, access_js_1.accessDeclarationsOf)(context.getHandler());
23
23
  if (declared.length !== 1) {
24
24
  // The start audit stops such an application; a handler that slipped past it is refused
25
25
  // rather than opened by a guess.
@@ -38,14 +38,14 @@ let AuthGuard = class AuthGuard {
38
38
  if (access.kind === 'permission' && !(0, auth_contract_1.hasPermission)(caller, access.permission)) {
39
39
  throw new common_1.ForbiddenException();
40
40
  }
41
- request[auth_tokens_1.REQUEST_CALLER] = caller;
41
+ request[auth_tokens_js_1.REQUEST_CALLER] = caller;
42
42
  return true;
43
43
  }
44
44
  };
45
45
  exports.AuthGuard = AuthGuard;
46
46
  exports.AuthGuard = AuthGuard = tslib_1.__decorate([
47
47
  (0, common_1.Injectable)(),
48
- tslib_1.__param(0, (0, common_1.Inject)(auth_tokens_1.AUTH_TOKEN_VERIFIER)),
48
+ tslib_1.__param(0, (0, common_1.Inject)(auth_tokens_js_1.AUTH_TOKEN_VERIFIER)),
49
49
  tslib_1.__metadata("design:paramtypes", [Function])
50
50
  ], AuthGuard);
51
51
  //# sourceMappingURL=auth.guard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":";;;;AAAA,2CAA8H;AAE9H,2DAAiE;AAEjE,qCAAyD;AACzD,+CAAoE;AASpE;;;;;;GAMG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAS;IACT,SAAS,CAAwB;IAE1C,YAAyC,QAA+B;QACpE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,OAAyB;QAC9C,MAAM,QAAQ,GAAuB,IAAA,6BAAoB,EAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,uFAAuF;YACvF,iCAAiC;YACjC,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACtC,CAAC;QACD,MAAM,MAAM,GAAY,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAiB,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAChF,MAAM,MAAM,GAAkC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAmB,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,IAAA,6BAAa,EAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,2BAAkB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,4BAAc,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA9BY,8BAAS;oBAAT,SAAS;IADrB,IAAA,mBAAU,GAAE;IAII,mBAAA,IAAA,eAAM,EAAC,iCAAmB,CAAC,CAAA;;GAH/B,SAAS,CA8BrB"}
1
+ {"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":";;;;AAAA,2CAA8H;AAE9H,2DAAiE;AAEjE,2CAA4D;AAC5D,qDAAuE;AASvE;;;;;;GAMG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAS;IACT,SAAS,CAAwB;IAE1C,YAAyC,QAA+B;QACpE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,OAAyB;QAC9C,MAAM,QAAQ,GAAuB,IAAA,gCAAoB,EAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,uFAAuF;YACvF,iCAAiC;YACjC,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACtC,CAAC;QACD,MAAM,MAAM,GAAY,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAiB,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAChF,MAAM,MAAM,GAAkC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAmB,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,8BAAqB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,IAAA,6BAAa,EAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,2BAAkB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,+BAAc,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA9BY,8BAAS;oBAAT,SAAS;IADrB,IAAA,mBAAU,GAAE;IAII,mBAAA,IAAA,eAAM,EAAC,oCAAmB,CAAC,CAAA;;GAH/B,SAAS,CA8BrB"}
@@ -3,10 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CurrentCaller = void 0;
4
4
  exports.callerOfRequest = callerOfRequest;
5
5
  const common_1 = require("@nestjs/common");
6
- const auth_tokens_1 = require("./auth.tokens");
6
+ const auth_tokens_js_1 = require("./auth.tokens.js");
7
7
  /** The caller the guard accepted; `undefined` on an operation open to everyone. */
8
8
  function callerOfRequest(request) {
9
- const caller = Reflect.get(request, auth_tokens_1.REQUEST_CALLER);
9
+ const caller = Reflect.get(request, auth_tokens_js_1.REQUEST_CALLER);
10
10
  return caller === undefined ? undefined : caller;
11
11
  }
12
12
  /** A handler parameter that receives the caller the guard accepted. */
@@ -1 +1 @@
1
- {"version":3,"file":"current-caller.decorator.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/current-caller.decorator.ts"],"names":[],"mappings":";;;AAOA,0CAGC;AAVD,2CAAwE;AAIxE,+CAA+C;AAE/C,mFAAmF;AACnF,SAAgB,eAAe,CAAC,OAAe;IAC3C,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,4BAAc,CAAC,CAAC;IAC7D,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAkB,CAAC;AAClE,CAAC;AAED,uEAAuE;AAC1D,QAAA,aAAa,GAA6B,IAAA,6BAAoB,EACvE,CAAC,KAAc,EAAE,OAAyB,EAAuB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAU,CAAC,CACnI,CAAC"}
1
+ {"version":3,"file":"current-caller.decorator.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/current-caller.decorator.ts"],"names":[],"mappings":";;;AAOA,0CAGC;AAVD,2CAAwE;AAIxE,qDAAkD;AAElD,mFAAmF;AACnF,SAAgB,eAAe,CAAC,OAAe;IAC3C,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,+BAAc,CAAC,CAAC;IAC7D,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAkB,CAAC;AAClE,CAAC;AAED,uEAAuE;AAC1D,QAAA,aAAa,GAA6B,IAAA,6BAAoB,EACvE,CAAC,KAAc,EAAE,OAAyB,EAAuB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAU,CAAC,CACnI,CAAC"}
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AUTH_ENV_VARIABLES = void 0;
4
+ exports.realmOfIssuer = realmOfIssuer;
5
+ exports.clientSettingsOf = clientSettingsOf;
6
+ exports.authOptionsFromEnv = authOptionsFromEnv;
7
+ /** The variables without which a server checks no token. */
8
+ exports.AUTH_ENV_VARIABLES = ['AUTH_ISSUER', 'AUTH_CLIENT_ID'];
9
+ /** The service client of the realm allowed to create the roles of a client, unless named otherwise. */
10
+ const SYNC_CLIENT_ID = 'rt-catalog-sync';
11
+ /** The Keycloak address and the realm name, taken from the issuer: `https://sso.example.com/realms/rt`. */
12
+ const ISSUER = /^(https?:\/\/.+)\/realms\/([^/]+)\/?$/;
13
+ /** The Keycloak address and the realm of an issuer, or `null` when it is not the address of a realm. */
14
+ function realmOfIssuer(issuer) {
15
+ const match = ISSUER.exec(issuer);
16
+ return match ? { url: match[1], realm: match[2] } : null;
17
+ }
18
+ /**
19
+ * The settings the browser part signs in with, taken from the options of the server. The admin
20
+ * asks them at start instead of carrying its own copy: a copy built into the page drifts from the
21
+ * server at the first move of Keycloak to another address.
22
+ */
23
+ function clientSettingsOf(options) {
24
+ const realm = realmOfIssuer(options.issuer);
25
+ if (!realm) {
26
+ throw new Error(`The issuer is not the address of a Keycloak realm: ${options.issuer}`);
27
+ }
28
+ return { ...realm, clientId: options.clientId };
29
+ }
30
+ /**
31
+ * The options of the entry module from the environment of the server.
32
+ *
33
+ * The realm and the client belong to the stand, not to the code: a missing one stops the start by
34
+ * name instead of checking tokens against a guess. The catalog goes to Keycloak only when the
35
+ * environment holds the secret of the sync client (`AUTH_SYNC_SECRET`); `AUTH_SYNC_CLIENT_ID`
36
+ * names that client when it is not `rt-catalog-sync`. Without the secret the realm file of the
37
+ * stand declares the roles itself. `AUTH_KEYS_URL` names where the keys are read when the server
38
+ * reaches Keycloak by another address than the browser. `AUTH_SERVICE_CLIENTS` names, by commas, the
39
+ * service clients whose tokens the server accepts besides the client of the admin.
40
+ */
41
+ function authOptionsFromEnv(env, catalog) {
42
+ const missing = exports.AUTH_ENV_VARIABLES.filter((name) => !env[name]);
43
+ if (missing.length) {
44
+ throw new Error(`The server needs ${missing.join(' and ')} in the environment`);
45
+ }
46
+ const issuer = String(env.AUTH_ISSUER);
47
+ const clientId = String(env.AUTH_CLIENT_ID);
48
+ const secret = env.AUTH_SYNC_SECRET;
49
+ const services = (env.AUTH_SERVICE_CLIENTS ?? '')
50
+ .split(',')
51
+ .map((name) => name.trim())
52
+ .filter(Boolean);
53
+ const keys = {
54
+ ...(env.AUTH_KEYS_URL ? { keysUrl: env.AUTH_KEYS_URL } : {}),
55
+ ...(services.length ? { serviceClients: services } : {}),
56
+ };
57
+ if (!secret) {
58
+ return { issuer, clientId, catalog, ...keys };
59
+ }
60
+ const realm = realmOfIssuer(issuer);
61
+ if (!realm) {
62
+ throw new Error(`AUTH_ISSUER is not the address of a Keycloak realm: ${issuer}`);
63
+ }
64
+ return {
65
+ issuer,
66
+ clientId,
67
+ catalog,
68
+ ...keys,
69
+ sync: {
70
+ baseUrl: realm.url,
71
+ realm: realm.realm,
72
+ syncClientId: env.AUTH_SYNC_CLIENT_ID || SYNC_CLIENT_ID,
73
+ syncClientSecret: secret,
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=env-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-options.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/env-options.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,8 @@ function claimsOf(payload) {
36
36
  * The token check of one admin, shared by the NestJS guard and the Connect interceptor.
37
37
  *
38
38
  * A token is accepted when a key of the realm signed it, the realm issued it, its term holds and
39
- * it was issued to the client of this admin (`azp`). Every other case answers `null` and nothing
39
+ * it was issued to the client of this admin or to a service client it names (`azp`). Every other
40
+ * case answers `null` and nothing
40
41
  * more: the reason of a refusal is not told to the caller, so the answers do not show which tokens
41
42
  * come close.
42
43
  *
@@ -48,7 +49,7 @@ class KeycloakTokenVerifier {
48
49
  #keys;
49
50
  constructor(options, keys) {
50
51
  this.#options = options;
51
- this.#keys = keys ?? (0, jose_1.createRemoteJWKSet)(new URL(`${options.issuer}/protocol/openid-connect/certs`));
52
+ this.#keys = keys ?? (0, jose_1.createRemoteJWKSet)(new URL(options.keysUrl ?? `${options.issuer}/protocol/openid-connect/certs`));
52
53
  }
53
54
  /** The caller of the `Authorization` header, or `null` when the token is not accepted. */
54
55
  async callerOf(authorization) {
@@ -58,7 +59,7 @@ class KeycloakTokenVerifier {
58
59
  }
59
60
  try {
60
61
  const { payload } = await (0, jose_1.jwtVerify)(token, this.#keys, { issuer: this.#options.issuer });
61
- if (payload['azp'] !== this.#options.clientId || typeof payload.sub !== 'string') {
62
+ if (!this.#accepts(payload['azp']) || typeof payload.sub !== 'string') {
62
63
  return null;
63
64
  }
64
65
  return (0, auth_contract_1.callerFromClaims)(claimsOf(payload), this.#options.clientId);
@@ -67,6 +68,10 @@ class KeycloakTokenVerifier {
67
68
  return null;
68
69
  }
69
70
  }
71
+ /** Whether a token issued to this client is accepted: the client of this admin or a service client it names. */
72
+ #accepts(azp) {
73
+ return azp === this.#options.clientId || (typeof azp === 'string' && (this.#options.serviceClients ?? []).includes(azp));
74
+ }
70
75
  }
71
76
  exports.KeycloakTokenVerifier = KeycloakTokenVerifier;
72
77
  //# sourceMappingURL=token-verifier.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-verifier.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-verifier.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"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"}
package/esm/index.d.ts CHANGED
@@ -6,5 +6,6 @@ export * from './lib/auth.tokens.js';
6
6
  export * from './lib/catalog-sync.js';
7
7
  export * from './lib/connect-access.js';
8
8
  export * from './lib/current-caller.decorator.js';
9
+ export * from './lib/env-options.js';
9
10
  export * from './lib/token-verifier.js';
10
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC"}
package/esm/index.js CHANGED
@@ -6,5 +6,6 @@ export * from './lib/auth.tokens.js';
6
6
  export * from './lib/catalog-sync.js';
7
7
  export * from './lib/connect-access.js';
8
8
  export * from './lib/current-caller.decorator.js';
9
+ export * from './lib/env-options.js';
9
10
  export * from './lib/token-verifier.js';
10
11
  //# sourceMappingURL=index.js.map
package/esm/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,yBAAyB,CAAC","sourcesContent":["export * from './lib/access.js';\nexport * from './lib/access-audit.js';\nexport * from './lib/auth-server.module.js';\nexport * from './lib/auth.guard.js';\nexport * from './lib/auth.tokens.js';\nexport * from './lib/catalog-sync.js';\nexport * from './lib/connect-access.js';\nexport * from './lib/current-caller.decorator.js';\nexport * from './lib/token-verifier.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../projects/auth-server/src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,uBAAuB,CAAC;AACtC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,mCAAmC,CAAC;AAClD,cAAc,sBAAsB,CAAC;AACrC,cAAc,yBAAyB,CAAC","sourcesContent":["export * from './lib/access.js';\nexport * from './lib/access-audit.js';\nexport * from './lib/auth-server.module.js';\nexport * from './lib/auth.guard.js';\nexport * from './lib/auth.tokens.js';\nexport * from './lib/catalog-sync.js';\nexport * from './lib/connect-access.js';\nexport * from './lib/current-caller.decorator.js';\nexport * from './lib/env-options.js';\nexport * from './lib/token-verifier.js';\n"]}
@@ -1,5 +1,5 @@
1
1
  import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants';
2
- import { accessDeclarationsOf } from './access';
2
+ import { accessDeclarationsOf } from './access.js';
3
3
  function isRoute(handler) {
4
4
  return Reflect.getMetadata(PATH_METADATA, handler) !== undefined && Reflect.getMetadata(METHOD_METADATA, handler) !== undefined;
5
5
  }
@@ -1 +1 @@
1
- {"version":3,"file":"access-audit.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/access-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAKhD,SAAS,OAAO,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,aAAa,EAAE,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC;AACpI,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAwC;IACrE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YACvD,MAAM,OAAO,GAAY,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtD,IAAI,IAAI,KAAK,aAAa,IAAI,OAAO,OAAO,KAAK,UAAU,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/E,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAW,oBAAoB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC3D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,MAAyB;IACtD,MAAM,KAAK,GAAW,MAAM,CAAC,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,IAAI,KAAK,CAAC,+DAA+D,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC","sourcesContent":["import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants';\n\nimport { accessDeclarationsOf } from './access';\n\n/** A class whose prototype holds the route handlers. */\nexport type TControllerClass = abstract new (...args: never[]) => object;\n\nfunction isRoute(handler: object): boolean {\n return Reflect.getMetadata(PATH_METADATA, handler) !== undefined && Reflect.getMetadata(METHOD_METADATA, handler) !== undefined;\n}\n\n/**\n * The route handlers of the controllers that declare no access or more than one, as\n * `Controller.method — none` / `— 2`.\n *\n * A handler is a prototype method with a route mark of the framework. An empty list means every\n * handler declares exactly one access.\n */\nexport function undeclaredAccess(controllers: readonly TControllerClass[]): readonly string[] {\n const faults: string[] = [];\n for (const controller of controllers) {\n const prototype: object = controller.prototype as object;\n for (const name of Object.getOwnPropertyNames(prototype)) {\n const handler: unknown = Reflect.get(prototype, name);\n if (name === 'constructor' || typeof handler !== 'function' || !isRoute(handler)) {\n continue;\n }\n const count: number = accessDeclarationsOf(handler).length;\n if (count !== 1) {\n faults.push(`${controller.name}.${name} — ${count === 0 ? 'none' : String(count)}`);\n }\n }\n }\n return faults;\n}\n\n/** The refusal of the start, naming every faulty operation at once. */\nexport function accessAuditError(faults: readonly string[]): Error {\n const lines: string = faults.map((fault: string): string => ' ' + fault).join('\\n');\n return new Error(`Every operation declares exactly one access; these do not:\\n${lines}`);\n}\n"]}
1
+ {"version":3,"file":"access-audit.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/access-audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAKnD,SAAS,OAAO,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,aAAa,EAAE,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,eAAe,EAAE,OAAO,CAAC,KAAK,SAAS,CAAC;AACpI,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAwC;IACrE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACnC,MAAM,SAAS,GAAW,UAAU,CAAC,SAAmB,CAAC;QACzD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,EAAE,CAAC;YACvD,MAAM,OAAO,GAAY,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACtD,IAAI,IAAI,KAAK,aAAa,IAAI,OAAO,OAAO,KAAK,UAAU,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC/E,SAAS;YACb,CAAC;YACD,MAAM,KAAK,GAAW,oBAAoB,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;YAC3D,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBACd,MAAM,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,IAAI,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACxF,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,gBAAgB,CAAC,MAAyB;IACtD,MAAM,KAAK,GAAW,MAAM,CAAC,GAAG,CAAC,CAAC,KAAa,EAAU,EAAE,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,OAAO,IAAI,KAAK,CAAC,+DAA+D,KAAK,EAAE,CAAC,CAAC;AAC7F,CAAC","sourcesContent":["import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants';\n\nimport { accessDeclarationsOf } from './access.js';\n\n/** A class whose prototype holds the route handlers. */\nexport type TControllerClass = abstract new (...args: never[]) => object;\n\nfunction isRoute(handler: object): boolean {\n return Reflect.getMetadata(PATH_METADATA, handler) !== undefined && Reflect.getMetadata(METHOD_METADATA, handler) !== undefined;\n}\n\n/**\n * The route handlers of the controllers that declare no access or more than one, as\n * `Controller.method — none` / `— 2`.\n *\n * A handler is a prototype method with a route mark of the framework. An empty list means every\n * handler declares exactly one access.\n */\nexport function undeclaredAccess(controllers: readonly TControllerClass[]): readonly string[] {\n const faults: string[] = [];\n for (const controller of controllers) {\n const prototype: object = controller.prototype as object;\n for (const name of Object.getOwnPropertyNames(prototype)) {\n const handler: unknown = Reflect.get(prototype, name);\n if (name === 'constructor' || typeof handler !== 'function' || !isRoute(handler)) {\n continue;\n }\n const count: number = accessDeclarationsOf(handler).length;\n if (count !== 1) {\n faults.push(`${controller.name}.${name} — ${count === 0 ? 'none' : String(count)}`);\n }\n }\n }\n return faults;\n}\n\n/** The refusal of the start, naming every faulty operation at once. */\nexport function accessAuditError(faults: readonly string[]): Error {\n const lines: string = faults.map((fault: string): string => ' ' + fault).join('\\n');\n return new Error(`Every operation declares exactly one access; these do not:\\n${lines}`);\n}\n"]}
@@ -1,8 +1,8 @@
1
1
  import { DynamicModule, OnApplicationBootstrap } from '@nestjs/common';
2
2
  import { DiscoveryService } from '@nestjs/core';
3
3
  import { TPermission } from '@rt-tools/auth-contract';
4
- import { ICatalogSyncOptions } from './catalog-sync';
5
- import { ITokenCheckOptions } from './token-verifier';
4
+ import { ICatalogSyncOptions } from './catalog-sync.js';
5
+ import { ITokenCheckOptions } from './token-verifier.js';
6
6
  /** What an admin server tells the module. */
7
7
  export interface IAuthServerOptions extends ITokenCheckOptions {
8
8
  /** Every right of this admin — `definePermissions(…)` of the contract. */
@@ -1 +1 @@
1
- {"version":3,"file":"auth-server.module.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACvF,OAAO,EAA8B,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAKtD,OAAO,EAAE,mBAAmB,EAA6C,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAE,kBAAkB,EAAyB,MAAM,kBAAkB,CAAC;AAE7E,6CAA6C;AAC7C,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC1D,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,sBAAsB;;gBAK7C,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,kBAAkB;IAKvD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBvD;AAED,2FAA2F;AAC3F,qBACa,gBAAgB;WACX,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,aAAa;CAkBpE"}
1
+ {"version":3,"file":"auth-server.module.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACvF,OAAO,EAA8B,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG5E,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAKtD,OAAO,EAAE,mBAAmB,EAA6C,MAAM,mBAAmB,CAAC;AACnG,OAAO,EAAE,kBAAkB,EAAyB,MAAM,qBAAqB,CAAC;AAEhF,6CAA6C;AAC7C,MAAM,WAAW,kBAAmB,SAAQ,kBAAkB;IAC1D,0EAA0E;IAC1E,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,CAAC;CACzD;AAED;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,sBAAsB;;gBAK7C,SAAS,EAAE,gBAAgB,EAAE,OAAO,EAAE,kBAAkB;IAKvD,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;CAoBvD;AAED,2FAA2F;AAC3F,qBACa,gBAAgB;WACX,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,aAAa;CAkBpE"}
@@ -2,11 +2,11 @@ var AuthServerModule_1;
2
2
  import { __decorate } from "tslib";
3
3
  import { Logger, Module } from '@nestjs/common';
4
4
  import { APP_GUARD, DiscoveryModule, DiscoveryService } from '@nestjs/core';
5
- import { accessAuditError, undeclaredAccess } from './access-audit';
6
- import { AuthGuard } from './auth.guard';
7
- import { AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER } from './auth.tokens';
8
- import { syncPermissionCatalog } from './catalog-sync';
9
- import { KeycloakTokenVerifier } from './token-verifier';
5
+ import { accessAuditError, undeclaredAccess } from './access-audit.js';
6
+ import { AuthGuard } from './auth.guard.js';
7
+ import { AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER } from './auth.tokens.js';
8
+ import { syncPermissionCatalog } from './catalog-sync.js';
9
+ import { KeycloakTokenVerifier } from './token-verifier.js';
10
10
  /**
11
11
  * At start: every operation declares exactly one access, and the catalog reaches Keycloak.
12
12
  *
@@ -56,7 +56,7 @@ let AuthServerModule = AuthServerModule_1 = class AuthServerModule {
56
56
  inject: [DiscoveryService],
57
57
  },
58
58
  ],
59
- exports: [AUTH_TOKEN_VERIFIER],
59
+ exports: [AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER],
60
60
  };
61
61
  }
62
62
  };
@@ -1 +1 @@
1
- {"version":3,"file":"auth-server.module.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":";;AAAA,OAAO,EAAiB,MAAM,EAAE,MAAM,EAA0B,MAAM,gBAAgB,CAAC;AACvF,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAK5E,OAAO,EAAE,gBAAgB,EAAoB,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACzE,OAAO,EAA2C,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAChG,OAAO,EAAsB,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAU7E;;;;;GAKG;AACH,MAAM,OAAO,cAAc;IACd,UAAU,CAAmB;IAC7B,QAAQ,CAAqB;IAC7B,OAAO,GAAW,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC;IAEpD,YAAY,SAA2B,EAAE,OAA2B;QAChE,IAAI,CAAC,UAAU,GAAG,SAAS,CAAC;QAC5B,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC;IAC5B,CAAC;IAEM,KAAK,CAAC,sBAAsB;QAC/B,MAAM,WAAW,GAAuB,IAAI,CAAC,UAAU;aAClD,cAAc,EAAE;aAChB,GAAG,CAAC,CAAC,OAAwB,EAAW,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC;aAC5D,MAAM,CAAC,CAAC,QAAiB,EAAgC,EAAE,CAAC,OAAO,QAAQ,KAAK,UAAU,CAAC,CAAC;QACjG,MAAM,MAAM,GAAsB,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAChE,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACnC,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YACrB,MAAM,MAAM,GAAuB,MAAM,qBAAqB,CAC1D,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,EAC3D,IAAI,CAAC,QAAQ,CAAC,OAAO,CACxB,CAAC;YACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,oBAAoB,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;YACtG,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC;gBACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,4CAA4C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC7F,CAAC;QACL,CAAC;IACL,CAAC;CACJ;AAED,2FAA2F;AAEpF,IAAM,gBAAgB,wBAAtB,MAAM,gBAAgB;IAClB,MAAM,CAAC,OAAO,CAAC,OAA2B;QAC7C,OAAO;YACH,MAAM,EAAE,kBAAgB;YACxB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,CAAC,eAAe,CAAC;YAC1B,SAAS,EAAE;gBACP,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACnD,EAAE,OAAO,EAAE,mBAAmB,EAAE,QAAQ,EAAE,IAAI,qBAAqB,CAAC,OAAO,CAAC,EAAE;gBAC9E,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE;gBAC3C;oBACI,OAAO,EAAE,cAAc;oBACvB,UAAU,EAAE,CAAC,SAA2B,EAAkB,EAAE,CAAC,IAAI,cAAc,CAAC,SAAS,EAAE,OAAO,CAAC;oBACnG,MAAM,EAAE,CAAC,gBAAgB,CAAC;iBAC7B;aACJ;YACD,OAAO,EAAE,CAAC,mBAAmB,CAAC;SACjC,CAAC;IACN,CAAC;CACJ,CAAA;AAnBY,gBAAgB;IAD5B,MAAM,CAAC,EAAE,CAAC;GACE,gBAAgB,CAmB5B","sourcesContent":["import { DynamicModule, Logger, Module, OnApplicationBootstrap } from '@nestjs/common';\nimport { APP_GUARD, DiscoveryModule, DiscoveryService } from '@nestjs/core';\nimport { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';\n\nimport { TPermission } from '@rt-tools/auth-contract';\n\nimport { accessAuditError, TControllerClass, undeclaredAccess } from './access-audit';\nimport { AuthGuard } from './auth.guard';\nimport { AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER } from './auth.tokens';\nimport { ICatalogSyncOptions, ICatalogSyncResult, syncPermissionCatalog } from './catalog-sync';\nimport { ITokenCheckOptions, KeycloakTokenVerifier } from './token-verifier';\n\n/** What an admin server tells the module. */\nexport interface IAuthServerOptions extends ITokenCheckOptions {\n /** Every right of this admin — `definePermissions(…)` of the contract. */\n readonly catalog: readonly TPermission[];\n /** Where the catalog is sent at start; without it nothing is sent. */\n readonly sync?: Omit<ICatalogSyncOptions, 'clientId'>;\n}\n\n/**\n * At start: every operation declares exactly one access, and the catalog reaches Keycloak.\n *\n * An operation without a declaration or with two stops the start and is named; the catalog sync\n * creates the roles Keycloak lacks and names the extra ones in the log.\n */\nexport class AuthStartCheck implements OnApplicationBootstrap {\n readonly #discovery: DiscoveryService;\n readonly #options: IAuthServerOptions;\n readonly #logger: Logger = new Logger('AuthServer');\n\n constructor(discovery: DiscoveryService, options: IAuthServerOptions) {\n this.#discovery = discovery;\n this.#options = options;\n }\n\n public async onApplicationBootstrap(): Promise<void> {\n const controllers: TControllerClass[] = this.#discovery\n .getControllers()\n .map((wrapper: InstanceWrapper): unknown => wrapper.metatype)\n .filter((metatype: unknown): metatype is TControllerClass => typeof metatype === 'function');\n const faults: readonly string[] = undeclaredAccess(controllers);\n if (faults.length) {\n throw accessAuditError(faults);\n }\n if (this.#options.sync) {\n const result: ICatalogSyncResult = await syncPermissionCatalog(\n { ...this.#options.sync, clientId: this.#options.clientId },\n this.#options.catalog\n );\n this.#logger.log(`catalog: ${this.#options.catalog.length} rights, created ${result.created.length}`);\n if (result.extra.length) {\n this.#logger.warn(`Keycloak holds rights the catalog lacks: ${result.extra.join(', ')}`);\n }\n }\n }\n}\n\n/** The entry module of an admin server: the token check, the guard and the start check. */\n@Module({})\nexport class AuthServerModule {\n public static forRoot(options: IAuthServerOptions): DynamicModule {\n return {\n module: AuthServerModule,\n global: true,\n imports: [DiscoveryModule],\n providers: [\n { provide: AUTH_SERVER_OPTIONS, useValue: options },\n { provide: AUTH_TOKEN_VERIFIER, useValue: new KeycloakTokenVerifier(options) },\n { provide: APP_GUARD, useClass: AuthGuard },\n {\n provide: AuthStartCheck,\n useFactory: (discovery: DiscoveryService): AuthStartCheck => new AuthStartCheck(discovery, options),\n inject: [DiscoveryService],\n },\n ],\n exports: [AUTH_TOKEN_VERIFIER],\n };\n }\n}\n"]}
1
+ {"version":3,"file":"auth-server.module.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth-server.module.ts"],"names":[],"mappings":"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","sourcesContent":["import { DynamicModule, Logger, Module, OnApplicationBootstrap } from '@nestjs/common';\nimport { APP_GUARD, DiscoveryModule, DiscoveryService } from '@nestjs/core';\nimport { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';\n\nimport { TPermission } from '@rt-tools/auth-contract';\n\nimport { accessAuditError, TControllerClass, undeclaredAccess } from './access-audit.js';\nimport { AuthGuard } from './auth.guard.js';\nimport { AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER } from './auth.tokens.js';\nimport { ICatalogSyncOptions, ICatalogSyncResult, syncPermissionCatalog } from './catalog-sync.js';\nimport { ITokenCheckOptions, KeycloakTokenVerifier } from './token-verifier.js';\n\n/** What an admin server tells the module. */\nexport interface IAuthServerOptions extends ITokenCheckOptions {\n /** Every right of this admin — `definePermissions(…)` of the contract. */\n readonly catalog: readonly TPermission[];\n /** Where the catalog is sent at start; without it nothing is sent. */\n readonly sync?: Omit<ICatalogSyncOptions, 'clientId'>;\n}\n\n/**\n * At start: every operation declares exactly one access, and the catalog reaches Keycloak.\n *\n * An operation without a declaration or with two stops the start and is named; the catalog sync\n * creates the roles Keycloak lacks and names the extra ones in the log.\n */\nexport class AuthStartCheck implements OnApplicationBootstrap {\n readonly #discovery: DiscoveryService;\n readonly #options: IAuthServerOptions;\n readonly #logger: Logger = new Logger('AuthServer');\n\n constructor(discovery: DiscoveryService, options: IAuthServerOptions) {\n this.#discovery = discovery;\n this.#options = options;\n }\n\n public async onApplicationBootstrap(): Promise<void> {\n const controllers: TControllerClass[] = this.#discovery\n .getControllers()\n .map((wrapper: InstanceWrapper): unknown => wrapper.metatype)\n .filter((metatype: unknown): metatype is TControllerClass => typeof metatype === 'function');\n const faults: readonly string[] = undeclaredAccess(controllers);\n if (faults.length) {\n throw accessAuditError(faults);\n }\n if (this.#options.sync) {\n const result: ICatalogSyncResult = await syncPermissionCatalog(\n { ...this.#options.sync, clientId: this.#options.clientId },\n this.#options.catalog\n );\n this.#logger.log(`catalog: ${this.#options.catalog.length} rights, created ${result.created.length}`);\n if (result.extra.length) {\n this.#logger.warn(`Keycloak holds rights the catalog lacks: ${result.extra.join(', ')}`);\n }\n }\n }\n}\n\n/** The entry module of an admin server: the token check, the guard and the start check. */\n@Module({})\nexport class AuthServerModule {\n public static forRoot(options: IAuthServerOptions): DynamicModule {\n return {\n module: AuthServerModule,\n global: true,\n imports: [DiscoveryModule],\n providers: [\n { provide: AUTH_SERVER_OPTIONS, useValue: options },\n { provide: AUTH_TOKEN_VERIFIER, useValue: new KeycloakTokenVerifier(options) },\n { provide: APP_GUARD, useClass: AuthGuard },\n {\n provide: AuthStartCheck,\n useFactory: (discovery: DiscoveryService): AuthStartCheck => new AuthStartCheck(discovery, options),\n inject: [DiscoveryService],\n },\n ],\n exports: [AUTH_SERVER_OPTIONS, AUTH_TOKEN_VERIFIER],\n };\n }\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { CanActivate, ExecutionContext } from '@nestjs/common';
2
- import type { KeycloakTokenVerifier } from './token-verifier';
2
+ import type { KeycloakTokenVerifier } from './token-verifier.js';
3
3
  /**
4
4
  * The only access check of an admin server. **Closed by default.**
5
5
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.guard.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAiE,MAAM,gBAAgB,CAAC;AAM9H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAQ9D;;;;;;GAMG;AACH,qBACa,SAAU,YAAW,WAAW;;gBAGA,QAAQ,EAAE,qBAAqB;IAI3D,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CAuBxE"}
1
+ {"version":3,"file":"auth.guard.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAiE,MAAM,gBAAgB,CAAC;AAM9H,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAQjE;;;;;;GAMG;AACH,qBACa,SAAU,YAAW,WAAW;;gBAGA,QAAQ,EAAE,qBAAqB;IAI3D,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;CAuBxE"}
@@ -1,8 +1,8 @@
1
1
  import { __decorate, __metadata, __param } from "tslib";
2
2
  import { ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
3
3
  import { hasPermission } from '@rt-tools/auth-contract';
4
- import { accessDeclarationsOf } from './access';
5
- import { AUTH_TOKEN_VERIFIER, REQUEST_CALLER } from './auth.tokens';
4
+ import { accessDeclarationsOf } from './access.js';
5
+ import { AUTH_TOKEN_VERIFIER, REQUEST_CALLER } from './auth.tokens.js';
6
6
  /**
7
7
  * The only access check of an admin server. **Closed by default.**
8
8
  *
@@ -1 +1 @@
1
- {"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":";AAAA,OAAO,EAAiC,kBAAkB,EAAE,MAAM,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE9H,OAAO,EAAE,aAAa,EAAW,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAW,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AASpE;;;;;;GAMG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAS;IACT,SAAS,CAAwB;IAE1C,YAAyC,QAA+B;QACpE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,OAAyB;QAC9C,MAAM,QAAQ,GAAuB,oBAAoB,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,uFAAuF;YACvF,iCAAiC;YACjC,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACtC,CAAC;QACD,MAAM,MAAM,GAAY,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAiB,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAChF,MAAM,MAAM,GAAkC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAmB,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,kBAAkB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA9BY,SAAS;IADrB,UAAU,EAAE;IAII,WAAA,MAAM,CAAC,mBAAmB,CAAC,CAAA;;GAH/B,SAAS,CA8BrB","sourcesContent":["import { CanActivate, ExecutionContext, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';\n\nimport { hasPermission, ICaller } from '@rt-tools/auth-contract';\n\nimport { accessDeclarationsOf, TAccess } from './access';\nimport { AUTH_TOKEN_VERIFIER, REQUEST_CALLER } from './auth.tokens';\nimport type { KeycloakTokenVerifier } from './token-verifier';\n\n/** The request as the guard reads it: the headers and the place for the caller. */\ninterface IAuthRequest {\n headers: Record<string, string | string[] | undefined>;\n [REQUEST_CALLER]?: ICaller;\n}\n\n/**\n * The only access check of an admin server. **Closed by default.**\n *\n * Two refusals, told apart by the question: no accepted token is «not signed in» (401) and is\n * cured by signing in; a token without the right is «not allowed» (403) and is not. Neither names\n * the missing right or what in the token did not match.\n */\n@Injectable()\nexport class AuthGuard implements CanActivate {\n readonly #verifier: KeycloakTokenVerifier;\n\n constructor(@Inject(AUTH_TOKEN_VERIFIER) verifier: KeycloakTokenVerifier) {\n this.#verifier = verifier;\n }\n\n public async canActivate(context: ExecutionContext): Promise<boolean> {\n const declared: readonly TAccess[] = accessDeclarationsOf(context.getHandler());\n if (declared.length !== 1) {\n // The start audit stops such an application; a handler that slipped past it is refused\n // rather than opened by a guess.\n throw new UnauthorizedException();\n }\n const access: TAccess = declared[0];\n if (access.kind === 'public') {\n return true;\n }\n const request: IAuthRequest = context.switchToHttp().getRequest<IAuthRequest>();\n const header: string | string[] | undefined = request.headers['authorization'];\n const caller: ICaller | null = await this.#verifier.callerOf(typeof header === 'string' ? header : null);\n if (!caller) {\n throw new UnauthorizedException();\n }\n if (access.kind === 'permission' && !hasPermission(caller, access.permission)) {\n throw new ForbiddenException();\n }\n request[REQUEST_CALLER] = caller;\n return true;\n }\n}\n"]}
1
+ {"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/auth.guard.ts"],"names":[],"mappings":";AAAA,OAAO,EAAiC,kBAAkB,EAAE,MAAM,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE9H,OAAO,EAAE,aAAa,EAAW,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,oBAAoB,EAAW,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvE;;;;;;GAMG;AAEI,IAAM,SAAS,GAAf,MAAM,SAAS;IACT,SAAS,CAAwB;IAE1C,YAAyC,QAA+B;QACpE,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC9B,CAAC;IAEM,KAAK,CAAC,WAAW,CAAC,OAAyB;QAC9C,MAAM,QAAQ,GAAuB,oBAAoB,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;QAChF,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACxB,uFAAuF;YACvF,iCAAiC;YACjC,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACtC,CAAC;QACD,MAAM,MAAM,GAAY,QAAQ,CAAC,CAAC,CAAC,CAAC;QACpC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC3B,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,MAAM,OAAO,GAAiB,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAChF,MAAM,MAAM,GAAkC,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QAC/E,MAAM,MAAM,GAAmB,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACzG,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,MAAM,IAAI,qBAAqB,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5E,MAAM,IAAI,kBAAkB,EAAE,CAAC;QACnC,CAAC;QACD,OAAO,CAAC,cAAc,CAAC,GAAG,MAAM,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA9BY,SAAS;IADrB,UAAU,EAAE;IAII,WAAA,MAAM,CAAC,mBAAmB,CAAC,CAAA;;GAH/B,SAAS,CA8BrB","sourcesContent":["import { CanActivate, ExecutionContext, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';\n\nimport { hasPermission, ICaller } from '@rt-tools/auth-contract';\n\nimport { accessDeclarationsOf, TAccess } from './access.js';\nimport { AUTH_TOKEN_VERIFIER, REQUEST_CALLER } from './auth.tokens.js';\nimport type { KeycloakTokenVerifier } from './token-verifier.js';\n\n/** The request as the guard reads it: the headers and the place for the caller. */\ninterface IAuthRequest {\n headers: Record<string, string | string[] | undefined>;\n [REQUEST_CALLER]?: ICaller;\n}\n\n/**\n * The only access check of an admin server. **Closed by default.**\n *\n * Two refusals, told apart by the question: no accepted token is «not signed in» (401) and is\n * cured by signing in; a token without the right is «not allowed» (403) and is not. Neither names\n * the missing right or what in the token did not match.\n */\n@Injectable()\nexport class AuthGuard implements CanActivate {\n readonly #verifier: KeycloakTokenVerifier;\n\n constructor(@Inject(AUTH_TOKEN_VERIFIER) verifier: KeycloakTokenVerifier) {\n this.#verifier = verifier;\n }\n\n public async canActivate(context: ExecutionContext): Promise<boolean> {\n const declared: readonly TAccess[] = accessDeclarationsOf(context.getHandler());\n if (declared.length !== 1) {\n // The start audit stops such an application; a handler that slipped past it is refused\n // rather than opened by a guess.\n throw new UnauthorizedException();\n }\n const access: TAccess = declared[0];\n if (access.kind === 'public') {\n return true;\n }\n const request: IAuthRequest = context.switchToHttp().getRequest<IAuthRequest>();\n const header: string | string[] | undefined = request.headers['authorization'];\n const caller: ICaller | null = await this.#verifier.callerOf(typeof header === 'string' ? header : null);\n if (!caller) {\n throw new UnauthorizedException();\n }\n if (access.kind === 'permission' && !hasPermission(caller, access.permission)) {\n throw new ForbiddenException();\n }\n request[REQUEST_CALLER] = caller;\n return true;\n }\n}\n"]}
@@ -1,7 +1,7 @@
1
1
  import { ContextKey, Interceptor } from '@connectrpc/connect';
2
2
  import { ICaller } from '@rt-tools/auth-contract';
3
- import { TAccess } from './access';
4
- import { KeycloakTokenVerifier } from './token-verifier';
3
+ import { TAccess } from './access.js';
4
+ import { KeycloakTokenVerifier } from './token-verifier.js';
5
5
  /** The part of a Connect service description the access map is checked against. */
6
6
  export interface IConnectServiceLike {
7
7
  readonly typeName: string;
@@ -1 +1 @@
1
- {"version":3,"file":"connect-access.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/connect-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,UAAU,EAAE,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AAEjI,OAAO,EAAiB,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtF;AAED,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,IAAI;IAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO;CAAE,CAAC;AAE3I,wFAAwF;AACxF,eAAO,MAAM,cAAc,EAAE,UAAU,CAAC,OAAO,GAAG,IAAI,CAAmF,CAAC;AAE1I;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAa9B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,GAAG,WAAW,CAmBpI"}
1
+ {"version":3,"file":"connect-access.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/connect-access.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwC,UAAU,EAAE,WAAW,EAA+B,MAAM,qBAAqB,CAAC;AAEjI,OAAO,EAAiB,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEjE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,mFAAmF;AACnF,MAAM,WAAW,mBAAmB;IAChC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACtF;AAED,iFAAiF;AACjF,MAAM,MAAM,qBAAqB,CAAC,OAAO,SAAS;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,IAAI;IAAE,QAAQ,EAAE,MAAM,IAAI,MAAM,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO;CAAE,CAAC;AAE3I,wFAAwF;AACxF,eAAO,MAAM,cAAc,EAAE,UAAU,CAAC,OAAO,GAAG,IAAI,CAAmF,CAAC;AAE1I;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,mBAAmB,EAC5B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAa9B;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,qBAAqB,EAAE,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,GAAG,WAAW,CAmBpI"}
@@ -1 +1 @@
1
- {"version":3,"file":"connect-access.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/connect-access.ts"],"names":[],"mappings":"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","sourcesContent":["import { Code, ConnectError, createContextKey, ContextKey, Interceptor, UnaryRequest, StreamRequest } from '@connectrpc/connect';\n\nimport { hasPermission, ICaller } from '@rt-tools/auth-contract';\n\nimport { TAccess } from './access';\nimport { KeycloakTokenVerifier } from './token-verifier';\n\n/** The part of a Connect service description the access map is checked against. */\nexport interface IConnectServiceLike {\n readonly typeName: string;\n readonly methods: readonly { readonly name: string; readonly localName: string }[];\n}\n\n/** The access of every method of one service, keyed by the local method name. */\nexport type TConnectServiceAccess<SERVICE extends { readonly method: object }> = { readonly [METHOD in keyof SERVICE['method']]: TAccess };\n\n/** The caller the interceptor accepted, read by a procedure from its context values. */\nexport const CONNECT_CALLER: ContextKey<ICaller | null> = createContextKey<ICaller | null>(null, { description: 'rt-tools.auth.caller' });\n\n/**\n * The accesses of one service as entries `typeName/MethodName`, checked against its description.\n *\n * Every method declares exactly one access, and a key that names no method is refused too: both\n * would otherwise show only to the person who calls the method.\n */\nexport function connectAccessEntries(\n service: IConnectServiceLike,\n access: Readonly<Record<string, TAccess>>\n): readonly [string, TAccess][] {\n const names: Set<string> = new Set(service.methods.map((method: { localName: string }): string => method.localName));\n const missing: string[] = [...names].filter((name: string): boolean => !(name in access));\n const unknown: string[] = Object.keys(access).filter((name: string): boolean => !names.has(name));\n if (missing.length || unknown.length) {\n throw new Error(\n `Every method of ${service.typeName} declares exactly one access; missing: ${missing.join(', ') || 'none'}; unknown: ${unknown.join(', ') || 'none'}`\n );\n }\n return service.methods.map((method: { name: string; localName: string }): [string, TAccess] => [\n `${service.typeName}/${method.name}`,\n access[method.localName],\n ]);\n}\n\n/**\n * The Connect interceptor of an admin server: the same token check as the NestJS guard, and the\n * same two refusals — `unauthenticated` and `permission_denied` — without naming the right.\n *\n * A method the map does not name is refused: the map is closed by default, as the guard is.\n */\nexport function createAuthInterceptor(verifier: KeycloakTokenVerifier, entries: readonly (readonly [string, TAccess])[]): Interceptor {\n const accessOf: Map<string, TAccess> = new Map(entries);\n return (next: Parameters<Interceptor>[0]) => async (request: UnaryRequest | StreamRequest) => {\n const access: TAccess | undefined = accessOf.get(`${request.method.parent.typeName}/${request.method.name}`);\n if (!access) {\n throw new ConnectError('unauthenticated', Code.Unauthenticated);\n }\n if (access.kind !== 'public') {\n const caller: ICaller | null = await verifier.callerOf(request.header.get('authorization'));\n if (!caller) {\n throw new ConnectError('unauthenticated', Code.Unauthenticated);\n }\n if (access.kind === 'permission' && !hasPermission(caller, access.permission)) {\n throw new ConnectError('permission denied', Code.PermissionDenied);\n }\n request.contextValues.set(CONNECT_CALLER, caller);\n }\n return next(request);\n };\n}\n"]}
1
+ {"version":3,"file":"connect-access.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/connect-access.ts"],"names":[],"mappings":"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","sourcesContent":["import { Code, ConnectError, createContextKey, ContextKey, Interceptor, UnaryRequest, StreamRequest } from '@connectrpc/connect';\n\nimport { hasPermission, ICaller } from '@rt-tools/auth-contract';\n\nimport { TAccess } from './access.js';\nimport { KeycloakTokenVerifier } from './token-verifier.js';\n\n/** The part of a Connect service description the access map is checked against. */\nexport interface IConnectServiceLike {\n readonly typeName: string;\n readonly methods: readonly { readonly name: string; readonly localName: string }[];\n}\n\n/** The access of every method of one service, keyed by the local method name. */\nexport type TConnectServiceAccess<SERVICE extends { readonly method: object }> = { readonly [METHOD in keyof SERVICE['method']]: TAccess };\n\n/** The caller the interceptor accepted, read by a procedure from its context values. */\nexport const CONNECT_CALLER: ContextKey<ICaller | null> = createContextKey<ICaller | null>(null, { description: 'rt-tools.auth.caller' });\n\n/**\n * The accesses of one service as entries `typeName/MethodName`, checked against its description.\n *\n * Every method declares exactly one access, and a key that names no method is refused too: both\n * would otherwise show only to the person who calls the method.\n */\nexport function connectAccessEntries(\n service: IConnectServiceLike,\n access: Readonly<Record<string, TAccess>>\n): readonly [string, TAccess][] {\n const names: Set<string> = new Set(service.methods.map((method: { localName: string }): string => method.localName));\n const missing: string[] = [...names].filter((name: string): boolean => !(name in access));\n const unknown: string[] = Object.keys(access).filter((name: string): boolean => !names.has(name));\n if (missing.length || unknown.length) {\n throw new Error(\n `Every method of ${service.typeName} declares exactly one access; missing: ${missing.join(', ') || 'none'}; unknown: ${unknown.join(', ') || 'none'}`\n );\n }\n return service.methods.map((method: { name: string; localName: string }): [string, TAccess] => [\n `${service.typeName}/${method.name}`,\n access[method.localName],\n ]);\n}\n\n/**\n * The Connect interceptor of an admin server: the same token check as the NestJS guard, and the\n * same two refusals — `unauthenticated` and `permission_denied` — without naming the right.\n *\n * A method the map does not name is refused: the map is closed by default, as the guard is.\n */\nexport function createAuthInterceptor(verifier: KeycloakTokenVerifier, entries: readonly (readonly [string, TAccess])[]): Interceptor {\n const accessOf: Map<string, TAccess> = new Map(entries);\n return (next: Parameters<Interceptor>[0]) => async (request: UnaryRequest | StreamRequest) => {\n const access: TAccess | undefined = accessOf.get(`${request.method.parent.typeName}/${request.method.name}`);\n if (!access) {\n throw new ConnectError('unauthenticated', Code.Unauthenticated);\n }\n if (access.kind !== 'public') {\n const caller: ICaller | null = await verifier.callerOf(request.header.get('authorization'));\n if (!caller) {\n throw new ConnectError('unauthenticated', Code.Unauthenticated);\n }\n if (access.kind === 'permission' && !hasPermission(caller, access.permission)) {\n throw new ConnectError('permission denied', Code.PermissionDenied);\n }\n request.contextValues.set(CONNECT_CALLER, caller);\n }\n return next(request);\n };\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import { createParamDecorator } from '@nestjs/common';
2
- import { REQUEST_CALLER } from './auth.tokens';
2
+ import { REQUEST_CALLER } from './auth.tokens.js';
3
3
  /** The caller the guard accepted; `undefined` on an operation open to everyone. */
4
4
  export function callerOfRequest(request) {
5
5
  const caller = Reflect.get(request, REQUEST_CALLER);
@@ -1 +1 @@
1
- {"version":3,"file":"current-caller.decorator.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/current-caller.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,gBAAgB,CAAC;AAIxE,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,OAAe;IAC3C,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC7D,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAkB,CAAC;AAClE,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAA6B,oBAAoB,CACvE,CAAC,KAAc,EAAE,OAAyB,EAAuB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAU,CAAC,CACnI,CAAC","sourcesContent":["import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nimport { ICaller } from '@rt-tools/auth-contract';\n\nimport { REQUEST_CALLER } from './auth.tokens';\n\n/** The caller the guard accepted; `undefined` on an operation open to everyone. */\nexport function callerOfRequest(request: object): ICaller | undefined {\n const caller: unknown = Reflect.get(request, REQUEST_CALLER);\n return caller === undefined ? undefined : (caller as ICaller);\n}\n\n/** A handler parameter that receives the caller the guard accepted. */\nexport const CurrentCaller: () => ParameterDecorator = createParamDecorator(\n (_data: unknown, context: ExecutionContext): ICaller | undefined => callerOfRequest(context.switchToHttp().getRequest<object>())\n);\n"]}
1
+ {"version":3,"file":"current-caller.decorator.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/current-caller.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAoB,MAAM,gBAAgB,CAAC;AAIxE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,mFAAmF;AACnF,MAAM,UAAU,eAAe,CAAC,OAAe;IAC3C,MAAM,MAAM,GAAY,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC7D,OAAO,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAE,MAAkB,CAAC;AAClE,CAAC;AAED,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAA6B,oBAAoB,CACvE,CAAC,KAAc,EAAE,OAAyB,EAAuB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAU,CAAC,CACnI,CAAC","sourcesContent":["import { createParamDecorator, ExecutionContext } from '@nestjs/common';\n\nimport { ICaller } from '@rt-tools/auth-contract';\n\nimport { REQUEST_CALLER } from './auth.tokens.js';\n\n/** The caller the guard accepted; `undefined` on an operation open to everyone. */\nexport function callerOfRequest(request: object): ICaller | undefined {\n const caller: unknown = Reflect.get(request, REQUEST_CALLER);\n return caller === undefined ? undefined : (caller as ICaller);\n}\n\n/** A handler parameter that receives the caller the guard accepted. */\nexport const CurrentCaller: () => ParameterDecorator = createParamDecorator(\n (_data: unknown, context: ExecutionContext): ICaller | undefined => callerOfRequest(context.switchToHttp().getRequest<object>())\n);\n"]}
@@ -0,0 +1,44 @@
1
+ import { TPermission } from '@rt-tools/auth-contract';
2
+ import type { IAuthServerOptions } from './auth-server.module.js';
3
+ /** The environment variables the entry module is set up by; `process.env` fits it as it is. */
4
+ export interface IAuthEnv {
5
+ readonly AUTH_ISSUER?: string;
6
+ readonly AUTH_CLIENT_ID?: string;
7
+ readonly AUTH_SYNC_SECRET?: string;
8
+ readonly AUTH_SYNC_CLIENT_ID?: string;
9
+ readonly AUTH_KEYS_URL?: string;
10
+ readonly AUTH_SERVICE_CLIENTS?: string;
11
+ }
12
+ /** The variables without which a server checks no token. */
13
+ export declare const AUTH_ENV_VARIABLES: readonly (keyof IAuthEnv)[];
14
+ /** What the browser part of an admin needs to sign a person in: the same realm and client as the server. */
15
+ export interface IAuthClientSettings {
16
+ /** The Keycloak address, for example `https://sso.example.com`. */
17
+ readonly url: string;
18
+ readonly realm: string;
19
+ readonly clientId: string;
20
+ }
21
+ /** The Keycloak address and the realm of an issuer, or `null` when it is not the address of a realm. */
22
+ export declare function realmOfIssuer(issuer: string): {
23
+ readonly url: string;
24
+ readonly realm: string;
25
+ } | null;
26
+ /**
27
+ * The settings the browser part signs in with, taken from the options of the server. The admin
28
+ * asks them at start instead of carrying its own copy: a copy built into the page drifts from the
29
+ * server at the first move of Keycloak to another address.
30
+ */
31
+ export declare function clientSettingsOf(options: IAuthServerOptions): IAuthClientSettings;
32
+ /**
33
+ * The options of the entry module from the environment of the server.
34
+ *
35
+ * The realm and the client belong to the stand, not to the code: a missing one stops the start by
36
+ * name instead of checking tokens against a guess. The catalog goes to Keycloak only when the
37
+ * environment holds the secret of the sync client (`AUTH_SYNC_SECRET`); `AUTH_SYNC_CLIENT_ID`
38
+ * names that client when it is not `rt-catalog-sync`. Without the secret the realm file of the
39
+ * stand declares the roles itself. `AUTH_KEYS_URL` names where the keys are read when the server
40
+ * reaches Keycloak by another address than the browser. `AUTH_SERVICE_CLIENTS` names, by commas, the
41
+ * service clients whose tokens the server accepts besides the client of the admin.
42
+ */
43
+ export declare function authOptionsFromEnv(env: IAuthEnv, catalog: readonly TPermission[]): IAuthServerOptions;
44
+ //# sourceMappingURL=env-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-options.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/env-options.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAEtD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC1C;AAED,4DAA4D;AAC5D,eAAO,MAAM,kBAAkB,EAAE,SAAS,CAAC,MAAM,QAAQ,CAAC,EAAsC,CAAC;AAQjG,4GAA4G;AAC5G,MAAM,WAAW,mBAAmB;IAChC,mEAAmE;IACnE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAGrG;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,mBAAmB,CAMjF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,WAAW,EAAE,GAAG,kBAAkB,CAmCrG"}
@@ -0,0 +1,71 @@
1
+ /** The variables without which a server checks no token. */
2
+ export const AUTH_ENV_VARIABLES = ['AUTH_ISSUER', 'AUTH_CLIENT_ID'];
3
+ /** The service client of the realm allowed to create the roles of a client, unless named otherwise. */
4
+ const SYNC_CLIENT_ID = 'rt-catalog-sync';
5
+ /** The Keycloak address and the realm name, taken from the issuer: `https://sso.example.com/realms/rt`. */
6
+ const ISSUER = /^(https?:\/\/.+)\/realms\/([^/]+)\/?$/;
7
+ /** The Keycloak address and the realm of an issuer, or `null` when it is not the address of a realm. */
8
+ export function realmOfIssuer(issuer) {
9
+ const match = ISSUER.exec(issuer);
10
+ return match ? { url: match[1], realm: match[2] } : null;
11
+ }
12
+ /**
13
+ * The settings the browser part signs in with, taken from the options of the server. The admin
14
+ * asks them at start instead of carrying its own copy: a copy built into the page drifts from the
15
+ * server at the first move of Keycloak to another address.
16
+ */
17
+ export function clientSettingsOf(options) {
18
+ const realm = realmOfIssuer(options.issuer);
19
+ if (!realm) {
20
+ throw new Error(`The issuer is not the address of a Keycloak realm: ${options.issuer}`);
21
+ }
22
+ return { ...realm, clientId: options.clientId };
23
+ }
24
+ /**
25
+ * The options of the entry module from the environment of the server.
26
+ *
27
+ * The realm and the client belong to the stand, not to the code: a missing one stops the start by
28
+ * name instead of checking tokens against a guess. The catalog goes to Keycloak only when the
29
+ * environment holds the secret of the sync client (`AUTH_SYNC_SECRET`); `AUTH_SYNC_CLIENT_ID`
30
+ * names that client when it is not `rt-catalog-sync`. Without the secret the realm file of the
31
+ * stand declares the roles itself. `AUTH_KEYS_URL` names where the keys are read when the server
32
+ * reaches Keycloak by another address than the browser. `AUTH_SERVICE_CLIENTS` names, by commas, the
33
+ * service clients whose tokens the server accepts besides the client of the admin.
34
+ */
35
+ export function authOptionsFromEnv(env, catalog) {
36
+ const missing = AUTH_ENV_VARIABLES.filter((name) => !env[name]);
37
+ if (missing.length) {
38
+ throw new Error(`The server needs ${missing.join(' and ')} in the environment`);
39
+ }
40
+ const issuer = String(env.AUTH_ISSUER);
41
+ const clientId = String(env.AUTH_CLIENT_ID);
42
+ const secret = env.AUTH_SYNC_SECRET;
43
+ const services = (env.AUTH_SERVICE_CLIENTS ?? '')
44
+ .split(',')
45
+ .map((name) => name.trim())
46
+ .filter(Boolean);
47
+ const keys = {
48
+ ...(env.AUTH_KEYS_URL ? { keysUrl: env.AUTH_KEYS_URL } : {}),
49
+ ...(services.length ? { serviceClients: services } : {}),
50
+ };
51
+ if (!secret) {
52
+ return { issuer, clientId, catalog, ...keys };
53
+ }
54
+ const realm = realmOfIssuer(issuer);
55
+ if (!realm) {
56
+ throw new Error(`AUTH_ISSUER is not the address of a Keycloak realm: ${issuer}`);
57
+ }
58
+ return {
59
+ issuer,
60
+ clientId,
61
+ catalog,
62
+ ...keys,
63
+ sync: {
64
+ baseUrl: realm.url,
65
+ realm: realm.realm,
66
+ syncClientId: env.AUTH_SYNC_CLIENT_ID || SYNC_CLIENT_ID,
67
+ syncClientSecret: secret,
68
+ },
69
+ };
70
+ }
71
+ //# sourceMappingURL=env-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-options.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/env-options.ts"],"names":[],"mappings":"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","sourcesContent":["import { TPermission } from '@rt-tools/auth-contract';\n\nimport type { IAuthServerOptions } from './auth-server.module.js';\n\n/** The environment variables the entry module is set up by; `process.env` fits it as it is. */\nexport interface IAuthEnv {\n readonly AUTH_ISSUER?: string;\n readonly AUTH_CLIENT_ID?: string;\n readonly AUTH_SYNC_SECRET?: string;\n readonly AUTH_SYNC_CLIENT_ID?: string;\n readonly AUTH_KEYS_URL?: string;\n readonly AUTH_SERVICE_CLIENTS?: string;\n}\n\n/** The variables without which a server checks no token. */\nexport const AUTH_ENV_VARIABLES: readonly (keyof IAuthEnv)[] = ['AUTH_ISSUER', 'AUTH_CLIENT_ID'];\n\n/** The service client of the realm allowed to create the roles of a client, unless named otherwise. */\nconst SYNC_CLIENT_ID: string = 'rt-catalog-sync';\n\n/** The Keycloak address and the realm name, taken from the issuer: `https://sso.example.com/realms/rt`. */\nconst ISSUER: RegExp = /^(https?:\\/\\/.+)\\/realms\\/([^/]+)\\/?$/;\n\n/** What the browser part of an admin needs to sign a person in: the same realm and client as the server. */\nexport interface IAuthClientSettings {\n /** The Keycloak address, for example `https://sso.example.com`. */\n readonly url: string;\n readonly realm: string;\n readonly clientId: string;\n}\n\n/** The Keycloak address and the realm of an issuer, or `null` when it is not the address of a realm. */\nexport function realmOfIssuer(issuer: string): { readonly url: string; readonly realm: string } | null {\n const match: RegExpExecArray | null = ISSUER.exec(issuer);\n return match ? { url: match[1], realm: match[2] } : null;\n}\n\n/**\n * The settings the browser part signs in with, taken from the options of the server. The admin\n * asks them at start instead of carrying its own copy: a copy built into the page drifts from the\n * server at the first move of Keycloak to another address.\n */\nexport function clientSettingsOf(options: IAuthServerOptions): IAuthClientSettings {\n const realm: { readonly url: string; readonly realm: string } | null = realmOfIssuer(options.issuer);\n if (!realm) {\n throw new Error(`The issuer is not the address of a Keycloak realm: ${options.issuer}`);\n }\n return { ...realm, clientId: options.clientId };\n}\n\n/**\n * The options of the entry module from the environment of the server.\n *\n * The realm and the client belong to the stand, not to the code: a missing one stops the start by\n * name instead of checking tokens against a guess. The catalog goes to Keycloak only when the\n * environment holds the secret of the sync client (`AUTH_SYNC_SECRET`); `AUTH_SYNC_CLIENT_ID`\n * names that client when it is not `rt-catalog-sync`. Without the secret the realm file of the\n * stand declares the roles itself. `AUTH_KEYS_URL` names where the keys are read when the server\n * reaches Keycloak by another address than the browser. `AUTH_SERVICE_CLIENTS` names, by commas, the\n * service clients whose tokens the server accepts besides the client of the admin.\n */\nexport function authOptionsFromEnv(env: IAuthEnv, catalog: readonly TPermission[]): IAuthServerOptions {\n const missing: readonly string[] = AUTH_ENV_VARIABLES.filter((name: keyof IAuthEnv): boolean => !env[name]);\n if (missing.length) {\n throw new Error(`The server needs ${missing.join(' and ')} in the environment`);\n }\n const issuer: string = String(env.AUTH_ISSUER);\n const clientId: string = String(env.AUTH_CLIENT_ID);\n const secret: string | undefined = env.AUTH_SYNC_SECRET;\n const services: readonly string[] = (env.AUTH_SERVICE_CLIENTS ?? '')\n .split(',')\n .map((name: string): string => name.trim())\n .filter(Boolean);\n const keys: { readonly keysUrl?: string; readonly serviceClients?: readonly string[] } = {\n ...(env.AUTH_KEYS_URL ? { keysUrl: env.AUTH_KEYS_URL } : {}),\n ...(services.length ? { serviceClients: services } : {}),\n };\n if (!secret) {\n return { issuer, clientId, catalog, ...keys };\n }\n const realm: { readonly url: string; readonly realm: string } | null = realmOfIssuer(issuer);\n if (!realm) {\n throw new Error(`AUTH_ISSUER is not the address of a Keycloak realm: ${issuer}`);\n }\n return {\n issuer,\n clientId,\n catalog,\n ...keys,\n sync: {\n baseUrl: realm.url,\n realm: realm.realm,\n syncClientId: env.AUTH_SYNC_CLIENT_ID || SYNC_CLIENT_ID,\n syncClientSecret: secret,\n },\n };\n}\n"]}
@@ -6,6 +6,17 @@ export interface ITokenCheckOptions {
6
6
  readonly issuer: string;
7
7
  /** The client of this admin in the realm. A token issued to another client is refused. */
8
8
  readonly clientId: string;
9
+ /**
10
+ * The service clients whose tokens this admin accepts too: commands that call it without a
11
+ * person. Their rights are the roles of `clientId` on their service account, the same place the
12
+ * rights of a person are read from.
13
+ */
14
+ readonly serviceClients?: readonly string[];
15
+ /**
16
+ * Where the server reads the keys of the realm, when it reaches Keycloak by another address than
17
+ * the browser does: an inner network, a container. By default — the key set of the issuer.
18
+ */
19
+ readonly keysUrl?: string;
9
20
  }
10
21
  /** The token of the `Authorization` header, or `null` when the header is absent or of another form. */
11
22
  export declare function bearerToken(authorization: string | null | undefined): string | null;
@@ -13,7 +24,8 @@ export declare function bearerToken(authorization: string | null | undefined): s
13
24
  * The token check of one admin, shared by the NestJS guard and the Connect interceptor.
14
25
  *
15
26
  * A token is accepted when a key of the realm signed it, the realm issued it, its term holds and
16
- * it was issued to the client of this admin (`azp`). Every other case answers `null` and nothing
27
+ * it was issued to the client of this admin or to a service client it names (`azp`). Every other
28
+ * case answers `null` and nothing
17
29
  * more: the reason of a refusal is not told to the caller, so the answers do not show which tokens
18
30
  * come close.
19
31
  *
@@ -1 +1 @@
1
- {"version":3,"file":"token-verifier.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,eAAe,EAAa,MAAM,MAAM,CAAC;AAElF,OAAO,EAAoB,OAAO,EAAmB,MAAM,yBAAyB,CAAC;AAErF,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC7B;AAID,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGnF;AA0BD;;;;;;;;;;GAUG;AACH,qBAAa,qBAAqB;;gBAIlB,OAAO,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,eAAe;IAK/D,0FAA0F;IAC7E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAe3F"}
1
+ {"version":3,"file":"token-verifier.d.ts","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkC,eAAe,EAAa,MAAM,MAAM,CAAC;AAElF,OAAO,EAAoB,OAAO,EAAmB,MAAM,yBAAyB,CAAC;AAErF,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IAC/B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0FAA0F;IAC1F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGnF;AA0BD;;;;;;;;;;;GAWG;AACH,qBAAa,qBAAqB;;gBAIlB,OAAO,EAAE,kBAAkB,EAAE,IAAI,CAAC,EAAE,eAAe;IAK/D,0FAA0F;IAC7E,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC;CAoB3F"}
@@ -32,7 +32,8 @@ function claimsOf(payload) {
32
32
  * The token check of one admin, shared by the NestJS guard and the Connect interceptor.
33
33
  *
34
34
  * A token is accepted when a key of the realm signed it, the realm issued it, its term holds and
35
- * it was issued to the client of this admin (`azp`). Every other case answers `null` and nothing
35
+ * it was issued to the client of this admin or to a service client it names (`azp`). Every other
36
+ * case answers `null` and nothing
36
37
  * more: the reason of a refusal is not told to the caller, so the answers do not show which tokens
37
38
  * come close.
38
39
  *
@@ -44,7 +45,7 @@ export class KeycloakTokenVerifier {
44
45
  #keys;
45
46
  constructor(options, keys) {
46
47
  this.#options = options;
47
- this.#keys = keys ?? createRemoteJWKSet(new URL(`${options.issuer}/protocol/openid-connect/certs`));
48
+ this.#keys = keys ?? createRemoteJWKSet(new URL(options.keysUrl ?? `${options.issuer}/protocol/openid-connect/certs`));
48
49
  }
49
50
  /** The caller of the `Authorization` header, or `null` when the token is not accepted. */
50
51
  async callerOf(authorization) {
@@ -54,7 +55,7 @@ export class KeycloakTokenVerifier {
54
55
  }
55
56
  try {
56
57
  const { payload } = await jwtVerify(token, this.#keys, { issuer: this.#options.issuer });
57
- if (payload['azp'] !== this.#options.clientId || typeof payload.sub !== 'string') {
58
+ if (!this.#accepts(payload['azp']) || typeof payload.sub !== 'string') {
58
59
  return null;
59
60
  }
60
61
  return callerFromClaims(claimsOf(payload), this.#options.clientId);
@@ -63,5 +64,9 @@ export class KeycloakTokenVerifier {
63
64
  return null;
64
65
  }
65
66
  }
67
+ /** Whether a token issued to this client is accepted: the client of this admin or a service client it names. */
68
+ #accepts(azp) {
69
+ return azp === this.#options.clientId || (typeof azp === 'string' && (this.#options.serviceClients ?? []).includes(azp));
70
+ }
66
71
  }
67
72
  //# sourceMappingURL=token-verifier.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-verifier.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRemoteJWKSet, JWTPayload, JWTVerifyGetKey, jwtVerify } from 'jose';\n\nimport { callerFromClaims, ICaller, IKeycloakClaims } from '@rt-tools/auth-contract';\n\n/** Where the tokens of an admin come from: the realm and the client of this admin. */\nexport interface ITokenCheckOptions {\n /** The realm issuer, for example `https://sso.example.com/realms/rt`. */\n readonly issuer: string;\n /** The client of this admin in the realm. A token issued to another client is refused. */\n readonly clientId: string;\n}\n\nconst BEARER: RegExp = /^Bearer ([A-Za-z0-9\\-_]+\\.[A-Za-z0-9\\-_]+\\.[A-Za-z0-9\\-_]+)$/;\n\n/** The token of the `Authorization` header, or `null` when the header is absent or of another form. */\nexport function bearerToken(authorization: string | null | undefined): string | null {\n const match: RegExpExecArray | null = BEARER.exec(authorization?.trim() ?? '');\n return match ? match[1] : null;\n}\n\nfunction resourceAccessOf(value: unknown): IKeycloakClaims['resource_access'] {\n if (typeof value !== 'object' || value === null) {\n return undefined;\n }\n const access: Record<string, { roles?: readonly string[] }> = {};\n for (const [clientId, entry] of Object.entries(value)) {\n const roles: unknown = typeof entry === 'object' && entry !== null ? Reflect.get(entry, 'roles') : undefined;\n access[clientId] = { roles: Array.isArray(roles) ? roles.filter((role: unknown): role is string => typeof role === 'string') : [] };\n }\n return access;\n}\n\n/** The claims the caller is read from, taken from a verified payload by their shape. */\nfunction claimsOf(payload: JWTPayload): IKeycloakClaims {\n return {\n sub: String(payload.sub),\n email: typeof payload['email'] === 'string' ? payload['email'] : undefined,\n email_verified: payload['email_verified'] === true,\n name: typeof payload['name'] === 'string' ? payload['name'] : undefined,\n preferred_username: typeof payload['preferred_username'] === 'string' ? payload['preferred_username'] : undefined,\n resource_access: resourceAccessOf(payload['resource_access']),\n };\n}\n\n/**\n * The token check of one admin, shared by the NestJS guard and the Connect interceptor.\n *\n * A token is accepted when a key of the realm signed it, the realm issued it, its term holds and\n * it was issued to the client of this admin (`azp`). Every other case answers `null` and nothing\n * more: the reason of a refusal is not told to the caller, so the answers do not show which tokens\n * come close.\n *\n * The keys are fetched from the key set address of the realm and cached by `jose`; a key the\n * realm rotates in is picked up without a restart.\n */\nexport class KeycloakTokenVerifier {\n readonly #options: ITokenCheckOptions;\n readonly #keys: JWTVerifyGetKey;\n\n constructor(options: ITokenCheckOptions, keys?: JWTVerifyGetKey) {\n this.#options = options;\n this.#keys = keys ?? createRemoteJWKSet(new URL(`${options.issuer}/protocol/openid-connect/certs`));\n }\n\n /** The caller of the `Authorization` header, or `null` when the token is not accepted. */\n public async callerOf(authorization: string | null | undefined): Promise<ICaller | null> {\n const token: string | null = bearerToken(authorization);\n if (!token) {\n return null;\n }\n try {\n const { payload } = await jwtVerify(token, this.#keys, { issuer: this.#options.issuer });\n if (payload['azp'] !== this.#options.clientId || typeof payload.sub !== 'string') {\n return null;\n }\n return callerFromClaims(claimsOf(payload), this.#options.clientId);\n } catch {\n return null;\n }\n }\n}\n"]}
1
+ {"version":3,"file":"token-verifier.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/token-verifier.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRemoteJWKSet, JWTPayload, JWTVerifyGetKey, jwtVerify } from 'jose';\n\nimport { callerFromClaims, ICaller, IKeycloakClaims } from '@rt-tools/auth-contract';\n\n/** Where the tokens of an admin come from: the realm and the client of this admin. */\nexport interface ITokenCheckOptions {\n /** The realm issuer, for example `https://sso.example.com/realms/rt`. */\n readonly issuer: string;\n /** The client of this admin in the realm. A token issued to another client is refused. */\n readonly clientId: string;\n /**\n * The service clients whose tokens this admin accepts too: commands that call it without a\n * person. Their rights are the roles of `clientId` on their service account, the same place the\n * rights of a person are read from.\n */\n readonly serviceClients?: readonly string[];\n /**\n * Where the server reads the keys of the realm, when it reaches Keycloak by another address than\n * the browser does: an inner network, a container. By default — the key set of the issuer.\n */\n readonly keysUrl?: string;\n}\n\nconst BEARER: RegExp = /^Bearer ([A-Za-z0-9\\-_]+\\.[A-Za-z0-9\\-_]+\\.[A-Za-z0-9\\-_]+)$/;\n\n/** The token of the `Authorization` header, or `null` when the header is absent or of another form. */\nexport function bearerToken(authorization: string | null | undefined): string | null {\n const match: RegExpExecArray | null = BEARER.exec(authorization?.trim() ?? '');\n return match ? match[1] : null;\n}\n\nfunction resourceAccessOf(value: unknown): IKeycloakClaims['resource_access'] {\n if (typeof value !== 'object' || value === null) {\n return undefined;\n }\n const access: Record<string, { roles?: readonly string[] }> = {};\n for (const [clientId, entry] of Object.entries(value)) {\n const roles: unknown = typeof entry === 'object' && entry !== null ? Reflect.get(entry, 'roles') : undefined;\n access[clientId] = { roles: Array.isArray(roles) ? roles.filter((role: unknown): role is string => typeof role === 'string') : [] };\n }\n return access;\n}\n\n/** The claims the caller is read from, taken from a verified payload by their shape. */\nfunction claimsOf(payload: JWTPayload): IKeycloakClaims {\n return {\n sub: String(payload.sub),\n email: typeof payload['email'] === 'string' ? payload['email'] : undefined,\n email_verified: payload['email_verified'] === true,\n name: typeof payload['name'] === 'string' ? payload['name'] : undefined,\n preferred_username: typeof payload['preferred_username'] === 'string' ? payload['preferred_username'] : undefined,\n resource_access: resourceAccessOf(payload['resource_access']),\n };\n}\n\n/**\n * The token check of one admin, shared by the NestJS guard and the Connect interceptor.\n *\n * A token is accepted when a key of the realm signed it, the realm issued it, its term holds and\n * it was issued to the client of this admin or to a service client it names (`azp`). Every other\n * case answers `null` and nothing\n * more: the reason of a refusal is not told to the caller, so the answers do not show which tokens\n * come close.\n *\n * The keys are fetched from the key set address of the realm and cached by `jose`; a key the\n * realm rotates in is picked up without a restart.\n */\nexport class KeycloakTokenVerifier {\n readonly #options: ITokenCheckOptions;\n readonly #keys: JWTVerifyGetKey;\n\n constructor(options: ITokenCheckOptions, keys?: JWTVerifyGetKey) {\n this.#options = options;\n this.#keys = keys ?? createRemoteJWKSet(new URL(options.keysUrl ?? `${options.issuer}/protocol/openid-connect/certs`));\n }\n\n /** The caller of the `Authorization` header, or `null` when the token is not accepted. */\n public async callerOf(authorization: string | null | undefined): Promise<ICaller | null> {\n const token: string | null = bearerToken(authorization);\n if (!token) {\n return null;\n }\n try {\n const { payload } = await jwtVerify(token, this.#keys, { issuer: this.#options.issuer });\n if (!this.#accepts(payload['azp']) || typeof payload.sub !== 'string') {\n return null;\n }\n return callerFromClaims(claimsOf(payload), this.#options.clientId);\n } catch {\n return null;\n }\n }\n\n /** Whether a token issued to this client is accepted: the client of this admin or a service client it names. */\n #accepts(azp: unknown): boolean {\n return azp === this.#options.clientId || (typeof azp === 'string' && (this.#options.serviceClients ?? []).includes(azp));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rt-tools/auth-server",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Keycloak token check, access declarations and the rights catalog sync for NestJS controllers and Connect procedures",
5
5
  "author": "RT Team",
6
6
  "license": "Apache-2.0",
@@ -32,11 +32,6 @@
32
32
  "reflect-metadata": "^0.2.0",
33
33
  "rxjs": "^7.8.0"
34
34
  },
35
- "peerDependenciesMeta": {
36
- "@connectrpc/connect": {
37
- "optional": true
38
- }
39
- },
40
35
  "devDependencies": {
41
36
  "@connectrpc/connect": "2.2.0"
42
37
  },