@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.
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/dist/bearer-challenge.d.ts +27 -0
- package/dist/bearer-challenge.filter.d.ts +13 -0
- package/dist/bearer-challenge.filter.js +76 -0
- package/dist/bearer-challenge.filter.js.map +1 -0
- package/dist/bearer-challenge.js +120 -0
- package/dist/bearer-challenge.js.map +1 -0
- package/dist/config.d.ts +23 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/deployment-profile.d.ts +25 -0
- package/dist/deployment-profile.js +103 -0
- package/dist/deployment-profile.js.map +1 -0
- package/dist/guard.d.ts +18 -0
- package/dist/guard.js +210 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt-verifier.d.ts +36 -0
- package/dist/jwt-verifier.js +167 -0
- package/dist/jwt-verifier.js.map +1 -0
- package/dist/log-sanitise.d.ts +3 -0
- package/dist/log-sanitise.js +23 -0
- package/dist/log-sanitise.js.map +1 -0
- package/dist/module.d.ts +12 -0
- package/dist/module.js +61 -0
- package/dist/module.js.map +1 -0
- package/dist/options.d.ts +31 -0
- package/dist/options.js +9 -0
- package/dist/options.js.map +1 -0
- package/dist/principal-resolver.d.ts +19 -0
- package/dist/principal-resolver.js +25 -0
- package/dist/principal-resolver.js.map +1 -0
- package/dist/principal.decorator.d.ts +2 -0
- package/dist/principal.decorator.js +28 -0
- package/dist/principal.decorator.js.map +1 -0
- package/dist/protected-resource.controller.d.ts +11 -0
- package/dist/protected-resource.controller.js +130 -0
- package/dist/protected-resource.controller.js.map +1 -0
- package/dist/public.decorator.d.ts +2 -0
- package/dist/public.decorator.js +11 -0
- package/dist/public.decorator.js.map +1 -0
- package/dist/scope-vocabulary.d.ts +17 -0
- package/dist/scope-vocabulary.js +52 -0
- package/dist/scope-vocabulary.js.map +1 -0
- package/dist/strategy.d.ts +16 -0
- package/dist/strategy.js +89 -0
- package/dist/strategy.js.map +1 -0
- package/dist/testing/fixture.d.ts +8 -0
- package/dist/testing/fixture.js +38 -0
- package/dist/testing/fixture.js.map +1 -0
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +11 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/jwks-server.d.ts +10 -0
- package/dist/testing/jwks-server.js +33 -0
- package/dist/testing/jwks-server.js.map +1 -0
- package/dist/testing/signet-token.d.ts +18 -0
- package/dist/testing/signet-token.js +31 -0
- package/dist/testing/signet-token.js.map +1 -0
- package/dist/validate-options.d.ts +8 -0
- package/dist/validate-options.js +88 -0
- package/dist/validate-options.js.map +1 -0
- 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;
|
package/dist/options.js
ADDED
|
@@ -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,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,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 {};
|
package/dist/strategy.js
ADDED
|
@@ -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,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"}
|