@boopkit/cap-oauth-server 0.1.3 → 0.3.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 (81) hide show
  1. package/capability.json +14 -2
  2. package/dist/authorization-code-service.d.ts +40 -11
  3. package/dist/authorization-code-service.d.ts.map +1 -1
  4. package/dist/authorization-code-service.js +93 -41
  5. package/dist/authorization-code-service.js.map +1 -1
  6. package/dist/bearer.d.ts +27 -4
  7. package/dist/bearer.d.ts.map +1 -1
  8. package/dist/bearer.js +4 -4
  9. package/dist/bearer.js.map +1 -1
  10. package/dist/client-prune.d.ts +102 -0
  11. package/dist/client-prune.d.ts.map +1 -0
  12. package/dist/client-prune.js +51 -0
  13. package/dist/client-prune.js.map +1 -0
  14. package/dist/client-registry-service.d.ts +78 -7
  15. package/dist/client-registry-service.d.ts.map +1 -1
  16. package/dist/client-registry-service.js +200 -52
  17. package/dist/client-registry-service.js.map +1 -1
  18. package/dist/config.d.ts +6 -0
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +1 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/connections-controller.js +1 -1
  23. package/dist/connections-controller.js.map +1 -1
  24. package/dist/consent-controller.d.ts +80 -6
  25. package/dist/consent-controller.d.ts.map +1 -1
  26. package/dist/consent-controller.js +134 -18
  27. package/dist/consent-controller.js.map +1 -1
  28. package/dist/discovery-controller.d.ts +2 -1
  29. package/dist/discovery-controller.d.ts.map +1 -1
  30. package/dist/discovery-controller.js +14 -4
  31. package/dist/discovery-controller.js.map +1 -1
  32. package/dist/discovery.d.ts +26 -0
  33. package/dist/discovery.d.ts.map +1 -1
  34. package/dist/discovery.js +55 -1
  35. package/dist/discovery.js.map +1 -1
  36. package/dist/errors.d.ts +38 -0
  37. package/dist/errors.d.ts.map +1 -1
  38. package/dist/errors.js +59 -0
  39. package/dist/errors.js.map +1 -1
  40. package/dist/grant-service.d.ts +11 -4
  41. package/dist/grant-service.d.ts.map +1 -1
  42. package/dist/grant-service.js +6 -3
  43. package/dist/grant-service.js.map +1 -1
  44. package/dist/index.d.ts +10 -5
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +18 -3
  47. package/dist/index.js.map +1 -1
  48. package/dist/metadata-document.d.ts +22 -0
  49. package/dist/metadata-document.d.ts.map +1 -1
  50. package/dist/metadata-document.js +44 -2
  51. package/dist/metadata-document.js.map +1 -1
  52. package/dist/outbound-target.d.ts +48 -0
  53. package/dist/outbound-target.d.ts.map +1 -0
  54. package/dist/outbound-target.js +211 -0
  55. package/dist/outbound-target.js.map +1 -0
  56. package/dist/rate-limit.d.ts +53 -11
  57. package/dist/rate-limit.d.ts.map +1 -1
  58. package/dist/rate-limit.js +27 -4
  59. package/dist/rate-limit.js.map +1 -1
  60. package/dist/redirect-uri.d.ts +17 -9
  61. package/dist/redirect-uri.d.ts.map +1 -1
  62. package/dist/redirect-uri.js +26 -10
  63. package/dist/redirect-uri.js.map +1 -1
  64. package/dist/registration-controller.d.ts +10 -0
  65. package/dist/registration-controller.d.ts.map +1 -1
  66. package/dist/registration-controller.js +10 -0
  67. package/dist/registration-controller.js.map +1 -1
  68. package/dist/same-site.d.ts +54 -0
  69. package/dist/same-site.d.ts.map +1 -0
  70. package/dist/same-site.js +124 -0
  71. package/dist/same-site.js.map +1 -0
  72. package/dist/token-controller.d.ts +21 -0
  73. package/dist/token-controller.d.ts.map +1 -1
  74. package/dist/token-controller.js +42 -18
  75. package/dist/token-controller.js.map +1 -1
  76. package/package.json +2 -2
  77. package/scaffold/reference/oauth-server.ref.md +280 -13
  78. package/scaffold/src/middleware/oauth-server.ts +33 -17
  79. package/scaffold/src/scripts/prune-oauth-clients.ts +86 -0
  80. package/scaffold/src/services/oauth-server.service.ts +24 -0
  81. package/scaffold/views/oauth-consent-error.hbs +10 -3
package/dist/index.js CHANGED
@@ -19,8 +19,9 @@
19
19
  * that mechanically.
