@rt-tools/auth-server 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/lib/access-audit.js +4 -4
- package/cjs/lib/access-audit.js.map +1 -1
- package/cjs/lib/auth-server.module.js +12 -12
- package/cjs/lib/auth-server.module.js.map +1 -1
- package/cjs/lib/auth.guard.js +5 -5
- package/cjs/lib/auth.guard.js.map +1 -1
- package/cjs/lib/current-caller.decorator.js +2 -2
- package/cjs/lib/current-caller.decorator.js.map +1 -1
- package/esm/lib/access-audit.js +2 -2
- package/esm/lib/access-audit.js.map +1 -1
- package/esm/lib/auth-server.module.d.ts +2 -2
- package/esm/lib/auth-server.module.d.ts.map +1 -1
- package/esm/lib/auth-server.module.js +5 -5
- package/esm/lib/auth-server.module.js.map +1 -1
- package/esm/lib/auth.guard.d.ts +1 -1
- package/esm/lib/auth.guard.d.ts.map +1 -1
- package/esm/lib/auth.guard.js +2 -2
- package/esm/lib/auth.guard.js.map +1 -1
- package/esm/lib/connect-access.d.ts +2 -2
- package/esm/lib/connect-access.d.ts.map +1 -1
- package/esm/lib/connect-access.js.map +1 -1
- package/esm/lib/current-caller.decorator.js +1 -1
- package/esm/lib/current-caller.decorator.js.map +1 -1
- package/esm/lib/env-options.d.ts +1 -1
- package/esm/lib/env-options.d.ts.map +1 -1
- package/esm/lib/env-options.js.map +1 -1
- package/package.json +1 -1
package/cjs/lib/access-audit.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.undeclaredAccess = undeclaredAccess;
|
|
4
4
|
exports.accessAuditError = accessAuditError;
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const constants_js_1 = require("@nestjs/common/constants.js");
|
|
6
|
+
const access_js_1 = require("./access.js");
|
|
7
7
|
function isRoute(handler) {
|
|
8
|
-
return Reflect.getMetadata(
|
|
8
|
+
return Reflect.getMetadata(constants_js_1.PATH_METADATA, handler) !== undefined && Reflect.getMetadata(constants_js_1.METHOD_METADATA, handler) !== undefined;
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* The route handlers of the controllers that declare no access or more than one, as
|
|
@@ -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,
|
|
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,
|
|
1
|
+
{"version":3,"file":"access-audit.js","sourceRoot":"","sources":["../../../../projects/auth-server/src/lib/access-audit.ts"],"names":[],"mappings":";;AAkBA,4CAgBC;AAGD,4CAGC;AAxCD,8DAA6E;AAE7E,2CAAmD;AAKnD,SAAS,OAAO,CAAC,OAAe;IAC5B,OAAO,OAAO,CAAC,WAAW,CAAC,4BAAa,EAAE,OAAO,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,WAAW,CAAC,8BAAe,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
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
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,
|
|
32
|
+
const faults = (0, access_audit_js_1.undeclaredAccess)(controllers);
|
|
33
33
|
if (faults.length) {
|
|
34
|
-
throw (0,
|
|
34
|
+
throw (0, access_audit_js_1.accessAuditError)(faults);
|
|
35
35
|
}
|
|
36
36
|
if (this.#options.sync) {
|
|
37
|
-
const result = await (0,
|
|
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:
|
|
55
|
-
{ provide:
|
|
56
|
-
{ provide: core_1.APP_GUARD, useClass:
|
|
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: [
|
|
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,
|
|
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"}
|
package/cjs/lib/auth.guard.js
CHANGED
|
@@ -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
|
|
8
|
-
const
|
|
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,
|
|
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[
|
|
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)(
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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"}
|
package/esm/lib/access-audit.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants';
|
|
2
|
-
import { accessDeclarationsOf } from './access';
|
|
1
|
+
import { METHOD_METADATA, PATH_METADATA } from '@nestjs/common/constants.js';
|
|
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,
|
|
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,6BAA6B,CAAC;AAE7E,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.js';\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,
|
|
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
|
*
|
|
@@ -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,
|
|
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.js';\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"]}
|
package/esm/lib/auth.guard.d.ts
CHANGED
|
@@ -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,
|
|
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"}
|
package/esm/lib/auth.guard.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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,
|
|
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"]}
|
package/esm/lib/env-options.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TPermission } from '@rt-tools/auth-contract';
|
|
2
|
-
import type { IAuthServerOptions } from './auth-server.module';
|
|
2
|
+
import type { IAuthServerOptions } from './auth-server.module.js';
|
|
3
3
|
/** The environment variables the entry module is set up by; `process.env` fits it as it is. */
|
|
4
4
|
export interface IAuthEnv {
|
|
5
5
|
readonly AUTH_ISSUER?: string;
|
|
@@ -1 +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,
|
|
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"}
|
|
@@ -1 +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';\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"]}
|
|
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"]}
|
package/package.json
CHANGED