@marxbiotech/signet-integration 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +111 -0
  3. package/dist/bearer-challenge.d.ts +27 -0
  4. package/dist/bearer-challenge.filter.d.ts +13 -0
  5. package/dist/bearer-challenge.filter.js +76 -0
  6. package/dist/bearer-challenge.filter.js.map +1 -0
  7. package/dist/bearer-challenge.js +120 -0
  8. package/dist/bearer-challenge.js.map +1 -0
  9. package/dist/config.d.ts +23 -0
  10. package/dist/config.js +56 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/deployment-profile.d.ts +25 -0
  13. package/dist/deployment-profile.js +103 -0
  14. package/dist/deployment-profile.js.map +1 -0
  15. package/dist/guard.d.ts +18 -0
  16. package/dist/guard.js +210 -0
  17. package/dist/guard.js.map +1 -0
  18. package/dist/index.d.ts +15 -0
  19. package/dist/index.js +50 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/jwt-verifier.d.ts +36 -0
  22. package/dist/jwt-verifier.js +167 -0
  23. package/dist/jwt-verifier.js.map +1 -0
  24. package/dist/log-sanitise.d.ts +3 -0
  25. package/dist/log-sanitise.js +23 -0
  26. package/dist/log-sanitise.js.map +1 -0
  27. package/dist/module.d.ts +12 -0
  28. package/dist/module.js +61 -0
  29. package/dist/module.js.map +1 -0
  30. package/dist/options.d.ts +31 -0
  31. package/dist/options.js +9 -0
  32. package/dist/options.js.map +1 -0
  33. package/dist/principal-resolver.d.ts +19 -0
  34. package/dist/principal-resolver.js +25 -0
  35. package/dist/principal-resolver.js.map +1 -0
  36. package/dist/principal.decorator.d.ts +2 -0
  37. package/dist/principal.decorator.js +28 -0
  38. package/dist/principal.decorator.js.map +1 -0
  39. package/dist/protected-resource.controller.d.ts +11 -0
  40. package/dist/protected-resource.controller.js +130 -0
  41. package/dist/protected-resource.controller.js.map +1 -0
  42. package/dist/public.decorator.d.ts +2 -0
  43. package/dist/public.decorator.js +11 -0
  44. package/dist/public.decorator.js.map +1 -0
  45. package/dist/scope-vocabulary.d.ts +17 -0
  46. package/dist/scope-vocabulary.js +52 -0
  47. package/dist/scope-vocabulary.js.map +1 -0
  48. package/dist/strategy.d.ts +16 -0
  49. package/dist/strategy.js +89 -0
  50. package/dist/strategy.js.map +1 -0
  51. package/dist/testing/fixture.d.ts +8 -0
  52. package/dist/testing/fixture.js +38 -0
  53. package/dist/testing/fixture.js.map +1 -0
  54. package/dist/testing/index.d.ts +3 -0
  55. package/dist/testing/index.js +11 -0
  56. package/dist/testing/index.js.map +1 -0
  57. package/dist/testing/jwks-server.d.ts +10 -0
  58. package/dist/testing/jwks-server.js +33 -0
  59. package/dist/testing/jwks-server.js.map +1 -0
  60. package/dist/testing/signet-token.d.ts +18 -0
  61. package/dist/testing/signet-token.js +31 -0
  62. package/dist/testing/signet-token.js.map +1 -0
  63. package/dist/validate-options.d.ts +8 -0
  64. package/dist/validate-options.js +88 -0
  65. package/dist/validate-options.js.map +1 -0
  66. package/package.json +93 -0