20
20
  */
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.OAUTH_REFRESH_DOMAIN = exports.OAUTH_ACCESS_DOMAIN = exports.OAUTH_CODE_DOMAIN = exports.AUTHORIZATION_CODE_TTL_SECONDS = exports.buildIdentityKey = exports.createTokenService = exports.CLIENT_MODES = exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS = exports.DEFAULT_REGISTRATION_RATE_LIMIT = exports.createRegistrationRateLimiter = exports.toRegistrationResponse = exports.createRegistrationRouter = exports.createRegistrationHandlers = exports.createDiscoveryRouter = exports.createDiscoveryHandlers = exports.REFRESH_TOKEN_TTL_BOUNDS = exports.ACCESS_TOKEN_TTL_BOUNDS = exports.DEFAULT_REFRESH_TOKEN_TTL_SECONDS = exports.DEFAULT_ACCESS_TOKEN_TTL_SECONDS = exports.OauthServerConfigError = exports.clampTtlSeconds = exports.readServerConfig = exports.DISCOVERY_MAX_AGE_SECONDS = exports.DEFAULT_SCOPES_SUPPORTED = exports.DEVICE_CODE_GRANT_TYPE = exports.CODE_CHALLENGE_METHODS_SUPPORTED = exports.buildProtectedResourceMetadata = exports.buildAuthorizationServerMetadata = exports.DEFAULT_METADATA_MAX_REDIRECTS = exports.DEFAULT_METADATA_MAX_BYTES = exports.DEFAULT_METADATA_TIMEOUT_MS = exports.isValidMetadataUri = exports.fetchMetadataDocument = exports.DEFAULT_MAX_REGISTERED_CLIENTS = exports.DEFAULT_ABANDONED_CLIENT_TTL_MS = exports.DEFAULT_METADATA_MAX_AGE_MS = exports.DEFAULT_RESPONSE_TYPES = exports.DEFAULT_GRANT_TYPES = exports.validateRedirectUris = exports.createClientRegistryService = exports.isClientRegistryError = exports.ClientRegistryError = exports.CLIENT_PUBLIC_ID_LENGTH = exports.CLIENT_PUBLIC_ID_ALPHABET = exports.isValidClientPublicId = exports.generateClientPublicId = exports.isLoopbackHost = exports.matchesAnyRedirectUri = exports.matchRedirectUri = exports.classifyRedirectUri = void 0;
23
- exports.ACCOUNT_COVERAGE_STATEMENT = exports.createConnectionsRouter = exports.createConnectionsHandlers = exports.createRevocationRouter = exports.createRevocationHandlers = exports.REVOKED_CLIENT_WIDE = exports.REVOKED_BY_USER = exports.REVOKED_BY_CLIENT = exports.createRevocationService = exports.DEFAULT_BEARER_CHALLENGE = exports.requireOAuth = exports.missingScopes = exports.createOAuthTokenResolver = exports.createOAuthResourceServer = exports.createOAuthBearerMiddleware = exports.OFFLINE_ACCESS_SCOPE = exports.parseScopes = exports.createTokenRouter = exports.createTokenHandlers = exports.SCOPE_DESCRIPTIONS_LOCAL = exports.warnUndescribedScopes = exports.toScopeViews = exports.scopeDescriptionsFrom = exports.safeLogoUri = exports.provenanceFor = exports.monogramFor = exports.defaultIsSignedIn = exports.createConsentRouter = exports.createConsentHandlers = exports.writeArray = exports.readArray = exports.isStaleWriteError = exports.isOAuthError = exports.OAuthError = exports.verifyCodeChallenge = exports.isValidCodeVerifier = exports.isValidCodeChallenge = exports.deriveCodeChallenge = exports.CODE_CHALLENGE_METHOD = exports.OAUTH_CODES_INFO_SCHEMA = exports.OAUTH_CODES_PROPERTY = exports.createAuthorizationCodeService = exports.OAUTH_GRANTS_INFO_SCHEMA = exports.OAUTH_GRANTS_PROPERTY = exports.isGrantActive = exports.createGrantService = void 0;
22
+ exports.DEFAULT_REGISTRATION_RATE_LIMIT = exports.DEFAULT_METADATA_RESOLUTION_RATE_LIMIT = exports.rateLimitAddress = exports.createRegistrationRateLimiter = exports.createFixedWindowRateLimiter = exports.toRegistrationResponse = exports.createRegistrationRouter = exports.createRegistrationHandlers = exports.createDiscoveryRouter = exports.createDiscoveryHandlers = exports.REFRESH_TOKEN_TTL_BOUNDS = exports.ACCESS_TOKEN_TTL_BOUNDS = exports.DEFAULT_REFRESH_TOKEN_TTL_SECONDS = exports.DEFAULT_ACCESS_TOKEN_TTL_SECONDS = exports.OauthServerConfigError = exports.clampTtlSeconds = exports.readServerConfig = exports.protectedResourceMetadataPaths = exports.PROTECTED_RESOURCE_METADATA_PATH = exports.DISCOVERY_MAX_AGE_SECONDS = exports.DEFAULT_SCOPES_SUPPORTED = exports.DEVICE_CODE_GRANT_TYPE = exports.CODE_CHALLENGE_METHODS_SUPPORTED = exports.buildProtectedResourceMetadata = exports.buildAuthorizationServerMetadata = exports.DEFAULT_METADATA_MAX_REDIRECTS = exports.DEFAULT_METADATA_MAX_BYTES = exports.DEFAULT_METADATA_TIMEOUT_MS = exports.isValidMetadataUri = exports.fetchMetadataDocument = exports.isBlockedAddress = exports.blockedHostReason = exports.DEFAULT_MAX_REGISTERED_CLIENTS = exports.DEFAULT_ABANDONED_CLIENT_TTL_MS = exports.DEFAULT_METADATA_MAX_AGE_MS = exports.DEFAULT_RESPONSE_TYPES = exports.DEFAULT_GRANT_TYPES = exports.STRANGER_CREATED_MODES = exports.validateRedirectUris = exports.createClientRegistryService = exports.isClientRegistryError = exports.ClientRegistryError = exports.CLIENT_PUBLIC_ID_LENGTH = exports.CLIENT_PUBLIC_ID_ALPHABET = exports.isValidClientPublicId = exports.generateClientPublicId = exports.isLoopbackHost = exports.matchesAnyRedirectUri = exports.matchRedirectUri = exports.classifyRedirectUri = void 0;
23
+ exports.REVOKED_BY_CLIENT = exports.createRevocationService = exports.createClientPruneJob = exports.createClientInUsePredicate = exports.DEFAULT_BEARER_CHALLENGE = exports.requireOAuth = exports.missingScopes = exports.createOAuthTokenResolver = exports.createOAuthResourceServer = exports.createOAuthBearerMiddleware = exports.OFFLINE_ACCESS_SCOPE = exports.parseScopes = exports.createTokenRouter = exports.createTokenHandlers = exports.isSameSiteRequest = exports.SCOPE_DESCRIPTIONS_LOCAL = exports.warnUndescribedScopes = exports.toScopeViews = exports.scopeDescriptionsFrom = exports.safeHttpsUri = exports.provenanceFor = exports.monogramFor = exports.defaultIsSignedIn = exports.createConsentRouter = exports.createConsentHandlers = exports.writeArray = exports.readArray = exports.isStaleWriteError = exports.isOAuthError = exports.OAuthError = exports.verifyCodeChallenge = exports.isValidCodeVerifier = exports.isValidCodeChallenge = exports.deriveCodeChallenge = exports.CODE_CHALLENGE_METHOD = exports.OAUTH_CODES_INFO_SCHEMA = exports.OAUTH_CODES_PROPERTY = exports.createAuthorizationCodeService = exports.OAUTH_GRANTS_INFO_SCHEMA = exports.OAUTH_GRANTS_PROPERTY = exports.isGrantActive = exports.createGrantService = exports.OAUTH_REFRESH_DOMAIN = exports.OAUTH_ACCESS_DOMAIN = exports.OAUTH_CODE_DOMAIN = exports.AUTHORIZATION_CODE_TTL_SECONDS = exports.buildIdentityKey = exports.createTokenService = exports.CLIENT_MODES = exports.DEFAULT_REGISTRATION_RATE_WINDOW_MS = void 0;
24
+ exports.ACCOUNT_COVERAGE_STATEMENT = exports.createConnectionsRouter = exports.createConnectionsHandlers = exports.createRevocationRouter = exports.createRevocationHandlers = exports.REVOKED_CLIENT_WIDE = exports.REVOKED_BY_USER = void 0;
24
25
  // Side-effect import: the Express request augmentation this capability's
