@decohere/http 0.0.1-dev.126 → 0.0.1-dev.5
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/README.md +2 -2
- package/package.json +4 -13
- package/src/app/app.ts +147 -0
- package/src/app/module.ts +19 -0
- package/src/auth/ability.ts +56 -0
- package/src/auth/auth.config.ts +34 -0
- package/src/auth/auth.extensions.ts +115 -0
- package/src/auth/auth.types.ts +124 -0
- package/src/auth/casl-authorization.service.ts +62 -0
- package/{types/src/auth/principal-provider.d.ts → src/auth/principal-provider.ts} +13 -5
- package/src/auth/safe-outbound-url.ts +117 -0
- package/src/auth/token-service.ts +192 -0
- package/src/builder/builder.ts +118 -0
- package/{es6/src/const.js → src/const.ts} +25 -17
- package/src/health/health.controller.ts +34 -0
- package/src/health/health.extensions.ts +53 -0
- package/src/health/readiness.service.ts +33 -0
- package/{types/src/health/readiness.types.d.ts → src/health/readiness.types.ts} +4 -8
- package/src/hosting/hosted-service-host.ts +102 -0
- package/src/hosting/hosted-service.ts +31 -0
- package/src/http/http.compression.ts +38 -0
- package/src/http/http.extensions.ts +244 -0
- package/{types/src/http/http.options.store.d.ts → src/http/http.options.store.ts} +11 -1
- package/src/http/http.options.ts +41 -0
- package/{types/src/http/http.security.d.ts → src/http/http.security.ts} +16 -10
- package/src/http/http.static.ts +359 -0
- package/{es6/src/index.js → src/index.ts} +1 -0
- package/src/openapi/index.ts +22 -0
- package/src/openapi/openapi.generator.ts +78 -0
- package/src/openapi/openapi.types.ts +60 -0
- package/src/openapi/request-schema.ts +155 -0
- package/src/openapi/request-validator.ts +95 -0
- package/src/providers/fastify.provider.ts +454 -0
- package/src/routing/anvil.types.ts +150 -0
- package/src/routing/decorators/argument.decorator.ts +33 -0
- package/src/routing/decorators/auth.decorator.ts +79 -0
- package/src/routing/decorators/controller.decorator.ts +9 -0
- package/src/routing/decorators/field.decorator.ts +15 -0
- package/src/routing/decorators/request.decorator.ts +34 -0
- package/src/routing/decorators/response.decorator.ts +18 -0
- package/src/routing/decorators/route.decorator.ts +52 -0
- package/src/routing/filters/exception-filter-host.ts +90 -0
- package/src/routing/filters/exception-filter.ts +29 -0
- package/src/routing/middleware/http-access-log.middleware.ts +36 -0
- package/src/routing/middleware/middleware.ts +26 -0
- package/src/routing/middleware/request-id.ts +18 -0
- package/src/routing/path.util.ts +38 -0
- package/src/routing/pipes/pipes.ts +132 -0
- package/src/routing/services/route-argument-resolver.ts +92 -0
- package/src/routing/services/route-registry.ts +229 -0
- package/src/routing/services/router.ts +166 -0
- package/src/sse/sse.decorator.ts +41 -0
- package/src/sse/sse.encoder.ts +86 -0
- package/src/sse/sse.types.ts +47 -0
- package/src/sse/sse.wire.ts +34 -0
- package/src/testing/boot-module.ts +63 -0
- package/src/testing/sse-reader.ts +120 -0
- package/tests/app.hosted-services.spec.ts +292 -0
- package/tests/app.shutdown.integration.spec.ts +160 -0
- package/tests/app.spec.ts +132 -0
- package/tests/auth.ability.spec.ts +48 -0
- package/tests/auth.decorators.spec.ts +20 -0
- package/tests/auth.fail-closed.pentest.spec.ts +77 -0
- package/tests/auth.integration.spec.ts +234 -0
- package/tests/auth.jwks-cache.spec.ts +52 -0
- package/tests/auth.missing-auth.pentest.spec.ts +56 -0
- package/tests/auth.oidc-ssrf.pentest.spec.ts +72 -0
- package/tests/auth.oidc.integration.spec.ts +161 -0
- package/tests/auth.token-service.spec.ts +251 -0
- package/tests/builder.spec.ts +12 -0
- package/tests/health.integration.spec.ts +111 -0
- package/tests/helpers/listen-ephemeral.ts +13 -0
- package/tests/helpers/local-oidc-server.ts +100 -0
- package/tests/http.access-log.integration.spec.ts +111 -0
- package/tests/http.boolean-query.integration.spec.ts +59 -0
- package/tests/http.compression.integration.spec.ts +604 -0
- package/tests/http.date-query.integration.spec.ts +71 -0
- package/tests/http.global-prefix.integration.spec.ts +284 -0
- package/tests/http.number-query.integration.spec.ts +65 -0
- package/tests/http.pino-roll-hmr.integration.spec.ts +79 -0
- package/tests/http.pipeline-order.integration.spec.ts +95 -0
- package/tests/http.request-cleanup.integration.spec.ts +57 -0
- package/tests/http.request-id-scope.pentest.spec.ts +50 -0
- package/tests/http.security.integration.spec.ts +106 -0
- package/tests/http.static.integration.spec.ts +847 -0
- package/tests/http.static.spec.ts +111 -0
- package/tests/http.surface.integration.spec.ts +167 -0
- package/tests/openapi.integration.spec.ts +193 -0
- package/tests/openapi.validator-cache.integration.spec.ts +55 -0
- package/tests/routing.decorators.spec.ts +15 -0
- package/tests/routing.registry.spec.ts +44 -0
- package/tests/routing.resolver.spec.ts +92 -0
- package/tests/sse.integration.spec.ts +421 -0
- package/tests/sse.wire.spec.ts +33 -0
- package/LICENSE +0 -15
- package/commonjs/src/app/app.js +0 -166
- package/commonjs/src/app/index.js +0 -19
- package/commonjs/src/app/module.js +0 -17
- package/commonjs/src/auth/ability.js +0 -54
- package/commonjs/src/auth/auth.config.js +0 -23
- package/commonjs/src/auth/auth.extensions.js +0 -106
- package/commonjs/src/auth/auth.types.js +0 -65
- package/commonjs/src/auth/casl-authorization.service.js +0 -36
- package/commonjs/src/auth/index.js +0 -23
- package/commonjs/src/auth/principal-provider.js +0 -19
- package/commonjs/src/auth/safe-outbound-url.js +0 -102
- package/commonjs/src/auth/token-service.js +0 -151
- package/commonjs/src/builder/builder.js +0 -95
- package/commonjs/src/builder/index.js +0 -18
- package/commonjs/src/const.js +0 -87
- package/commonjs/src/health/health.controller.js +0 -75
- package/commonjs/src/health/health.extensions.js +0 -50
- package/commonjs/src/health/index.js +0 -21
- package/commonjs/src/health/readiness.service.js +0 -67
- package/commonjs/src/health/readiness.types.js +0 -7
- package/commonjs/src/hosting/hosted-service-host.js +0 -88
- package/commonjs/src/hosting/hosted-service.js +0 -11
- package/commonjs/src/hosting/index.js +0 -19
- package/commonjs/src/http/http.compression.js +0 -27
- package/commonjs/src/http/http.extensions.js +0 -269
- package/commonjs/src/http/http.options.js +0 -26
- package/commonjs/src/http/http.options.store.js +0 -20
- package/commonjs/src/http/http.security.js +0 -27
- package/commonjs/src/http/http.static.js +0 -248
- package/commonjs/src/http/index.js +0 -22
- package/commonjs/src/index.js +0 -27
- package/commonjs/src/openapi/index.js +0 -45
- package/commonjs/src/openapi/openapi.generator.js +0 -103
- package/commonjs/src/openapi/openapi.types.js +0 -4
- package/commonjs/src/openapi/request-schema.js +0 -187
- package/commonjs/src/openapi/request-validator.js +0 -85
- package/commonjs/src/providers/fastify.provider.js +0 -391
- package/commonjs/src/providers/index.js +0 -18
- package/commonjs/src/routing/anvil.types.js +0 -99
- package/commonjs/src/routing/decorators/argument.decorator.js +0 -49
- package/commonjs/src/routing/decorators/auth.decorator.js +0 -70
- package/commonjs/src/routing/decorators/controller.decorator.js +0 -16
- package/commonjs/src/routing/decorators/field.decorator.js +0 -25
- package/commonjs/src/routing/decorators/index.js +0 -24
- package/commonjs/src/routing/decorators/request.decorator.js +0 -30
- package/commonjs/src/routing/decorators/response.decorator.js +0 -34
- package/commonjs/src/routing/decorators/route.decorator.js +0 -67
- package/commonjs/src/routing/filters/exception-filter-host.js +0 -145
- package/commonjs/src/routing/filters/exception-filter.js +0 -28
- package/commonjs/src/routing/index.js +0 -25
- package/commonjs/src/routing/middleware/http-access-log.middleware.js +0 -47
- package/commonjs/src/routing/middleware/middleware.js +0 -20
- package/commonjs/src/routing/middleware/request-id.js +0 -30
- package/commonjs/src/routing/path.util.js +0 -51
- package/commonjs/src/routing/pipes/index.js +0 -18
- package/commonjs/src/routing/pipes/pipes.js +0 -145
- package/commonjs/src/routing/services/index.js +0 -18
- package/commonjs/src/routing/services/route-argument-resolver.js +0 -77
- package/commonjs/src/routing/services/route-registry.js +0 -194
- package/commonjs/src/routing/services/router.js +0 -158
- package/commonjs/src/sse/index.js +0 -26
- package/commonjs/src/sse/sse.decorator.js +0 -42
- package/commonjs/src/sse/sse.encoder.js +0 -73
- package/commonjs/src/sse/sse.types.js +0 -36
- package/commonjs/src/sse/sse.wire.js +0 -40
- package/commonjs/src/testing/boot-module.js +0 -38
- package/commonjs/src/testing/index.js +0 -20
- package/commonjs/src/testing/sse-reader.js +0 -98
- package/es6/src/app/app.js +0 -148
- package/es6/src/app/index.js +0 -2
- package/es6/src/app/module.js +0 -7
- package/es6/src/auth/ability.js +0 -45
- package/es6/src/auth/auth.config.js +0 -19
- package/es6/src/auth/auth.extensions.js +0 -83
- package/es6/src/auth/auth.types.js +0 -35
- package/es6/src/auth/casl-authorization.service.js +0 -40
- package/es6/src/auth/index.js +0 -6
- package/es6/src/auth/principal-provider.js +0 -14
- package/es6/src/auth/safe-outbound-url.js +0 -99
- package/es6/src/auth/token-service.js +0 -141
- package/es6/src/builder/builder.js +0 -85
- package/es6/src/builder/index.js +0 -1
- package/es6/src/health/health.controller.js +0 -65
- package/es6/src/health/health.extensions.js +0 -40
- package/es6/src/health/index.js +0 -4
- package/es6/src/health/readiness.service.js +0 -57
- package/es6/src/health/readiness.types.js +0 -4
- package/es6/src/hosting/hosted-service-host.js +0 -81
- package/es6/src/hosting/hosted-service.js +0 -8
- package/es6/src/hosting/index.js +0 -2
- package/es6/src/http/http.compression.js +0 -9
- package/es6/src/http/http.extensions.js +0 -259
- package/es6/src/http/http.options.js +0 -16
- package/es6/src/http/http.options.store.js +0 -13
- package/es6/src/http/http.security.js +0 -9
- package/es6/src/http/http.static.js +0 -224
- package/es6/src/http/index.js +0 -5
- package/es6/src/openapi/index.js +0 -3
- package/es6/src/openapi/openapi.generator.js +0 -82
- package/es6/src/openapi/openapi.types.js +0 -1
- package/es6/src/openapi/request-schema.js +0 -160
- package/es6/src/openapi/request-validator.js +0 -71
- package/es6/src/providers/fastify.provider.js +0 -376
- package/es6/src/providers/index.js +0 -1
- package/es6/src/routing/anvil.types.js +0 -57
- package/es6/src/routing/decorators/argument.decorator.js +0 -28
- package/es6/src/routing/decorators/auth.decorator.js +0 -58
- package/es6/src/routing/decorators/controller.decorator.js +0 -6
- package/es6/src/routing/decorators/field.decorator.js +0 -15
- package/es6/src/routing/decorators/index.js +0 -7
- package/es6/src/routing/decorators/request.decorator.js +0 -24
- package/es6/src/routing/decorators/response.decorator.js +0 -16
- package/es6/src/routing/decorators/route.decorator.js +0 -37
- package/es6/src/routing/filters/exception-filter-host.js +0 -127
- package/es6/src/routing/filters/exception-filter.js +0 -10
- package/es6/src/routing/index.js +0 -8
- package/es6/src/routing/middleware/http-access-log.middleware.js +0 -37
- package/es6/src/routing/middleware/middleware.js +0 -10
- package/es6/src/routing/middleware/request-id.js +0 -14
- package/es6/src/routing/path.util.js +0 -33
- package/es6/src/routing/pipes/index.js +0 -1
- package/es6/src/routing/pipes/pipes.js +0 -118
- package/es6/src/routing/services/index.js +0 -1
- package/es6/src/routing/services/route-argument-resolver.js +0 -67
- package/es6/src/routing/services/route-registry.js +0 -184
- package/es6/src/routing/services/router.js +0 -148
- package/es6/src/sse/index.js +0 -2
- package/es6/src/sse/sse.decorator.js +0 -31
- package/es6/src/sse/sse.encoder.js +0 -67
- package/es6/src/sse/sse.types.js +0 -22
- package/es6/src/sse/sse.wire.js +0 -30
- package/es6/src/testing/boot-module.js +0 -35
- package/es6/src/testing/index.js +0 -2
- package/es6/src/testing/sse-reader.js +0 -91
- package/types/src/app/app.d.ts +0 -41
- package/types/src/app/module.d.ts +0 -15
- package/types/src/auth/ability.d.ts +0 -19
- package/types/src/auth/auth.config.d.ts +0 -28
- package/types/src/auth/auth.extensions.d.ts +0 -53
- package/types/src/auth/auth.types.d.ts +0 -73
- package/types/src/auth/casl-authorization.service.d.ts +0 -28
- package/types/src/auth/safe-outbound-url.d.ts +0 -9
- package/types/src/auth/token-service.d.ts +0 -31
- package/types/src/builder/builder.d.ts +0 -36
- package/types/src/const.d.ts +0 -32
- package/types/src/health/health.controller.d.ts +0 -16
- package/types/src/health/health.extensions.d.ts +0 -31
- package/types/src/health/readiness.service.d.ts +0 -9
- package/types/src/hosting/hosted-service-host.d.ts +0 -37
- package/types/src/hosting/hosted-service.d.ts +0 -31
- package/types/src/http/http.compression.d.ts +0 -32
- package/types/src/http/http.extensions.d.ts +0 -79
- package/types/src/http/http.options.d.ts +0 -39
- package/types/src/http/http.static.d.ts +0 -160
- package/types/src/index.d.ts +0 -10
- package/types/src/openapi/index.d.ts +0 -6
- package/types/src/openapi/openapi.generator.d.ts +0 -6
- package/types/src/openapi/openapi.types.d.ts +0 -58
- package/types/src/openapi/request-schema.d.ts +0 -21
- package/types/src/openapi/request-validator.d.ts +0 -9
- package/types/src/providers/fastify.provider.d.ts +0 -56
- package/types/src/routing/anvil.types.d.ts +0 -119
- package/types/src/routing/decorators/argument.decorator.d.ts +0 -11
- package/types/src/routing/decorators/auth.decorator.d.ts +0 -30
- package/types/src/routing/decorators/controller.decorator.d.ts +0 -3
- package/types/src/routing/decorators/field.decorator.d.ts +0 -2
- package/types/src/routing/decorators/request.decorator.d.ts +0 -8
- package/types/src/routing/decorators/response.decorator.d.ts +0 -4
- package/types/src/routing/decorators/route.decorator.d.ts +0 -7
- package/types/src/routing/filters/exception-filter-host.d.ts +0 -19
- package/types/src/routing/filters/exception-filter.d.ts +0 -16
- package/types/src/routing/middleware/http-access-log.middleware.d.ts +0 -8
- package/types/src/routing/middleware/middleware.d.ts +0 -10
- package/types/src/routing/middleware/request-id.d.ts +0 -6
- package/types/src/routing/path.util.d.ts +0 -9
- package/types/src/routing/pipes/pipes.d.ts +0 -21
- package/types/src/routing/services/route-argument-resolver.d.ts +0 -10
- package/types/src/routing/services/route-registry.d.ts +0 -20
- package/types/src/routing/services/router.d.ts +0 -22
- package/types/src/sse/sse.decorator.d.ts +0 -17
- package/types/src/sse/sse.encoder.d.ts +0 -16
- package/types/src/sse/sse.types.d.ts +0 -37
- package/types/src/sse/sse.wire.d.ts +0 -9
- package/types/src/testing/boot-module.d.ts +0 -34
- package/types/src/testing/sse-reader.d.ts +0 -29
- /package/{types/src/app/index.d.ts → src/app/index.ts} +0 -0
- /package/{types/src/auth/index.d.ts → src/auth/index.ts} +0 -0
- /package/{types/src/builder/index.d.ts → src/builder/index.ts} +0 -0
- /package/{types/src/health/index.d.ts → src/health/index.ts} +0 -0
- /package/{types/src/hosting/index.d.ts → src/hosting/index.ts} +0 -0
- /package/{types/src/http/index.d.ts → src/http/index.ts} +0 -0
- /package/{types/src/providers/index.d.ts → src/providers/index.ts} +0 -0
- /package/{types/src/routing/decorators/index.d.ts → src/routing/decorators/index.ts} +0 -0
- /package/{types/src/routing/index.d.ts → src/routing/index.ts} +0 -0
- /package/{types/src/routing/pipes/index.d.ts → src/routing/pipes/index.ts} +0 -0
- /package/{types/src/routing/services/index.d.ts → src/routing/services/index.ts} +0 -0
- /package/{types/src/sse/index.d.ts → src/sse/index.ts} +0 -0
- /package/{types/src/testing/index.d.ts → src/testing/index.ts} +0 -0
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { createRemoteJWKSet, importJWK, importSPKI, jwtVerify, SignJWT } from "jose";
|
|
2
|
-
import { UnauthorizedError } from "../routing/anvil.types.js";
|
|
3
|
-
import { createPrincipalFromPayload } from "./ability.js";
|
|
4
|
-
import { assertDevStaticAllowed, assertNonEmptyAudience, isDevStaticConfig, isDiscoveryConfig, isJwksConfig, isPublicKeyConfig, isSecretConfig } from "./auth.types.js";
|
|
5
|
-
import { assertSafeOutboundUrl } from "./safe-outbound-url.js";
|
|
6
|
-
export class TokenService {
|
|
7
|
-
config;
|
|
8
|
-
principalMapper;
|
|
9
|
-
/** Lazily resolved JWKS set when using OIDC discovery. */ discoveryJwks;
|
|
10
|
-
discoveryIssuer;
|
|
11
|
-
/** Remote key set for `jwksUrl` mode, created once so jose can cache the keys. */ configuredJwks;
|
|
12
|
-
constructor(config, principalMapper){
|
|
13
|
-
if (isDevStaticConfig(config)) {
|
|
14
|
-
assertDevStaticAllowed();
|
|
15
|
-
} else {
|
|
16
|
-
assertNonEmptyAudience(config.audience);
|
|
17
|
-
}
|
|
18
|
-
this.config = config;
|
|
19
|
-
this.principalMapper = principalMapper;
|
|
20
|
-
}
|
|
21
|
-
async verifyRequest(request) {
|
|
22
|
-
const authHeader = request.headers.authorization;
|
|
23
|
-
if (!authHeader) {
|
|
24
|
-
throw new UnauthorizedError("Missing Authorization header");
|
|
25
|
-
}
|
|
26
|
-
const spaceIdx = authHeader.indexOf(" ");
|
|
27
|
-
if (spaceIdx === -1) {
|
|
28
|
-
throw new UnauthorizedError("Malformed Authorization header");
|
|
29
|
-
}
|
|
30
|
-
const scheme = authHeader.slice(0, spaceIdx);
|
|
31
|
-
if (scheme !== "Bearer") {
|
|
32
|
-
throw new UnauthorizedError(`Unsupported authorization scheme: ${scheme}`);
|
|
33
|
-
}
|
|
34
|
-
const credentials = authHeader.slice(spaceIdx + 1);
|
|
35
|
-
return this.verify(credentials);
|
|
36
|
-
}
|
|
37
|
-
async verify(credentials) {
|
|
38
|
-
if (isDevStaticConfig(this.config)) {
|
|
39
|
-
return this.config.devStatic;
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const { key, issuer, audience } = await this.resolveVerificationContext();
|
|
43
|
-
// jose's overloads don't accept Key | JWKS-getter as a union; runtime does.
|
|
44
|
-
const { payload } = await jwtVerify(credentials, key, {
|
|
45
|
-
issuer,
|
|
46
|
-
audience
|
|
47
|
-
});
|
|
48
|
-
return this.mapPrincipal(payload);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
throw new UnauthorizedError(err instanceof Error ? err.message : "Token verification failed");
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Resolve how to verify tokens for the configured strategy:
|
|
55
|
-
* - OIDC discovery → fetch JWKS + issuer from `/.well-known/openid-configuration`
|
|
56
|
-
* - explicit JWKS URL → remote key set + configured issuer/audience
|
|
57
|
-
* - public key / shared secret → static key material + configured issuer/audience
|
|
58
|
-
*/ async resolveVerificationContext() {
|
|
59
|
-
if (isDiscoveryConfig(this.config)) {
|
|
60
|
-
const { jwks, issuer } = await this.resolveOidcDiscovery();
|
|
61
|
-
return {
|
|
62
|
-
key: jwks,
|
|
63
|
-
issuer,
|
|
64
|
-
audience: this.config.audience
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
if (isJwksConfig(this.config)) {
|
|
68
|
-
this.configuredJwks ??= createRemoteJWKSet(await assertSafeOutboundUrl(this.config.jwksUrl, "jwksUrl"));
|
|
69
|
-
return {
|
|
70
|
-
key: this.configuredJwks,
|
|
71
|
-
issuer: this.config.issuer,
|
|
72
|
-
audience: this.config.audience
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
return {
|
|
76
|
-
key: await this.getStaticKey(),
|
|
77
|
-
issuer: this.config.issuer,
|
|
78
|
-
audience: this.config.audience
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
async resolveOidcDiscovery() {
|
|
82
|
-
if (this.discoveryJwks && this.discoveryIssuer) {
|
|
83
|
-
return {
|
|
84
|
-
jwks: this.discoveryJwks,
|
|
85
|
-
issuer: this.discoveryIssuer
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
const { discoveryUrl } = this.config;
|
|
89
|
-
await assertSafeOutboundUrl(discoveryUrl, "discoveryUrl");
|
|
90
|
-
const resp = await fetch(`${discoveryUrl.replace(/\/$/, "")}/.well-known/openid-configuration`, {
|
|
91
|
-
redirect: "error"
|
|
92
|
-
});
|
|
93
|
-
if (!resp.ok) {
|
|
94
|
-
throw new Error(`OIDC discovery failed for ${discoveryUrl}: HTTP ${resp.status}`);
|
|
95
|
-
}
|
|
96
|
-
const doc = await resp.json();
|
|
97
|
-
if (typeof doc.jwks_uri !== "string" || doc.jwks_uri.length === 0) {
|
|
98
|
-
throw new Error(`OIDC discovery for ${discoveryUrl} did not return jwks_uri`);
|
|
99
|
-
}
|
|
100
|
-
const jwksUrl = await assertSafeOutboundUrl(doc.jwks_uri, "jwks_uri");
|
|
101
|
-
this.discoveryIssuer = doc.issuer;
|
|
102
|
-
this.discoveryJwks = createRemoteJWKSet(jwksUrl);
|
|
103
|
-
return {
|
|
104
|
-
jwks: this.discoveryJwks,
|
|
105
|
-
issuer: this.discoveryIssuer
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Sign a payload and return a JWT string.
|
|
110
|
-
* Only available when configured with a SecretConfig (HS256).
|
|
111
|
-
* For RS256/ES256 issuance, the caller is responsible for key management.
|
|
112
|
-
*/ async sign(payload, expiresIn = "1h") {
|
|
113
|
-
if (!isSecretConfig(this.config)) {
|
|
114
|
-
throw new Error("TokenService.sign() requires SecretConfig (shared secret / HS256)");
|
|
115
|
-
}
|
|
116
|
-
const key = new TextEncoder().encode(this.config.secret);
|
|
117
|
-
return new SignJWT(payload).setProtectedHeader({
|
|
118
|
-
alg: "HS256"
|
|
119
|
-
}).setIssuedAt().setIssuer(this.config.issuer ?? "").setAudience(this.config.audience ?? "").setExpirationTime(expiresIn).sign(key);
|
|
120
|
-
}
|
|
121
|
-
async getStaticKey() {
|
|
122
|
-
if (isPublicKeyConfig(this.config)) {
|
|
123
|
-
const cfg = this.config;
|
|
124
|
-
const trimmed = cfg.publicKey.trim();
|
|
125
|
-
if (trimmed.startsWith("{")) {
|
|
126
|
-
const jwk = JSON.parse(trimmed);
|
|
127
|
-
return importJWK(jwk);
|
|
128
|
-
}
|
|
129
|
-
return importSPKI(trimmed, cfg.algorithms?.[0] ?? "RS256");
|
|
130
|
-
}
|
|
131
|
-
if (isSecretConfig(this.config)) {
|
|
132
|
-
const enc = new TextEncoder().encode(this.config.secret);
|
|
133
|
-
return enc;
|
|
134
|
-
}
|
|
135
|
-
throw new Error("Invalid TokenService configuration");
|
|
136
|
-
}
|
|
137
|
-
mapPrincipal(payload) {
|
|
138
|
-
if (this.principalMapper) return this.principalMapper(payload);
|
|
139
|
-
return createPrincipalFromPayload(payload);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import { ServiceContainerBuilder } from "@decohere/crucible";
|
|
2
|
-
import { ConfigurationServiceToken } from "@decohere/core/configuration";
|
|
3
|
-
import { scribe } from "@decohere/scribe";
|
|
4
|
-
import { App } from "../app/app.js";
|
|
5
|
-
import { HostedServiceHostToken } from "../const.js";
|
|
6
|
-
import { HostedServiceHost } from "../hosting/hosted-service-host.js";
|
|
7
|
-
export class Builder {
|
|
8
|
-
static create(options) {
|
|
9
|
-
return new Builder(options);
|
|
10
|
-
}
|
|
11
|
-
containerBuilder;
|
|
12
|
-
applicationName;
|
|
13
|
-
logger = scribe.logger("name");
|
|
14
|
-
userServices = [];
|
|
15
|
-
preBuildSteps = [];
|
|
16
|
-
postBuildSteps = [];
|
|
17
|
-
hostedServices = new HostedServiceHost();
|
|
18
|
-
constructor(options){
|
|
19
|
-
this.containerBuilder = options?.containerBuilder ?? new ServiceContainerBuilder();
|
|
20
|
-
this.applicationName = options?.applicationName;
|
|
21
|
-
}
|
|
22
|
-
withServices(userServices) {
|
|
23
|
-
this.userServices.push(userServices);
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Run {@link HostedService}s for the life of the app: started in this order before the HTTP
|
|
28
|
-
* server listens, stopped in reverse when the app stops (including SIGTERM and Flux reboots).
|
|
29
|
-
* Each is registered as a singleton, so other services can inject it by its class.
|
|
30
|
-
*/ withHostedService(...types) {
|
|
31
|
-
for (const type of types)this.containerBuilder.registerSingleton(type);
|
|
32
|
-
this.hostedServices.add(...types);
|
|
33
|
-
return this;
|
|
34
|
-
}
|
|
35
|
-
registerPreBuildStep(step) {
|
|
36
|
-
this.preBuildSteps.push(step);
|
|
37
|
-
}
|
|
38
|
-
registerPostBuildStep(step) {
|
|
39
|
-
this.postBuildSteps.push(step);
|
|
40
|
-
}
|
|
41
|
-
async buildAndStart(type) {
|
|
42
|
-
const app = await this.build(type);
|
|
43
|
-
await app.start();
|
|
44
|
-
this.logger.info("App started...");
|
|
45
|
-
return app;
|
|
46
|
-
}
|
|
47
|
-
async build(type) {
|
|
48
|
-
this.logger.info("Building App...");
|
|
49
|
-
this.configureUserServices();
|
|
50
|
-
this.configureApp(type);
|
|
51
|
-
await this.runPreBuildSteps(this.containerBuilder);
|
|
52
|
-
const container = this.containerBuilder.build();
|
|
53
|
-
await this.runPostBuildSteps(container);
|
|
54
|
-
const app = this.resolveApp(container, type);
|
|
55
|
-
return app;
|
|
56
|
-
}
|
|
57
|
-
configureUserServices() {
|
|
58
|
-
for (const userService of this.userServices){
|
|
59
|
-
userService(this.containerBuilder);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
async runPreBuildSteps(containerBuilder) {
|
|
63
|
-
if (this.preBuildSteps.length === 0) {
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const container = containerBuilder.build();
|
|
67
|
-
const config = container.resolve(ConfigurationServiceToken);
|
|
68
|
-
for (const step of this.preBuildSteps){
|
|
69
|
-
await step(config, containerBuilder);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async runPostBuildSteps(container) {
|
|
73
|
-
for (const step of this.postBuildSteps){
|
|
74
|
-
await step(container);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
configureApp(type) {
|
|
78
|
-
this.containerBuilder.registerSingleton(HostedServiceHostToken, this.hostedServices);
|
|
79
|
-
this.containerBuilder.registerSingleton(type ?? App);
|
|
80
|
-
}
|
|
81
|
-
resolveApp(container, type) {
|
|
82
|
-
const app = type ? container.resolve(type) : container.resolve(App);
|
|
83
|
-
return app;
|
|
84
|
-
}
|
|
85
|
-
}
|
package/es6/src/builder/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./builder.js";
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
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;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
function _ts_param(paramIndex, decorator) {
|
|
11
|
-
return function(target, key) {
|
|
12
|
-
decorator(target, key, paramIndex);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
16
|
-
import { ReadinessServiceToken } from "../const.js";
|
|
17
|
-
import { HttpError } from "../routing/anvil.types.js";
|
|
18
|
-
import { AllowAnonymous, Controller, Get } from "../routing/index.js";
|
|
19
|
-
import { ReadinessService } from "./readiness.service.js";
|
|
20
|
-
export class HealthController {
|
|
21
|
-
readiness;
|
|
22
|
-
constructor(readiness){
|
|
23
|
-
this.readiness = readiness;
|
|
24
|
-
}
|
|
25
|
-
health() {
|
|
26
|
-
return {
|
|
27
|
-
status: "ok"
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
async ready() {
|
|
31
|
-
const result = await this.readiness.checkAll();
|
|
32
|
-
if (!result.ok) {
|
|
33
|
-
throw new HttpError("Not ready", 503);
|
|
34
|
-
}
|
|
35
|
-
return {
|
|
36
|
-
status: "ok"
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
_ts_decorate([
|
|
41
|
-
AllowAnonymous(),
|
|
42
|
-
Get("/health"),
|
|
43
|
-
_ts_metadata("design:type", Function),
|
|
44
|
-
_ts_metadata("design:paramtypes", []),
|
|
45
|
-
_ts_metadata("design:returntype", void 0)
|
|
46
|
-
], HealthController.prototype, "health", null);
|
|
47
|
-
_ts_decorate([
|
|
48
|
-
AllowAnonymous(),
|
|
49
|
-
Get("/ready"),
|
|
50
|
-
_ts_metadata("design:type", Function),
|
|
51
|
-
_ts_metadata("design:paramtypes", []),
|
|
52
|
-
_ts_metadata("design:returntype", Promise)
|
|
53
|
-
], HealthController.prototype, "ready", null);
|
|
54
|
-
HealthController = _ts_decorate([
|
|
55
|
-
Controller({
|
|
56
|
-
prefix: "/",
|
|
57
|
-
globalPrefix: false
|
|
58
|
-
}),
|
|
59
|
-
injectable(),
|
|
60
|
-
_ts_param(0, inject(ReadinessServiceToken)),
|
|
61
|
-
_ts_metadata("design:type", Function),
|
|
62
|
-
_ts_metadata("design:paramtypes", [
|
|
63
|
-
typeof ReadinessService === "undefined" ? Object : ReadinessService
|
|
64
|
-
])
|
|
65
|
-
], HealthController);
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
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;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
import { extension } from "@decohere/core/extensibility";
|
|
11
|
-
import { Builder } from "../builder/builder.js";
|
|
12
|
-
import { ReadinessCheckToken, ReadinessServiceToken } from "../const.js";
|
|
13
|
-
import "../http/http.extensions.js";
|
|
14
|
-
import { getOrCreateHttpOptions } from "../http/http.options.store.js";
|
|
15
|
-
import { HealthController } from "./health.controller.js";
|
|
16
|
-
import { ReadinessService } from "./readiness.service.js";
|
|
17
|
-
export class BuilderExtensions {
|
|
18
|
-
static withHealth(builder, options) {
|
|
19
|
-
builder.withServices((containerBuilder)=>{
|
|
20
|
-
containerBuilder.registerSingleton(ReadinessServiceToken, ReadinessService);
|
|
21
|
-
for (const check of options?.checks ?? []){
|
|
22
|
-
containerBuilder.registerSingleton(ReadinessCheckToken, check);
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
if (options?.underGlobalPrefix === true) {
|
|
26
|
-
getOrCreateHttpOptions(builder).underGlobalPrefix.add(HealthController);
|
|
27
|
-
}
|
|
28
|
-
builder.withControllers(HealthController);
|
|
29
|
-
return builder;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
_ts_decorate([
|
|
33
|
-
extension(Builder),
|
|
34
|
-
_ts_metadata("design:type", Function),
|
|
35
|
-
_ts_metadata("design:paramtypes", [
|
|
36
|
-
typeof Builder === "undefined" ? Object : Builder,
|
|
37
|
-
typeof WithHealthOptions === "undefined" ? Object : WithHealthOptions
|
|
38
|
-
]),
|
|
39
|
-
_ts_metadata("design:returntype", void 0)
|
|
40
|
-
], BuilderExtensions, "withHealth", null);
|
package/es6/src/health/index.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
2
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
-
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;
|
|
5
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
-
}
|
|
7
|
-
function _ts_metadata(k, v) {
|
|
8
|
-
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
-
}
|
|
10
|
-
function _ts_param(paramIndex, decorator) {
|
|
11
|
-
return function(target, key) {
|
|
12
|
-
decorator(target, key, paramIndex);
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
import { inject, injectable } from "@decohere/crucible/decorators";
|
|
16
|
-
import { HostedServiceHostToken, ReadinessCheckToken } from "../const.js";
|
|
17
|
-
export class ReadinessService {
|
|
18
|
-
hostedServices;
|
|
19
|
-
checks;
|
|
20
|
-
constructor(checks = [], hostedServices){
|
|
21
|
-
this.hostedServices = hostedServices;
|
|
22
|
-
this.checks = checks;
|
|
23
|
-
}
|
|
24
|
-
/** Registered checks, then each started hosted service that has a `check()` (still warming up). */ async checkAll() {
|
|
25
|
-
const failures = [];
|
|
26
|
-
const probes = [
|
|
27
|
-
...this.checks,
|
|
28
|
-
...this.hostedServices?.readinessChecks() ?? []
|
|
29
|
-
];
|
|
30
|
-
for (const probe of probes){
|
|
31
|
-
try {
|
|
32
|
-
await probe.check();
|
|
33
|
-
} catch {
|
|
34
|
-
failures.push(probe.name);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
if (failures.length > 0) {
|
|
38
|
-
return {
|
|
39
|
-
ok: false,
|
|
40
|
-
failures
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
ok: true
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
ReadinessService = _ts_decorate([
|
|
49
|
-
injectable(),
|
|
50
|
-
_ts_param(0, inject(ReadinessCheckToken)),
|
|
51
|
-
_ts_param(1, inject(HostedServiceHostToken)),
|
|
52
|
-
_ts_metadata("design:type", Function),
|
|
53
|
-
_ts_metadata("design:paramtypes", [
|
|
54
|
-
Array,
|
|
55
|
-
typeof HostedServiceHost === "undefined" ? Object : HostedServiceHost
|
|
56
|
-
])
|
|
57
|
-
], ReadinessService);
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { scribe } from "@decohere/scribe";
|
|
2
|
-
/**
|
|
3
|
-
* Starts and stops the app's {@link HostedService}s. One per app; `Builder.withHostedService`
|
|
4
|
-
* adds the types and `App` drives it.
|
|
5
|
-
*/ export class HostedServiceHost {
|
|
6
|
-
logger = scribe.logger(HostedServiceHost.name);
|
|
7
|
-
types = [];
|
|
8
|
-
controller = new AbortController();
|
|
9
|
-
/** In start order; stopped in reverse. */ started = [];
|
|
10
|
-
starting;
|
|
11
|
-
stopping;
|
|
12
|
-
add(...types) {
|
|
13
|
-
this.types.push(...types);
|
|
14
|
-
}
|
|
15
|
-
get isEmpty() {
|
|
16
|
-
return this.types.length === 0;
|
|
17
|
-
}
|
|
18
|
-
/**
|
|
19
|
-
* Start every service in registration order. If one throws, the ones already started are
|
|
20
|
-
* stopped and the error is rethrown. Stops launching more once {@link stop} has begun.
|
|
21
|
-
*/ start(container) {
|
|
22
|
-
this.starting ??= this.startAll(container);
|
|
23
|
-
return this.starting;
|
|
24
|
-
}
|
|
25
|
-
/** Abort the services' signal: background work should wind down. {@link stop} does this too. */ abort() {
|
|
26
|
-
this.controller.abort();
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Abort the signal, wait for a start still in progress, then stop the started services in
|
|
30
|
-
* reverse order. Every `stop()` runs even if one throws; the failures are rethrown after.
|
|
31
|
-
*/ stop() {
|
|
32
|
-
this.stopping ??= this.stopAll();
|
|
33
|
-
return this.stopping;
|
|
34
|
-
}
|
|
35
|
-
/** The started services that report readiness, for `GET /ready`. */ readinessChecks() {
|
|
36
|
-
return this.started.filter((service)=>typeof service.check === "function").map((service)=>({
|
|
37
|
-
name: service.constructor.name,
|
|
38
|
-
check: ()=>service.check?.() ?? Promise.resolve()
|
|
39
|
-
}));
|
|
40
|
-
}
|
|
41
|
-
async startAll(container) {
|
|
42
|
-
const { signal } = this.controller;
|
|
43
|
-
for (const type of this.types){
|
|
44
|
-
if (signal.aborted) return;
|
|
45
|
-
const service = container.resolve(type);
|
|
46
|
-
try {
|
|
47
|
-
await service.start(signal);
|
|
48
|
-
} catch (error) {
|
|
49
|
-
this.logger.error(`Hosted service ${type.name} failed to start`, error);
|
|
50
|
-
// Not stop(): it waits for this start to settle.
|
|
51
|
-
this.controller.abort();
|
|
52
|
-
await this.stopStarted().catch(()=>{});
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
this.started.push(service);
|
|
56
|
-
this.logger.debug(`Hosted service ${type.name} started`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
async stopAll() {
|
|
60
|
-
this.controller.abort();
|
|
61
|
-
await Promise.allSettled([
|
|
62
|
-
this.starting
|
|
63
|
-
]);
|
|
64
|
-
await this.stopStarted();
|
|
65
|
-
}
|
|
66
|
-
/** Stop what is started, newest first. Takes each service off the list, so it runs once. */ async stopStarted() {
|
|
67
|
-
const failures = [];
|
|
68
|
-
for(let service = this.started.pop(); service != null; service = this.started.pop()){
|
|
69
|
-
try {
|
|
70
|
-
await service.stop?.();
|
|
71
|
-
} catch (error) {
|
|
72
|
-
this.logger.error(`Hosted service ${service.constructor.name} failed to stop`, error);
|
|
73
|
-
failures.push(error);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (failures.length === 1) throw failures[0];
|
|
77
|
-
if (failures.length > 1) {
|
|
78
|
-
throw new AggregateError(failures, `${failures.length} hosted services failed to stop`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Work that runs for the life of the app outside a request: a feed, a poller, a queue consumer.
|
|
3
|
-
* Register it with `builder.withHostedService(Type)`; it is a singleton, so other services can
|
|
4
|
-
* inject it by its class.
|
|
5
|
-
*
|
|
6
|
-
* The app starts hosted services in registration order before the HTTP server listens, and stops
|
|
7
|
-
* them in reverse order when it stops (`app.dispose()`, SIGTERM or SIGINT, a Flux reboot).
|
|
8
|
-
*/ export { };
|
package/es6/src/hosting/index.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Response compression (`builder.withCompression`). Provider-neutral options and defaults;
|
|
3
|
-
* the HTTP provider hands them to its compression plugin and the plugin owns the policy
|
|
4
|
-
* (which responses are compressed, `Vary`, `HEAD`, streams). See ADR 0008.
|
|
5
|
-
*/ /** A content coding the HTTP package can offer. */ /** Bodies smaller than this many bytes are sent as they are (not applied to streams, such as files). */ export const DEFAULT_COMPRESSION_THRESHOLD = 1024;
|
|
6
|
-
/** The codings offered by default, in order of preference. `deflate` is left out on purpose. */ export const DEFAULT_COMPRESSION_ENCODINGS = [
|
|
7
|
-
"br",
|
|
8
|
-
"gzip"
|
|
9
|
-
];
|