@@ -0,0 +1,31 @@
1
+ export interface SignetEnvNames {
2
+ readonly deploymentNamespace: string;
3
+ readonly jwtAudience: string;
4
+ readonly jwtClockToleranceS: string;
5
+ readonly jwtIssuer: string;
6
+ readonly jwtJwksUri: string;
7
+ readonly legacyApiKeyEnabled?: string;
8
+ readonly signetEnabled?: string;
9
+ }
10
+ export interface SignetDeployedProfile {
11
+ readonly host: string;
12
+ readonly namespace: string;
13
+ }
14
+ export interface SignetDevelopmentProfile {
15
+ readonly canonicalResource: string;
16
+ readonly host: string;
17
+ readonly namespace: string;
18
+ }
19
+ export declare const DEVELOPMENT_ENVIRONMENT = "development";
20
+ export interface SignetIntegrationOptions<E extends string = string> {
21
+ readonly admissionScope: string;
22
+ readonly canonicalResourceFor: (host: string) => string;
23
+ readonly deployedProfiles: Readonly<Record<E, SignetDeployedProfile>>;
24
+ readonly developmentProfile: SignetDevelopmentProfile;
25
+ readonly env: SignetEnvNames;
26
+ readonly principalRefusalMessage?: string;
27
+ readonly realm: string;
28
+ readonly requestPrincipalKey: string;
29
+ readonly scopesSupported: readonly string[];
30
+ }
31
+ export declare const SIGNET_INTEGRATION_OPTIONS: unique symbol;
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // What a consumer supplies to the integration: its values, behind the
3
+ // SIGNET_INTEGRATION_OPTIONS token or as a parameter to the pure functions.
4
+ // The mechanism files in this package carry no consumer value of their own.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SIGNET_INTEGRATION_OPTIONS = exports.DEVELOPMENT_ENVIRONMENT = void 0;
7
+ exports.DEVELOPMENT_ENVIRONMENT = 'development';
8
+ exports.SIGNET_INTEGRATION_OPTIONS = Symbol('SIGNET_INTEGRATION_OPTIONS');
9
+ //# sourceMappingURL=options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"options.js","sourceRoot":"","sources":["../src/options.ts"],"names":[],"mappings":";AAAA,sEAAsE;AACtE,4EAA4E;AAC5E,4EAA4E;;;AAoC/D,QAAA,uBAAuB,GAAG,aAAa,CAAC;AAmCxC,QAAA,0BAA0B,GAAG,MAAM,CAAC,4BAA4B,CAAC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type VerifiedSignetIdentity } from './jwt-verifier';
2
+ export interface SignetPrincipalResolver<P = unknown> {
3
+ resolve(identity: VerifiedSignetIdentity): Promise<PrincipalResolution<P>>;
4
+ }
5
+ export type PrincipalResolution<P> = {
6
+ logFields?: Readonly<Record<string, string>>;
7
+ ok: false;
8
+ reason: string;
9
+ } | {
10
+ logFields?: Readonly<Record<string, string>>;
11
+ ok: true;
12
+ principal: P;
13
+ };
14
+ export declare const SIGNET_PRINCIPAL_RESOLVER: unique symbol;
15
+ export declare class PrincipalStoreUnavailableError extends Error {
16
+ readonly causeName: string;
17
+ readonly causeStack: string | undefined;
18
+ constructor(cause: unknown);
19
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrincipalStoreUnavailableError = exports.SIGNET_PRINCIPAL_RESOLVER = void 0;
4
+ exports.SIGNET_PRINCIPAL_RESOLVER = Symbol('SIGNET_PRINCIPAL_RESOLVER');
5
+ // Thrown by a resolver when its STORE could not be reached, as opposed to when
6
+ // it answered. Which errors mean "unreachable" is the resolver's knowledge:
7
+ // a socket error, a DNS failure, a driver exception are facts about HOW its
8
+ // store is queried, and this package carries no driver import.
9
+ //
10
+ // `causeName` and `causeStack` are captured at construction, in the same form
11
+ // the guard's 500 branch uses for an unwrapped error, so the 503 log line
12
+ // reads the cause's class and stack.
13
+ class PrincipalStoreUnavailableError extends Error {
14
+ causeName;
15
+ causeStack;
16
+ constructor(cause) {
17
+ super('authorization store unavailable', { cause });
18
+ this.name = 'PrincipalStoreUnavailableError';
19
+ this.causeName =
20
+ cause instanceof Error && cause.name ? cause.name : typeof cause;
21
+ this.causeStack = cause instanceof Error ? cause.stack : undefined;
22
+ }
23
+ }
24
+ exports.PrincipalStoreUnavailableError = PrincipalStoreUnavailableError;
25
+ //# sourceMappingURL=principal-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal-resolver.js","sourceRoot":"","sources":["../src/principal-resolver.ts"],"names":[],"mappings":";;;AA6Ba,QAAA,yBAAyB,GAAG,MAAM,CAAC,2BAA2B,CAAC,CAAC;AAE7E,+EAA+E;AAC/E,4EAA4E;AAC5E,4EAA4E;AAC5E,+DAA+D;AAC/D,EAAE;AACF,8EAA8E;AAC9E,0EAA0E;AAC1E,qCAAqC;AACrC,MAAa,8BAA+B,SAAQ,KAAK;IAC9C,SAAS,CAAS;IAClB,UAAU,CAAqB;IAExC,YAAY,KAAc;QACxB,KAAK,CAAC,iCAAiC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC,IAAI,GAAG,gCAAgC,CAAC;QAC7C,IAAI,CAAC,SAAS;YACZ,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC;QACnE,IAAI,CAAC,UAAU,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IACrE,CAAC;CACF;AAXD,wEAWC"}
@@ -0,0 +1,2 @@
1
+ import { type SignetIntegrationOptions } from './options';
2
+ export declare function createSignetPrincipalDecorator<P>(options: Pick<SignetIntegrationOptions, 'requestPrincipalKey'>, validate: (value: unknown) => value is P): () => ParameterDecorator;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSignetPrincipalDecorator = createSignetPrincipalDecorator;
4
+ const common_1 = require("@nestjs/common");
5
+ // A parameter decorator that reads the principal the Bearer guard attached
6
+ // under `requestPrincipalKey`. Built per consumer from the same options the
7
+ // guard runs with, because a decorator is evaluated at class definition,
8
+ // before any provider exists, so the key cannot come through injection.
9
+ //
10
+ // `validate` is the runtime shape check: nothing at runtime enforces a
11
+ // principal on a handler someone mounted without the guard, and Nest does not
12
+ // check the parameter's declared type against what arrives. A failing check
13
+ // is a 500, because a missing principal on a protected route is a wiring
14
+ // bug, not a caller's credential problem.
15
+ function createSignetPrincipalDecorator(options, validate) {
16
+ const { requestPrincipalKey } = options;
17
+ return (0, common_1.createParamDecorator)((_data, context) => {
18
+ const request = context
19
+ .switchToHttp()
20
+ .getRequest();
21
+ const value = request[requestPrincipalKey];
22
+ if (!validate(value)) {
23
+ throw new common_1.InternalServerErrorException(`no principal on request.${requestPrincipalKey}: is the Bearer guard mounted on this route?`);
24
+ }
25
+ return value;
26
+ });
27
+ }
28
+ //# sourceMappingURL=principal.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"principal.decorator.js","sourceRoot":"","sources":["../src/principal.decorator.ts"],"names":[],"mappings":";;AAkBA,wEAiBC;AAnCD,2CAIwB;AAIxB,2EAA2E;AAC3E,4EAA4E;AAC5E,yEAAyE;AACzE,wEAAwE;AACxE,EAAE;AACF,uEAAuE;AACvE,8EAA8E;AAC9E,4EAA4E;AAC5E,yEAAyE;AACzE,0CAA0C;AAC1C,SAAgB,8BAA8B,CAC5C,OAA8D,EAC9D,QAAwC;IAExC,MAAM,EAAE,mBAAmB,EAAE,GAAG,OAAO,CAAC;IACxC,OAAO,IAAA,6BAAoB,EAAC,CAAC,KAAc,EAAE,OAAyB,EAAE,EAAE;QACxE,MAAM,OAAO,GAAG,OAAO;aACpB,YAAY,EAAE;aACd,UAAU,EAA2B,CAAC;QACzC,MAAM,KAAK,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC3C,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,qCAA4B,CACpC,2BAA2B,mBAAmB,8CAA8C,CAC7F,CAAC;QACJ,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { type Type } from '@nestjs/common';
2
+ import { type SignetIntegrationOptions } from './options';
3
+ export interface ProtectedResourceMetadata {
4
+ readonly authorization_servers: readonly string[];
5
+ readonly bearer_methods_supported: readonly string[];
6
+ readonly resource: string;
7
+ readonly scopes_supported: readonly string[];
8
+ }
9
+ export declare function createProtectedResourceController(options: SignetIntegrationOptions): Type<{
10
+ metadata(): ProtectedResourceMetadata;
11
+ }>;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.createProtectedResourceController = createProtectedResourceController;
16
+ const common_1 = require("@nestjs/common");
17
+ const config_1 = require("@nestjs/config");
18
+ const swagger_1 = require("@nestjs/swagger");
19
+ const throttler_1 = require("@nestjs/throttler");
20
+ const bearer_challenge_1 = require("./bearer-challenge");
21
+ const config_2 = require("./config");
22
+ const deployment_profile_1 = require("./deployment-profile");
23
+ const options_1 = require("./options");
24
+ const public_decorator_1 = require("./public.decorator");
25
+ const PROTECTED_RESOURCE_METADATA_SCHEMA = {
26
+ properties: {
27
+ authorization_servers: { items: { type: 'string' }, type: 'array' },
28
+ bearer_methods_supported: { items: { type: 'string' }, type: 'array' },
29
+ resource: { type: 'string' },
30
+ scopes_supported: { items: { type: 'string' }, type: 'array' },
31
+ },
32
+ required: [
33
+ 'authorization_servers',
34
+ 'bearer_methods_supported',
35
+ 'resource',
36
+ 'scopes_supported',
37
+ ],
38
+ type: 'object',
39
+ };
40
+ // Tells a client WHICH authorization server issues tokens for this resource
41
+ // (RFC 9728). This controller's whole part in authorisation: it issues
42
+ // nothing, it points. An OAuth client goes 401 → the challenge's
43
+ // `resource_metadata` → this document's `authorization_servers` → issuer
44
+ // discovery → login.
45
+ //
46
+ // A FACTORY rather than a class, because the route path is a decorator
47
+ // argument -- evaluated at class definition, before any provider exists --
48
+ // and it is derived from the consumer's canonical resource. The consumer
49
+ // calls this once with its options and declares the returned class in the
50
+ // module of the process that serves the resource. Nothing else about the
51
+ // class depends on the options at definition time; the values it publishes
52
+ // are read through injection, from the SAME settings the verifier uses,
53
+ // never a second set -- metadata and verification that can be configured
54
+ // apart will drift apart.
55
+ //
56
+ // Public: a client that has no token yet is the one that needs to read it.
57
+ // Throttling is skipped: discovery precedes login and must not compete with
58
+ // a client's own request budget.
59
+ //
60
+ // The factory's `options` choose the route; the injected options choose the
61
+ // published values. They are the same object in every sane wiring, and the
62
+ // constructor refuses to start if they disagree on the metadata path.
63
+ function createProtectedResourceController(options) {
64
+ var ProtectedResourceController_1;
65
+ const mountedPath = (0, bearer_challenge_1.protectedResourceMetadataPath)(options);
66
+ let ProtectedResourceController = ProtectedResourceController_1 = class ProtectedResourceController {
67
+ configService;
68
+ profiles;
69
+ injected;
70
+ logger = new common_1.Logger(ProtectedResourceController_1.name);
71
+ config;
72
+ constructor(configService, profiles, injected) {
73
+ this.configService = configService;
74
+ this.profiles = profiles;
75
+ this.injected = injected;
76
+ const injectedPath = (0, bearer_challenge_1.protectedResourceMetadataPath)(injected);
77
+ if (injectedPath !== mountedPath) {
78
+ throw new Error(`protected resource metadata is mounted at ${mountedPath} but the injected options put it at ${injectedPath}; pass the same options to createProtectedResourceController and to SignetIntegrationModule.forRoot`);
79
+ }
80
+ }
81
+ metadata() {
82
+ this.config ??= (0, config_2.readSignetAuthConfig)(this.injected, this.configService, this.profiles.profile);
83
+ const { jwt } = this.config;
84
+ if (jwt === null) {
85
+ // The 404 body says nothing; this line is what tells it apart from
86
+ // an ingress or routing miss.
87
+ this.logger.warn('protected resource metadata not served: metric=metadata_channel_disabled reason=signet_disabled');
88
+ throw new common_1.NotFoundException();
89
+ }
90
+ return {
91
+ authorization_servers: [jwt.issuer],
92
+ bearer_methods_supported: ['header'],
93
+ resource: jwt.audience,
94
+ scopes_supported: this.injected.scopesSupported,
95
+ };
96
+ }
97
+ };
98
+ __decorate([
99
+ (0, common_1.Get)(),
100
+ (0, throttler_1.SkipThrottle)(),
101
+ (0, swagger_1.ApiOkResponse)({
102
+ description: 'RFC 9728 protected resource metadata',
103
+ schema: PROTECTED_RESOURCE_METADATA_SCHEMA,
104
+ }),
105
+ (0, swagger_1.ApiNotFoundResponse)({
106
+ description: 'The Signet Bearer channel is disabled in this deployment',
107
+ }),
108
+ (0, swagger_1.ApiOperation)({
109
+ description: 'OAuth 2.0 Protected Resource Metadata (RFC 9728) for this ' +
110
+ 'resource. Public; no credential required. Available only while ' +
111
+ 'the Signet Bearer channel is enabled -- 404 otherwise, so a ' +
112
+ 'discovery client is never pointed at an issuer this deployment does ' +
113
+ 'not accept tokens from.',
114
+ summary: 'Protected resource metadata',
115
+ }),
116
+ __metadata("design:type", Function),
117
+ __metadata("design:paramtypes", []),
118
+ __metadata("design:returntype", Object)
119
+ ], ProtectedResourceController.prototype, "metadata", null);
120
+ ProtectedResourceController = ProtectedResourceController_1 = __decorate([
121
+ (0, swagger_1.ApiTags)('OAuth'),
122
+ (0, public_decorator_1.Public)(),
123
+ (0, common_1.Controller)(mountedPath),
124
+ __param(2, (0, common_1.Inject)(options_1.SIGNET_INTEGRATION_OPTIONS)),
125
+ __metadata("design:paramtypes", [config_1.ConfigService,
126
+ deployment_profile_1.SignetDeploymentProfileService, Object])
127
+ ], ProtectedResourceController);
128
+ return ProtectedResourceController;
129
+ }
130
+ //# sourceMappingURL=protected-resource.controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protected-resource.controller.js","sourceRoot":"","sources":["../src/protected-resource.controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare const IS_PUBLIC_ROUTE = "signet-integration:public-route";
2
+ export declare const Public: () => ClassDecorator & MethodDecorator;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Public = exports.IS_PUBLIC_ROUTE = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ // Marks a route, or a whole controller, as reachable without a Bearer
6
+ // credential. SignetBearerGuard lets a marked route through before running
7
+ // the strategy; a consumer's own credential dispatcher reads the same key.
8
+ exports.IS_PUBLIC_ROUTE = 'signet-integration:public-route';
9
+ const Public = () => (0, common_1.SetMetadata)(exports.IS_PUBLIC_ROUTE, true);
10
+ exports.Public = Public;
11
+ //# sourceMappingURL=public.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public.decorator.js","sourceRoot":"","sources":["../src/public.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAE7C,sEAAsE;AACtE,2EAA2E;AAC3E,2EAA2E;AAC9D,QAAA,eAAe,GAAG,iCAAiC,CAAC;AAE1D,MAAM,MAAM,GAAG,GAAqC,EAAE,CAC3D,IAAA,oBAAW,EAAC,uBAAe,EAAE,IAAI,CAAC,CAAC;AADxB,QAAA,MAAM,UACkB"}
@@ -0,0 +1,17 @@
1
+ export interface TokenScopeRead<A extends string> {
2
+ readonly actions: ReadonlySet<A>;
3
+ }
4
+ export interface ScopeVocabularyInput<A extends string> {
5
+ readonly admissionScope: string;
6
+ readonly actions: readonly A[];
7
+ readonly defaultActions: ReadonlySet<A>;
8
+ readonly tokenScopeFor?: (action: A) => string;
9
+ }
10
+ export interface ScopeVocabulary<A extends string> {
11
+ readonly actionByScope: ReadonlyMap<string, A>;
12
+ readonly readTokenScopes: (scopes: readonly string[]) => TokenScopeRead<A>;
13
+ readonly scopesForAction: (action: A, held: ReadonlySet<A>) => readonly string[];
14
+ readonly supportedScopes: readonly string[];
15
+ readonly tokenScopeFor: (action: A) => string;
16
+ }
17
+ export declare function createScopeVocabulary<A extends string>(input: ScopeVocabularyInput<A>): ScopeVocabulary<A>;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // Token-level narrowing: one token carries no more than the scopes it was
3
+ // issued with, even when its holder's local authorization says more. The
4
+ // consumer supplies its action vocabulary; this file supplies the rules.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createScopeVocabulary = createScopeVocabulary;
7
+ function createScopeVocabulary(input) {
8
+ const tokenScopeFor = input.tokenScopeFor ?? ((action) => action);
9
+ const actionByScope = new Map(input.actions.map((action) => [tokenScopeFor(action), action]));
10
+ // The invariants the rest of this file relies on, refused rather than
11
+ // silently repaired: a non-injective encoder would make the Map keep the
12
+ // later action (a scope grants the wrong thing and `supportedScopes` loses
13
+ // one); an action spelled as the admission scope would make admission grant
14
+ // a business action; a default outside the vocabulary could never be
15
+ // narrowed away.
16
+ if (actionByScope.size !== new Set(input.actions).size) {
17
+ throw new Error('scope vocabulary: tokenScopeFor must map actions to distinct scopes');
18
+ }
19
+ if (actionByScope.has(input.admissionScope)) {
20
+ throw new Error(`scope vocabulary: an action is spelled as the admission scope ${input.admissionScope}`);
21
+ }
22
+ for (const action of input.defaultActions) {
23
+ if (!input.actions.includes(action)) {
24
+ throw new Error(`scope vocabulary: default action ${String(action)} is not in actions`);
25
+ }
26
+ }
27
+ return {
28
+ actionByScope,
29
+ readTokenScopes: (scopes) => {
30
+ const actions = new Set();
31
+ for (const scope of scopes) {
32
+ const action = actionByScope.get(scope);
33
+ if (action !== undefined)
34
+ actions.add(action);
35
+ }
36
+ if (actions.size > 0)
37
+ return { actions };
38
+ return { actions: input.defaultActions };
39
+ },
40
+ scopesForAction: (action, held) => {
41
+ const actions = new Set(held);
42
+ actions.add(action);
43
+ return [input.admissionScope, ...[...actions].map(tokenScopeFor).sort()];
44
+ },
45
+ supportedScopes: [
46
+ input.admissionScope,
47
+ ...[...actionByScope.keys()].sort(),
48
+ ],
49
+ tokenScopeFor,
50
+ };
51
+ }
52
+ //# sourceMappingURL=scope-vocabulary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-vocabulary.js","sourceRoot":"","sources":["../src/scope-vocabulary.ts"],"names":[],"mappings":";AAAA,0EAA0E;AAC1E,yEAAyE;AACzE,yEAAyE;;AAkDzE,sDAoDC;AApDD,SAAgB,qBAAqB,CACnC,KAA8B;IAE9B,MAAM,aAAa,GAAG,KAAK,CAAC,aAAa,IAAI,CAAC,CAAC,MAAS,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC;IACrE,MAAM,aAAa,GAA2B,IAAI,GAAG,CACnD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAC/D,CAAC;IACF,sEAAsE;IACtE,yEAAyE;IACzE,2EAA2E;IAC3E,4EAA4E;IAC5E,qEAAqE;IACrE,iBAAiB;IACjB,IAAI,aAAa,CAAC,IAAI,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QACvD,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IACD,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,iEAAiE,KAAK,CAAC,cAAc,EAAE,CACxF,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CACb,oCAAoC,MAAM,CAAC,MAAM,CAAC,oBAAoB,CACvE,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO;QACL,aAAa;QACb,eAAe,EAAE,CAAC,MAAM,EAAE,EAAE;YAC1B,MAAM,OAAO,GAAG,IAAI,GAAG,EAAK,CAAC;YAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBACxC,IAAI,MAAM,KAAK,SAAS;oBAAE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAChD,CAAC;YACD,IAAI,OAAO,CAAC,IAAI,GAAG,CAAC;gBAAE,OAAO,EAAE,OAAO,EAAE,CAAC;YACzC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,cAAc,EAAE,CAAC;QAC3C,CAAC;QACD,eAAe,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE;YAChC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9B,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACpB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC3E,CAAC;QACD,eAAe,EAAE;YACf,KAAK,CAAC,cAAc;YACpB,GAAG,CAAC,GAAG,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;SACpC;QACD,aAAa;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { ConfigService } from '@nestjs/config';
2
+ import { type Request } from 'express';
3
+ import { SignetDeploymentProfileService } from './deployment-profile';
4
+ import { type VerifiedSignetIdentity } from './jwt-verifier';
5
+ import { type SignetIntegrationOptions } from './options';
6
+ export declare const SIGNET_JWT_STRATEGY = "signet-jwt";
7
+ declare const SignetJwtStrategy_base: new (...args: any[]) => import("passport-custom");
8
+ export declare class SignetJwtStrategy extends SignetJwtStrategy_base {
9
+ private readonly logger;
10
+ private readonly verifier;
11
+ constructor(configService: ConfigService, profiles: SignetDeploymentProfileService, options: SignetIntegrationOptions);
12
+ validate(request: Request): Promise<VerifiedSignetIdentity | false>;
13
+ private reject;
14
+ }
15
+ export declare function bearerToken(request: Request): string | undefined;
16
+ export {};
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var SignetJwtStrategy_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.SignetJwtStrategy = exports.SIGNET_JWT_STRATEGY = void 0;
17
+ exports.bearerToken = bearerToken;
18
+ const common_1 = require("@nestjs/common");
19
+ const config_1 = require("@nestjs/config");
20
+ const passport_1 = require("@nestjs/passport");
21
+ const passport_custom_1 = require("passport-custom");
22
+ const config_2 = require("./config");
23
+ const deployment_profile_1 = require("./deployment-profile");
24
+ const jwt_verifier_1 = require("./jwt-verifier");
25
+ const log_sanitise_1 = require("./log-sanitise");
26
+ const options_1 = require("./options");
27
+ exports.SIGNET_JWT_STRATEGY = 'signet-jwt';
28
+ // Extracts `Authorization: Bearer <jwt>` and hands it to JwtVerifier.
29
+ // passport-custom rather than passport-jwt: passport-jwt is a second JWT
30
+ // implementation (jsonwebtoken) with its own JWKS story, and two verifiers
31
+ // means two places to get the algorithm allowlist right. Only Passport's
32
+ // registration and dispatch are borrowed; the cryptography stays in one place.
33
+ //
34
+ // Failure is `false`, never a throw. Passport moves to the next strategy only
35
+ // on fail(); error() short-circuits the chain, and Nest turns a thrown
36
+ // validate() into error(). A throw would also replace the guard's uniform 401
37
+ // with whatever the exception said.
38
+ //
39
+ // The log line carries the reason and the source address and NOTHING derived
40
+ // from the token: no fingerprint, no sub.
41
+ let SignetJwtStrategy = SignetJwtStrategy_1 = class SignetJwtStrategy extends (0, passport_1.PassportStrategy)(passport_custom_1.Strategy, exports.SIGNET_JWT_STRATEGY) {
42
+ logger = new common_1.Logger(SignetJwtStrategy_1.name);
43
+ verifier;
44
+ constructor(configService, profiles, options) {
45
+ super();
46
+ const config = (0, config_2.readSignetAuthConfig)(options, configService, profiles.profile);
47
+ if (config.signetEnabled) {
48
+ // Constructed once per process: the remote JWKS cache lives on it.
49
+ this.verifier = new jwt_verifier_1.JwtVerifier(config.jwt);
50
+ this.logger.log(`signet jwt verification loaded: iss=${config.jwt.issuer} aud=${config.jwt.audience}`);
51
+ }
52
+ else {
53
+ this.verifier = null;
54
+ }
55
+ }
56
+ async validate(request) {
57
+ if (this.verifier === null)
58
+ return this.reject(request, 'signet_disabled');
59
+ const token = bearerToken(request);
60
+ if (token === undefined) {
61
+ const present = typeof request.headers.authorization === 'string';
62
+ return this.reject(request, present ? 'header_malformed' : 'header_missing');
63
+ }
64
+ const result = await this.verifier.verify(token);
65
+ if (!result.ok)
66
+ return this.reject(request, result.reason);
67
+ return result.identity;
68
+ }
69
+ // Every false path goes through here, so there is no unlogged rejection.
70
+ reject(request, reason) {
71
+ this.logger.warn(`signet auth rejected: metric=signet_auth_rejected reason=${(0, log_sanitise_1.sanitiseLogToken)(reason)} source=${(0, log_sanitise_1.sanitiseLogToken)(request.ip ?? 'none')} method=${(0, log_sanitise_1.sanitiseLogToken)(request.method)} route=${(0, log_sanitise_1.sanitiseLogToken)((0, log_sanitise_1.truncate)(request.originalUrl, log_sanitise_1.REFUSAL_ROUTE_MAX))}`);
72
+ return false;
73
+ }
74
+ };
75
+ exports.SignetJwtStrategy = SignetJwtStrategy;
76
+ exports.SignetJwtStrategy = SignetJwtStrategy = SignetJwtStrategy_1 = __decorate([
77
+ (0, common_1.Injectable)(),
78
+ __param(2, (0, common_1.Inject)(options_1.SIGNET_INTEGRATION_OPTIONS)),
79
+ __metadata("design:paramtypes", [config_1.ConfigService,
80
+ deployment_profile_1.SignetDeploymentProfileService, Object])
81
+ ], SignetJwtStrategy);
82
+ // `authorization` is `string | undefined` on IncomingHttpHeaders -- Node
83
+ // discards duplicates of this header, so a dispatcher that must refuse
84
+ // repeats counts them on rawHeaders instead. Scheme matching is
85
+ // case-insensitive per RFC 7235; the token is taken verbatim.
86
+ function bearerToken(request) {
87
+ return /^Bearer +(\S+)$/i.exec(request.headers.authorization?.trim() ?? '')?.[1];
88
+ }
89
+ //# sourceMappingURL=strategy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strategy.js","sourceRoot":"","sources":["../src/strategy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { type SignetIntegrationOptions } from '../options';
2
+ export declare const FIXTURE_OPTIONS: SignetIntegrationOptions<'production' | 'staging'>;
3
+ export declare const FIXTURE_DEVELOPMENT_PROFILE: {
4
+ environment: "development";
5
+ canonicalResource: string;
6
+ host: string;
7
+ namespace: string;
8
+ };
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FIXTURE_DEVELOPMENT_PROFILE = exports.FIXTURE_OPTIONS = void 0;
4
+ // A complete options object for a fictional second service, for tests of
5
+ // the mechanism and for consumers' tests that need "some other service".
6
+ // Its canonical resource keeps a PATH (`/mcp`) so every derived path and URL
7
+ // differs from a bare-origin consumer's; a test that needs the bare-origin
8
+ // shape spreads over it with `canonicalResourceFor: (h) => \`https://${h}\``.
9
+ exports.FIXTURE_OPTIONS = {
10
+ admissionScope: 'acme:access',
11
+ canonicalResourceFor: (host) => `https://${host}/mcp`,
12
+ deployedProfiles: {
13
+ production: { host: 'acme.example', namespace: 'acme-production' },
14
+ staging: { host: 'acme.staging.example', namespace: 'acme-staging' },
15
+ },
16
+ developmentProfile: {
17
+ canonicalResource: 'http://localhost/mcp',
18
+ host: 'localhost',
19
+ namespace: 'development',
20
+ },
21
+ env: {
22
+ deploymentNamespace: 'ACME_DEPLOYMENT_NAMESPACE',
23
+ jwtAudience: 'ACME_JWT_AUDIENCE',
24
+ jwtClockToleranceS: 'ACME_JWT_CLOCK_TOLERANCE_S',
25
+ jwtIssuer: 'ACME_JWT_ISSUER',
26
+ jwtJwksUri: 'ACME_JWT_JWKS_URI',
27
+ legacyApiKeyEnabled: 'ACME_LEGACY_ENABLED',
28
+ signetEnabled: 'ACME_SIGNET_ENABLED',
29
+ },
30
+ realm: 'acme',
31
+ requestPrincipalKey: 'principal',
32
+ scopesSupported: ['acme:access', 'widgets:read', 'widgets:write'],
33
+ };
34
+ exports.FIXTURE_DEVELOPMENT_PROFILE = {
35
+ ...exports.FIXTURE_OPTIONS.developmentProfile,
36
+ environment: 'development',
37
+ };
38
+ //# sourceMappingURL=fixture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture.js","sourceRoot":"","sources":["../../src/testing/fixture.ts"],"names":[],"mappings":";;;AAEA,yEAAyE;AACzE,yEAAyE;AACzE,6EAA6E;AAC7E,2EAA2E;AAC3E,8EAA8E;AACjE,QAAA,eAAe,GAExB;IACF,cAAc,EAAE,aAAa;IAC7B,oBAAoB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,WAAW,IAAI,MAAM;IACrD,gBAAgB,EAAE;QAChB,UAAU,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,iBAAiB,EAAE;QAClE,OAAO,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,SAAS,EAAE,cAAc,EAAE;KACrE;IACD,kBAAkB,EAAE;QAClB,iBAAiB,EAAE,sBAAsB;QACzC,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,aAAa;KACzB;IACD,GAAG,EAAE;QACH,mBAAmB,EAAE,2BAA2B;QAChD,WAAW,EAAE,mBAAmB;QAChC,kBAAkB,EAAE,4BAA4B;QAChD,SAAS,EAAE,iBAAiB;QAC5B,UAAU,EAAE,mBAAmB;QAC/B,mBAAmB,EAAE,qBAAqB;QAC1C,aAAa,EAAE,qBAAqB;KACrC;IACD,KAAK,EAAE,MAAM;IACb,mBAAmB,EAAE,WAAW;IAChC,eAAe,EAAE,CAAC,aAAa,EAAE,cAAc,EAAE,eAAe,CAAC;CAClE,CAAC;AAEW,QAAA,2BAA2B,GAAG;IACzC,GAAG,uBAAe,CAAC,kBAAkB;IACrC,WAAW,EAAE,aAAsB;CACpC,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { FIXTURE_DEVELOPMENT_PROFILE, FIXTURE_OPTIONS } from './fixture';
2
+ export { type JwksServer, startJwksServer } from './jwks-server';
3
+ export { startTestIssuer, type TestIssuer } from './signet-token';
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startTestIssuer = exports.startJwksServer = exports.FIXTURE_OPTIONS = exports.FIXTURE_DEVELOPMENT_PROFILE = void 0;
4
+ var fixture_1 = require("./fixture");
5
+ Object.defineProperty(exports, "FIXTURE_DEVELOPMENT_PROFILE", { enumerable: true, get: function () { return fixture_1.FIXTURE_DEVELOPMENT_PROFILE; } });
6
+ Object.defineProperty(exports, "FIXTURE_OPTIONS", { enumerable: true, get: function () { return fixture_1.FIXTURE_OPTIONS; } });
7
+ var jwks_server_1 = require("./jwks-server");
8
+ Object.defineProperty(exports, "startJwksServer", { enumerable: true, get: function () { return jwks_server_1.startJwksServer; } });
9
+ var signet_token_1 = require("./signet-token");
10
+ Object.defineProperty(exports, "startTestIssuer", { enumerable: true, get: function () { return signet_token_1.startTestIssuer; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/testing/index.ts"],"names":[],"mappings":";;;AAAA,qCAAyE;AAAhE,sHAAA,2BAA2B,OAAA;AAAE,0GAAA,eAAe,OAAA;AACrD,6CAAiE;AAAvC,8GAAA,eAAe,OAAA;AACzC,+CAAkE;AAAzD,+GAAA,eAAe,OAAA"}
@@ -0,0 +1,10 @@
1
+ import { type JSONWebKeySet } from 'jose';
2
+ export interface JwksServer {
3
+ readonly calls: () => number;
4
+ readonly close: () => Promise<void>;
5
+ readonly fail: (status: number) => void;
6
+ readonly serve: (jwks: JSONWebKeySet) => void;
7
+ readonly serveRaw: (body: string) => void;
8
+ readonly url: string;
9
+ }
10
+ export declare function startJwksServer(jwks: JSONWebKeySet): Promise<JwksServer>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startJwksServer = startJwksServer;
4
+ const node_http_1 = require("node:http");
5
+ async function startJwksServer(jwks) {
6
+ let body = JSON.stringify(jwks);
7
+ let status = 200;
8
+ let calls = 0;
9
+ const server = (0, node_http_1.createServer)((_req, res) => {
10
+ calls += 1;
11
+ res.writeHead(status, { 'content-type': 'application/json' });
12
+ res.end(body);
13
+ });
14
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
15
+ const { port } = server.address();
16
+ return {
17
+ calls: () => calls,
18
+ close: () => new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))),
19
+ fail: (next) => {
20
+ status = next;
21
+ },
22
+ serve: (next) => {
23
+ body = JSON.stringify(next);
24
+ status = 200;
25
+ },
26
+ serveRaw: (next) => {
27
+ body = next;
28
+ status = 200;
29
+ },
30
+ url: `http://127.0.0.1:${port}/.well-known/jwks.json`,
31
+ };
32
+ }
33
+ //# sourceMappingURL=jwks-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwks-server.js","sourceRoot":"","sources":["../../src/testing/jwks-server.ts"],"names":[],"mappings":";;AAwBA,0CAgCC;AAxDD,yCAAsD;AAwB/C,KAAK,UAAU,eAAe,CACnC,IAAmB;IAEnB,IAAI,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,MAAM,GAAG,GAAG,CAAC;IACjB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,MAAM,GAAW,IAAA,wBAAY,EAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE;QAChD,KAAK,IAAI,CAAC,CAAC;QACX,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChB,CAAC,CAAC,CAAC;IACH,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,CAAC,OAAO,EAAiB,CAAC;IACjD,OAAO;QACL,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK;QAClB,KAAK,EAAE,GAAG,EAAE,CACV,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,CACpC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC7D;QACH,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;YACb,MAAM,GAAG,IAAI,CAAC;QAChB,CAAC;QACD,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE;YACd,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC5B,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;QACD,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE;YACjB,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,GAAG,GAAG,CAAC;QACf,CAAC;QACD,GAAG,EAAE,oBAAoB,IAAI,wBAAwB;KACtD,CAAC;AACJ,CAAC"}