25
26
  // middleware writes (`req.oauthGrant`) and its consent controller reads
26
27
  // (`req.user`). Importing the package is what gives an application those
@@ -42,11 +43,15 @@ Object.defineProperty(exports, "isClientRegistryError", { enumerable: true, get:
42
43
  var client_registry_service_js_1 = require("./client-registry-service.js");
43
44
  Object.defineProperty(exports, "createClientRegistryService", { enumerable: true, get: function () { return client_registry_service_js_1.createClientRegistryService; } });
44
45
  Object.defineProperty(exports, "validateRedirectUris", { enumerable: true, get: function () { return client_registry_service_js_1.validateRedirectUris; } });
46
+ Object.defineProperty(exports, "STRANGER_CREATED_MODES", { enumerable: true, get: function () { return client_registry_service_js_1.STRANGER_CREATED_MODES; } });
45
47
  Object.defineProperty(exports, "DEFAULT_GRANT_TYPES", { enumerable: true, get: function () { return client_registry_service_js_1.DEFAULT_GRANT_TYPES; } });
46
48
  Object.defineProperty(exports, "DEFAULT_RESPONSE_TYPES", { enumerable: true, get: function () { return client_registry_service_js_1.DEFAULT_RESPONSE_TYPES; } });
47
49
  Object.defineProperty(exports, "DEFAULT_METADATA_MAX_AGE_MS", { enumerable: true, get: function () { return client_registry_service_js_1.DEFAULT_METADATA_MAX_AGE_MS; } });
48
50
  Object.defineProperty(exports, "DEFAULT_ABANDONED_CLIENT_TTL_MS", { enumerable: true, get: function () { return client_registry_service_js_1.DEFAULT_ABANDONED_CLIENT_TTL_MS; } });
49
51
  Object.defineProperty(exports, "DEFAULT_MAX_REGISTERED_CLIENTS", { enumerable: true, get: function () { return client_registry_service_js_1.DEFAULT_MAX_REGISTERED_CLIENTS; } });
52
+ var outbound_target_js_1 = require("./outbound-target.js");
53
+ Object.defineProperty(exports, "blockedHostReason", { enumerable: true, get: function () { return outbound_target_js_1.blockedHostReason; } });
54
+ Object.defineProperty(exports, "isBlockedAddress", { enumerable: true, get: function () { return outbound_target_js_1.isBlockedAddress; } });
50
55
  var metadata_document_js_1 = require("./metadata-document.js");
51
56
  Object.defineProperty(exports, "fetchMetadataDocument", { enumerable: true, get: function () { return metadata_document_js_1.fetchMetadataDocument; } });
52
57
  Object.defineProperty(exports, "isValidMetadataUri", { enumerable: true, get: function () { return metadata_document_js_1.isValidMetadataUri; } });
@@ -60,6 +65,8 @@ Object.defineProperty(exports, "CODE_CHALLENGE_METHODS_SUPPORTED", { enumerable:
60
65
  Object.defineProperty(exports, "DEVICE_CODE_GRANT_TYPE", { enumerable: true, get: function () { return discovery_js_1.DEVICE_CODE_GRANT_TYPE; } });
61
66
  Object.defineProperty(exports, "DEFAULT_SCOPES_SUPPORTED", { enumerable: true, get: function () { return discovery_js_1.DEFAULT_SCOPES_SUPPORTED; } });
62
67
  Object.defineProperty(exports, "DISCOVERY_MAX_AGE_SECONDS", { enumerable: true, get: function () { return discovery_js_1.DISCOVERY_MAX_AGE_SECONDS; } });
68
+ Object.defineProperty(exports, "PROTECTED_RESOURCE_METADATA_PATH", { enumerable: true, get: function () { return discovery_js_1.PROTECTED_RESOURCE_METADATA_PATH; } });
69
+ Object.defineProperty(exports, "protectedResourceMetadataPaths", { enumerable: true, get: function () { return discovery_js_1.protectedResourceMetadataPaths; } });
63
70
  var config_js_1 = require("./config.js");
64
71
  Object.defineProperty(exports, "readServerConfig", { enumerable: true, get: function () { return config_js_1.readServerConfig; } });
65
72
  Object.defineProperty(exports, "clampTtlSeconds", { enumerable: true, get: function () { return config_js_1.clampTtlSeconds; } });
@@ -76,7 +83,10 @@ Object.defineProperty(exports, "createRegistrationHandlers", { enumerable: true,
76
83
  Object.defineProperty(exports, "createRegistrationRouter", { enumerable: true, get: function () { return registration_controller_js_1.createRegistrationRouter; } });
77
84
  Object.defineProperty(exports, "toRegistrationResponse", { enumerable: true, get: function () { return registration_controller_js_1.toRegistrationResponse; } });
78
85
  var rate_limit_js_1 = require("./rate-limit.js");
86
+ Object.defineProperty(exports, "createFixedWindowRateLimiter", { enumerable: true, get: function () { return rate_limit_js_1.createFixedWindowRateLimiter; } });
79
87
  Object.defineProperty(exports, "createRegistrationRateLimiter", { enumerable: true, get: function () { return rate_limit_js_1.createRegistrationRateLimiter; } });
88
+ Object.defineProperty(exports, "rateLimitAddress", { enumerable: true, get: function () { return rate_limit_js_1.rateLimitAddress; } });
89
+ Object.defineProperty(exports, "DEFAULT_METADATA_RESOLUTION_RATE_LIMIT", { enumerable: true, get: function () { return rate_limit_js_1.DEFAULT_METADATA_RESOLUTION_RATE_LIMIT; } });
80
90
  Object.defineProperty(exports, "DEFAULT_REGISTRATION_RATE_LIMIT", { enumerable: true, get: function () { return rate_limit_js_1.DEFAULT_REGISTRATION_RATE_LIMIT; } });
81
91
  Object.defineProperty(exports, "DEFAULT_REGISTRATION_RATE_WINDOW_MS", { enumerable: true, get: function () { return rate_limit_js_1.DEFAULT_REGISTRATION_RATE_WINDOW_MS; } });
82
92
  var types_js_1 = require("./types.js");
@@ -117,11 +127,13 @@ Object.defineProperty(exports, "createConsentRouter", { enumerable: true, get: f
117
127
  Object.defineProperty(exports, "defaultIsSignedIn", { enumerable: true, get: function () { return consent_controller_js_1.defaultIsSignedIn; } });
118
128
  Object.defineProperty(exports, "monogramFor", { enumerable: true, get: function () { return consent_controller_js_1.monogramFor; } });
119
129
  Object.defineProperty(exports, "provenanceFor", { enumerable: true, get: function () { return consent_controller_js_1.provenanceFor; } });
120
- Object.defineProperty(exports, "safeLogoUri", { enumerable: true, get: function () { return consent_controller_js_1.safeLogoUri; } });
130
+ Object.defineProperty(exports, "safeHttpsUri", { enumerable: true, get: function () { return consent_controller_js_1.safeHttpsUri; } });
121
131
  Object.defineProperty(exports, "scopeDescriptionsFrom", { enumerable: true, get: function () { return consent_controller_js_1.scopeDescriptionsFrom; } });
122
132
  Object.defineProperty(exports, "toScopeViews", { enumerable: true, get: function () { return consent_controller_js_1.toScopeViews; } });
123
133
  Object.defineProperty(exports, "warnUndescribedScopes", { enumerable: true, get: function () { return consent_controller_js_1.warnUndescribedScopes; } });
124
134
  Object.defineProperty(exports, "SCOPE_DESCRIPTIONS_LOCAL", { enumerable: true, get: function () { return consent_controller_js_1.SCOPE_DESCRIPTIONS_LOCAL; } });
135
+ var same_site_js_1 = require("./same-site.js");
136
+ Object.defineProperty(exports, "isSameSiteRequest", { enumerable: true, get: function () { return same_site_js_1.isSameSiteRequest; } });
125
137
  var token_controller_js_1 = require("./token-controller.js");
126
138
  Object.defineProperty(exports, "createTokenHandlers", { enumerable: true, get: function () { return token_controller_js_1.createTokenHandlers; } });
127
139
  Object.defineProperty(exports, "createTokenRouter", { enumerable: true, get: function () { return token_controller_js_1.createTokenRouter; } });
@@ -134,6 +146,9 @@ Object.defineProperty(exports, "createOAuthTokenResolver", { enumerable: true, g
134
146
  Object.defineProperty(exports, "missingScopes", { enumerable: true, get: function () { return bearer_js_1.missingScopes; } });
135
147
  Object.defineProperty(exports, "requireOAuth", { enumerable: true, get: function () { return bearer_js_1.requireOAuth; } });
136
148
  Object.defineProperty(exports, "DEFAULT_BEARER_CHALLENGE", { enumerable: true, get: function () { return bearer_js_1.DEFAULT_BEARER_CHALLENGE; } });
149
+ var client_prune_js_1 = require("./client-prune.js");
150
+ Object.defineProperty(exports, "createClientInUsePredicate", { enumerable: true, get: function () { return client_prune_js_1.createClientInUsePredicate; } });
151
+ Object.defineProperty(exports, "createClientPruneJob", { enumerable: true, get: function () { return client_prune_js_1.createClientPruneJob; } });
137
152
  var revocation_service_js_1 = require("./revocation-service.js");
138
153
  Object.defineProperty(exports, "createRevocationService", { enumerable: true, get: function () { return revocation_service_js_1.createRevocationService; } });
139
154
  Object.defineProperty(exports, "REVOKED_BY_CLIENT", { enumerable: true, get: function () { return revocation_service_js_1.REVOKED_BY_CLIENT; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,16 @@ import type { ClientIdMetadataDocument } from './types.js';
11
11
  * drive it directly: what a failing fetch, an oversized body or a lying
12
12
  * document do is behaviour worth pinning, and none of it is reachable through a
13
13
  * real network in a test suite.
14
+ *
15
+ * ## Where the URL may point
16
+ *
17
+ * The bounds below — timeout, size, hop count — bound what a fetch can *cost*.
18
+ * They say nothing about where it goes, and for a while nothing did: an
19
+ * unauthenticated `GET /oauth/authorize` naming any https URL made this server
20
+ * issue a request to it, so the response time answered "is there something at
21
+ * `10.0.0.5:443`?" for anyone who asked (boop-jd4x). `outbound-target.ts` is
22
+ * that guard, and it runs **before every hop**, including the ones a redirect
23
+ * chooses.
14
24
  */
15
25
  /** The `fetch` shape this module needs. Narrower than the global on purpose. */
16
26
  export type FetchLike = (url: string, init: {
@@ -36,6 +46,18 @@ export interface MetadataFetchOptions {
36
46
  maxBytes?: number;
37
47
  /** How many `https`→`https` hops to follow before giving up. */
38
48
  maxRedirects?: number;
49
+ /**
50
+ * Resolve a hostname to the addresses it points at.
51
+ *
52
+ * Optional. Without it, every *literal* private, loopback or link-local
53
+ * target is still refused, and so are the names reserved for a local network;
54
+ * with it, a perfectly ordinary name that answers `10.0.0.5` is refused too.
55
+ * Injected rather than imported because a capability package has no business
56
+ * reaching for `node:dns`, and because a resolver in the module body is a
57
+ * resolver no test can stand in front of. The scaffold wires
58
+ * `dns.promises.lookup`.
59
+ */
60
+ resolveHost?: (hostname: string) => Promise<string[]>;
39
61
  }
40
62
  export declare const DEFAULT_METADATA_TIMEOUT_MS = 3000;
41
63
  export declare const DEFAULT_METADATA_MAX_BYTES: number;
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-document.d.ts","sourceRoot":"","sources":["../src/metadata-document.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;;;;;GAYG;AAEH,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,KACE,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AACjD,eAAO,MAAM,0BAA0B,QAAY,CAAC;AACpD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAKhD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAczD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CA+CnC"}
1
+ {"version":3,"file":"metadata-document.d.ts","sourceRoot":"","sources":["../src/metadata-document.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,gFAAgF;AAChF,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,KACE,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAC9C,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED,MAAM,WAAW,oBAAoB;IACnC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACvD;AAED,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AACjD,eAAO,MAAM,0BAA0B,QAAY,CAAC;AACpD,eAAO,MAAM,8BAA8B,IAAI,CAAC;AAKhD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAczD;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,SAAS,EACpB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,wBAAwB,CAAC,CAoDnC"}
@@ -4,6 +4,7 @@ exports.DEFAULT_METADATA_MAX_REDIRECTS = exports.DEFAULT_METADATA_MAX_BYTES = ex
4
4
  exports.isValidMetadataUri = isValidMetadataUri;
5
5
  exports.fetchMetadataDocument = fetchMetadataDocument;
6
6
  const errors_js_1 = require("./errors.js");
7
+ const outbound_target_js_1 = require("./outbound-target.js");
7
8
  exports.DEFAULT_METADATA_TIMEOUT_MS = 3000;
8
9
  exports.DEFAULT_METADATA_MAX_BYTES = 64 * 1024;
9
10
  exports.DEFAULT_METADATA_MAX_REDIRECTS = 3;
@@ -44,7 +45,12 @@ async function fetchMetadataDocument(clientId, fetchImpl, options = {}) {
44
45
  if (!isValidMetadataUri(clientId)) {
45
46
  throw unusable(`client_id is not a usable https metadata document URL: ${clientId}`);
46
47
  }
47
- const body = await readDocument(clientId, fetchImpl, { timeoutMs, maxBytes, maxRedirects });
48
+ const body = await readDocument(clientId, fetchImpl, {
49
+ timeoutMs,
50
+ maxBytes,
51
+ maxRedirects,
52
+ resolveHost: options.resolveHost,
53
+ });
48
54
  let parsed;
49
55
  try {
50
56
  parsed = JSON.parse(body);
@@ -78,10 +84,46 @@ async function fetchMetadataDocument(clientId, fetchImpl, options = {}) {
78
84
  }
79
85
  return doc;
80
86
  }
81
- /** The transport half: redirects, timeout, size cap. */
87
+ /**
88
+ * Refuse a target that is not on the public internet.
89
+ *
90
+ * Called before the request rather than after it, because the request *is* the
91
+ * leak: the body is discarded on a refusal either way, and what an attacker
92
+ * reads is whether the connection was made and how long it took.
93
+ */
94
+ async function assertPublicTarget(url, resolveHost) {
95
+ const { hostname } = new URL(url);
96
+ const reason = (0, outbound_target_js_1.blockedHostReason)(hostname);
97
+ if (reason !== null)
98
+ throw unusable(`metadata document at ${url} is not fetchable: ${reason}`);
99
+ if (!resolveHost)
100
+ return;
101
+ let addresses;
102
+ try {
103
+ addresses = await resolveHost(hostname);
104
+ }
105
+ catch (err) {
106
+ // Fail closed. A name we could not resolve is a name we cannot vouch for,
107
+ // and "could not check" must never read as "checked and fine".
108
+ throw unusable(`metadata document at ${url} could not be resolved: ${describe(err)}`);
109
+ }
110
+ // An empty answer is not a clean bill of health: it means nothing was
111
+ // checked, which is the same position as a resolver that threw.
112
+ if (addresses.length === 0) {
113
+ throw unusable(`metadata document at ${url} resolves to no address`);
114
+ }
115
+ const blocked = addresses.find((address) => (0, outbound_target_js_1.isBlockedAddress)(address));
116
+ if (blocked !== undefined) {
117
+ throw unusable(`metadata document at ${url} resolves to ${blocked}, which is not a public address`);
118
+ }
119
+ }
120
+ /** The transport half: target guard, redirects, timeout, size cap. */
82
121
  async function readDocument(startUrl, fetchImpl, limits) {
83
122
  let url = startUrl;
84
123
  for (let hop = 0; hop <= limits.maxRedirects; hop += 1) {
124
+ // Every hop, not just the first: a public URL that redirects to
125
+ // `https://169.254.169.254` is the same probe with one step in front of it.
126
+ await assertPublicTarget(url, limits.resolveHost);
85
127
  const controller = new AbortController();
86
128
  const timer = setTimeout(() => controller.abort(), limits.timeoutMs);
87
129
  let response;
@@ -1 +1 @@
1
- {"version":3,"file":"metadata-document.js","sourceRoot":"","sources":["../src/metadata-document.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"metadata-document.js","sourceRoot":"","sources":["../src/metadata-document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Where a caller-chosen URL may point.
3
+ *
4
+ * One place in this capability fetches a URL a stranger supplied: the Client ID
5
+ * Metadata Document path, reached from an unauthenticated `GET /oauth/authorize`
6
+ * whose `client_id` is an https URL. Without a guard, that is an SSRF primitive
7
+ * pointed at whatever network the application runs in — the document body is
8
+ * discarded on a failure, but *existence* and *latency* are both readable
9
+ * through the request's response time, and the outbound request happens whatever
10
+ * the answer (boop-jd4x).
11
+ *
12
+ * ## What this can and cannot promise
13
+ *
14
+ * The literal tables below are exact: an address in one of these ranges is never
15
+ * fetched. A *name* is harder, and is handled in two layers.
16
+ *
17
+ * 1. Names reserved by convention for a local network — `localhost`, `.local`,
18
+ * `.internal`, `.home.arpa` — are refused outright.
19
+ * 2. Everything else needs DNS, which this package will not do: it has no
20
+ * business importing `node:dns`, and a capability that did could not be
21
+ * tested without a resolver. So the resolver is injected
22
+ * (`metadataResolveHost`), and when an application supplies one, every
23
+ * address a name resolves to is run through the same table.
24
+ *
25
+ * Even with a resolver this is not airtight, and the gap has a name: between the
26
+ * lookup here and the connection the HTTP client makes, the name can be
27
+ * re-resolved to a different address (DNS rebinding). Closing that needs a
28
+ * transport that connects to an address this code chose, which `fetch` does not
29
+ * offer. What is here raises the cost of the probe from "free" to "needs a
30
+ * rebinding setup", and refuses the whole class of literal and by-name targets
31
+ * that a probe actually uses.
32
+ */
33
+ /**
34
+ * Is this string a literal address we refuse to connect to?
35
+ *
36
+ * `false` for anything that is not an address at all, including every hostname —
37
+ * a name is `blockedHostReason`'s business, because deciding it needs DNS.
38
+ */
39
+ export declare function isBlockedAddress(value: string): boolean;
40
+ /**
41
+ * Why this hostname may not be fetched, or `null` if it may.
42
+ *
43
+ * A sentence rather than a boolean, because the caller puts it in a refusal an
44
+ * operator has to read: "could not be fetched" tells them nothing, and a CIMD
45
+ * publisher who pointed at the wrong host needs to see which host.
46
+ */
47
+ export declare function blockedHostReason(hostname: string): string | null;
48
+ //# sourceMappingURL=outbound-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-target.d.ts","sourceRoot":"","sources":["../src/outbound-target.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AA+GH;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAWvD;AASD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkBjE"}
@@ -0,0 +1,211 @@
1
+ "use strict";
2
+ /**
3
+ * Where a caller-chosen URL may point.
4
+ *
5
+ * One place in this capability fetches a URL a stranger supplied: the Client ID
6
+ * Metadata Document path, reached from an unauthenticated `GET /oauth/authorize`
7
+ * whose `client_id` is an https URL. Without a guard, that is an SSRF primitive
8
+ * pointed at whatever network the application runs in — the document body is
9
+ * discarded on a failure, but *existence* and *latency* are both readable
10
+ * through the request's response time, and the outbound request happens whatever
11
+ * the answer (boop-jd4x).
12
+ *
13
+ * ## What this can and cannot promise
14
+ *
15
+ * The literal tables below are exact: an address in one of these ranges is never
16
+ * fetched. A *name* is harder, and is handled in two layers.
17
+ *
18
+ * 1. Names reserved by convention for a local network — `localhost`, `.local`,
19
+ * `.internal`, `.home.arpa` — are refused outright.
20
+ * 2. Everything else needs DNS, which this package will not do: it has no
21
+ * business importing `node:dns`, and a capability that did could not be
22
+ * tested without a resolver. So the resolver is injected
23
+ * (`metadataResolveHost`), and when an application supplies one, every
24
+ * address a name resolves to is run through the same table.
25
+ *
26
+ * Even with a resolver this is not airtight, and the gap has a name: between the
27
+ * lookup here and the connection the HTTP client makes, the name can be
28
+ * re-resolved to a different address (DNS rebinding). Closing that needs a
29
+ * transport that connects to an address this code chose, which `fetch` does not
30
+ * offer. What is here raises the cost of the probe from "free" to "needs a
31
+ * rebinding setup", and refuses the whole class of literal and by-name targets
32
+ * that a probe actually uses.
33
+ */
34
+ Object.defineProperty(exports, "__esModule", { value: true });
35
+ exports.isBlockedAddress = isBlockedAddress;
36
+ exports.blockedHostReason = blockedHostReason;
37
+ /** The four octets of a dotted-quad, or `null` if it is not one. */
38
+ function parseIpv4(value) {
39
+ const parts = value.split('.');
40
+ if (parts.length !== 4)
41
+ return null;
42
+ const octets = [];
43
+ for (const part of parts) {
44
+ // No octal, no hex, no leading zeros: `010.0.0.1` is a spelling of
45
+ // `8.0.0.1` in some resolvers and of `10.0.0.1` in others, and a table that
46
+ // has to guess which is a table with a hole in it. Anything ambiguous is
47
+ // not an address as far as this code is concerned, and a hostname that is
48
+ // not an address is decided by DNS instead.
49
+ if (!/^(0|[1-9]\d{0,2})$/.test(part))
50
+ return null;
51
+ const octet = Number(part);
52
+ if (octet > 255)
53
+ return null;
54
+ octets.push(octet);
55
+ }
56
+ return octets;
57
+ }
58
+ /** The sixteen bytes of an IPv6 address, or `null` if it is not one. */
59
+ function parseIpv6(value) {
60
+ // A zone index (`fe80::1%eth0`) names an interface, not a different address.
61
+ let text = value.toLowerCase().split('%')[0];
62
+ if (!text.includes(':'))
63
+ return null;
64
+ if (!/^[0-9a-f:.]*$/.test(text))
65
+ return null;
66
+ // A trailing dotted-quad (`::ffff:127.0.0.1`) is the same address as the two
67
+ // hextets it stands for, so rewrite it and carry on with one parser.
68
+ const lastColon = text.lastIndexOf(':');
69
+ const tail = text.slice(lastColon + 1);
70
+ if (tail.includes('.')) {
71
+ const quad = parseIpv4(tail);
72
+ if (!quad)
73
+ return null;
74
+ const high = ((quad[0] << 8) | quad[1]).toString(16);
75
+ const low = ((quad[2] << 8) | quad[3]).toString(16);
76
+ text = `${text.slice(0, lastColon + 1)}${high}:${low}`;
77
+ }
78
+ const halves = text.split('::');
79
+ if (halves.length > 2)
80
+ return null;
81
+ const head = halves[0] === '' ? [] : halves[0].split(':');
82
+ const rest = halves.length === 2 ? (halves[1] === '' ? [] : halves[1].split(':')) : [];
83
+ let groups;
84
+ if (halves.length === 2) {
85
+ const fill = 8 - head.length - rest.length;
86
+ if (fill < 1)
87
+ return null;
88
+ groups = [...head, ...Array(fill).fill('0'), ...rest];
89
+ }
90
+ else {
91
+ groups = head;
92
+ }
93
+ if (groups.length !== 8)
94
+ return null;
95
+ const bytes = [];
96
+ for (const group of groups) {
97
+ if (!/^[0-9a-f]{1,4}$/.test(group))
98
+ return null;
99
+ const hextet = Number.parseInt(group, 16);
100
+ bytes.push(hextet >>> 8, hextet & 0xff);
101
+ }
102
+ return bytes;
103
+ }
104
+ /**
105
+ * Is this IPv4 address somewhere other than the public internet?
106
+ *
107
+ * The list is IANA's special-purpose registry, narrowed to the entries a probe
108
+ * would actually aim at. `169.254.0.0/16` is the one to notice: every major
109
+ * cloud puts its instance metadata service at `169.254.169.254`, and that
110
+ * endpoint hands out credentials.
111
+ */
112
+ function isBlockedIpv4(octets) {
113
+ const [a, b] = octets;
114
+ if (a === 0)
115
+ return true; // "this network"
116
+ if (a === 10)
117
+ return true; // RFC 1918
118
+ if (a === 127)
119
+ return true; // loopback
120
+ if (a === 100 && b >= 64 && b <= 127)
121
+ return true; // RFC 6598 carrier-grade NAT
122
+ if (a === 169 && b === 254)
123
+ return true; // link-local, incl. cloud metadata
124
+ if (a === 172 && b >= 16 && b <= 31)
125
+ return true; // RFC 1918
126
+ if (a === 192 && b === 0)
127
+ return true; // IETF protocol assignments
128
+ if (a === 192 && b === 168)
129
+ return true; // RFC 1918
130
+ if (a === 198 && (b === 18 || b === 19))
131
+ return true; // RFC 2544 benchmarking
132
+ if (a >= 224)
133
+ return true; // multicast, reserved, and 255.255.255.255
134
+ return false;
135
+ }
136
+ /** The same question for IPv6, including addresses wearing an IPv4 coat. */
137
+ function isBlockedIpv6(bytes) {
138
+ const isZero = (from, to) => bytes.slice(from, to).every((byte) => byte === 0);
139
+ // `::ffff:0:0/96` — an IPv4 address in IPv6 clothing. Judged as the IPv4
140
+ // address it is, or `::ffff:127.0.0.1` would walk straight past the table.
141
+ if (isZero(0, 10) && bytes[10] === 0xff && bytes[11] === 0xff) {
142
+ return isBlockedIpv4(bytes.slice(12));
143
+ }
144
+ // `64:ff9b::/96` — the well-known NAT64 prefix, which is another way to spell
145
+ // an IPv4 destination for a network that has a translator. Same treatment.
146
+ if (bytes[0] === 0x00 && bytes[1] === 0x64 && bytes[2] === 0xff && bytes[3] === 0x9b &&
147
+ isZero(4, 12)) {
148
+ return isBlockedIpv4(bytes.slice(12));
149
+ }
150
+ // `::` (unspecified) and `::1` (loopback), and the rest of `::/64` with them:
151
+ // nothing in that range is a host on the public internet.
152
+ if (isZero(0, 8))
153
+ return true;
154
+ if ((bytes[0] & 0xfe) === 0xfc)
155
+ return true; // fc00::/7 unique local
156
+ if (bytes[0] === 0xfe && (bytes[1] & 0xc0) === 0x80)
157
+ return true; // fe80::/10 link-local
158
+ if (bytes[0] === 0xff)
159
+ return true; // ff00::/8 multicast
160
+ return false;
161
+ }
162
+ /**
163
+ * Is this string a literal address we refuse to connect to?
164
+ *
165
+ * `false` for anything that is not an address at all, including every hostname —
166
+ * a name is `blockedHostReason`'s business, because deciding it needs DNS.
167
+ */
168
+ function isBlockedAddress(value) {
169
+ const text = value.trim();
170
+ if (text === '')
171
+ return false;
172
+ const ipv4 = parseIpv4(text);
173
+ if (ipv4)
174
+ return isBlockedIpv4(ipv4);
175
+ const ipv6 = parseIpv6(text);
176
+ if (ipv6)
177
+ return isBlockedIpv6(ipv6);
178
+ return false;
179
+ }
180
+ /**
181
+ * Names reserved for a local network. RFC 6761 (`localhost`), RFC 6762
182
+ * (`.local`), RFC 8375 (`.home.arpa`), and `.internal`, which is what cloud
183
+ * providers hand out for private zones and which ICANN has now reserved.
184
+ */
185
+ const BLOCKED_SUFFIXES = ['.localhost', '.local', '.internal', '.home.arpa'];
186
+ /**
187
+ * Why this hostname may not be fetched, or `null` if it may.
188
+ *
189
+ * A sentence rather than a boolean, because the caller puts it in a refusal an
190
+ * operator has to read: "could not be fetched" tells them nothing, and a CIMD
191
+ * publisher who pointed at the wrong host needs to see which host.
192
+ */
193
+ function blockedHostReason(hostname) {
194
+ // A URL spells an IPv6 literal in brackets; the address inside is what the
195
+ // table knows about.
196
+ const host = hostname.trim().toLowerCase().replace(/^\[|\]$/g, '');
197
+ if (host === '')
198
+ return 'the URL names no host';
199
+ if (isBlockedAddress(host)) {
200
+ return `${hostname} is a private, loopback, link-local or otherwise non-public address`;
201
+ }
202
+ // A trailing dot is a fully-qualified spelling of the same name.
203
+ const name = host.endsWith('.') ? host.slice(0, -1) : host;
204
+ if (name === 'localhost')
205
+ return `${hostname} names this machine`;
206
+ if (BLOCKED_SUFFIXES.some((suffix) => name.endsWith(suffix))) {
207
+ return `${hostname} is a name reserved for a local network`;
208
+ }
209
+ return null;
210
+ }
211
+ //# sourceMappingURL=outbound-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outbound-target.js","sourceRoot":"","sources":["../src/outbound-target.ts"],"names":[],"mappings":"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"}
@@ -1,13 +1,20 @@
1
- import type { RequestHandler } from 'express';
1
+ import type { Request, RequestHandler } from 'express';
2
2
  /**
3
- * Per-IP rate limiting for the one endpoint that lets a stranger write a row.
3
+ * Per-IP rate limiting for the paths that let a stranger write a row.
4
4
  *
5
- * `POST /oauth/register` is unauthenticated by design — RFC 7591 open
6
- * registration has no caller to authenticate — so there is no account to hang a
7
- * quota on and the source address is the only thing a limiter has to go on.
8
- * Neither donor has anything like this: paywalls' registration endpoint
9
- * required an authenticated `service-user`, which is a gate this endpoint
10
- * cannot have.
5
+ * There are two, and for a long time only one of them had this. `POST
6
+ * /oauth/register` is unauthenticated by design — RFC 7591 open registration has
7
+ * no caller to authenticate — so there is no account to hang a quota on and the
8
+ * source address is the only thing a limiter has to go on. Neither donor has
9
+ * anything like this: paywalls' registration endpoint required an authenticated
10
+ * `service-user`, which is a gate this endpoint cannot have.
11
+ *
12
+ * The second is **first-sight metadata-document resolution** at `GET
13
+ * /oauth/authorize`. It is equally unauthenticated, it writes a row per distinct
14
+ * URL, and it makes an outbound request besides — so it is limited by the same
15
+ * counter, mounted by the consent handlers rather than by a router, because only
16
+ * the handler knows whether a given request is about to resolve a new client or
17
+ * serve one that already has a row (boop-jd4x).
11
18
  *
12
19
  * ## What this is, and what it is not
13
20
  *
@@ -27,6 +34,14 @@ import type { RequestHandler } from 'express';
27
34
  * boundary. Named here rather than fixed, because the mitigation (a sliding
28
35
  * window) buys precision this does not need against a ceiling that is already
29
36
  * the real bound.
37
+ *
38
+ * ## The address has to be the caller's
39
+ *
40
+ * Both limiters key on `req.ip`, which behind a proxy is the proxy unless the
41
+ * application has set `trust proxy`. Left unset, every caller shares one bucket:
42
+ * one abuser exhausts it for everybody, and no individual abuser is ever
43
+ * stopped. That is worth checking in a deployment — it is the difference between
44
+ * a limiter and a decoration.
30
45
  */
31
46
  export interface RateLimiterOptions {
32
47
  /** Requests allowed per window, per address. */
@@ -43,14 +58,41 @@ export interface RateLimiterOptions {
43
58
  }
44
59
  export declare const DEFAULT_REGISTRATION_RATE_LIMIT = 10;
45
60
  export declare const DEFAULT_REGISTRATION_RATE_WINDOW_MS: number;
46
- export interface RegistrationRateLimiter {
61
+ /**
62
+ * First-sight metadata-document resolutions allowed per address per window.
63
+ *
64
+ * The same number as registration, and for the same reason: reaching it is not
65
+ * something an honest browser does. A resolution is charged only when the
66
+ * `client_id` is an https URL with **no row yet** — every later visit to the
67
+ * same client is served from the row and costs nothing — so ten distinct,
68
+ * never-before-seen connectors an hour from one address is already generous.
69
+ */
70
+ export declare const DEFAULT_METADATA_RESOLUTION_RATE_LIMIT = 10;
71
+ export interface FixedWindowRateLimiter {
72
+ /** The `POST /oauth/register` mount. */
47
73
  middleware: RequestHandler;
48
- /** Exposed for tests and for an operator script; not part of the route. */
74
+ /**
75
+ * The counter itself, for a caller that decides *whether* a request counts —
76
+ * the consent handlers charge only a first-sight resolution — and for tests
77
+ * and operator scripts.
78
+ */
49
79
  check(address: string): {
50
80
  allowed: boolean;
51
81
  retryAfterSeconds: number;
52
82
  };
53
83
  reset(): void;
54
84
  }
55
- export declare function createRegistrationRateLimiter(options?: RateLimiterOptions): RegistrationRateLimiter;
85
+ /** The old name, kept so an application that imported it still compiles. */
86
+ export type RegistrationRateLimiter = FixedWindowRateLimiter;
87
+ /**
88
+ * The address a limiter counts against.
89
+ *
90
+ * Exported so the middleware and the consent handlers cannot pick different
91
+ * answers — two limiters keyed differently are two quotas, and the one an
92
+ * attacker would find is whichever is keyed more loosely.
93
+ */
94
+ export declare function rateLimitAddress(req: Request): string;
95
+ export declare function createFixedWindowRateLimiter(options?: RateLimiterOptions): FixedWindowRateLimiter;
96
+ /** The registration endpoint's name for the same factory. */
97
+ export declare const createRegistrationRateLimiter: typeof createFixedWindowRateLimiter;
56
98
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,eAAO,MAAM,mCAAmC,QAAiB,CAAC;AAQlE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,cAAc,CAAC;IAC3B,2EAA2E;IAC3E,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,kBAAuB,GAC/B,uBAAuB,CAmDzB"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,OAAO,EAAE,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,MAAM,WAAW,kBAAkB;IACjC,gDAAgD;IAChD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qCAAqC;IACrC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,+BAA+B,KAAK,CAAC;AAClD,eAAO,MAAM,mCAAmC,QAAiB,CAAC;AAElE;;;;;;;;GAQG;AACH,eAAO,MAAM,sCAAsC,KAAK,CAAC;AAQzD,MAAM,WAAW,sBAAsB;IACrC,wCAAwC;IACxC,UAAU,EAAE,cAAc,CAAC;IAC3B;;;;OAIG;IACH,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,iBAAiB,EAAE,MAAM,CAAA;KAAE,CAAC;IACxE,KAAK,IAAI,IAAI,CAAC;CACf;AAED,4EAA4E;AAC5E,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,CAAC;AAE7D;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAErD;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,GAAE,kBAAuB,GAC/B,sBAAsB,CAmDxB;AAED,6DAA6D;AAC7D,eAAO,MAAM,6BAA6B,qCAA+B,CAAC"}