@dbx-tools/cli-tunnel 0.6.59 → 0.6.85
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 +1 -364
- package/index.ts +2 -19
- package/lib/index.d.ts +2 -19
- package/lib/index.js +2 -15
- package/lib/src/app.d.ts +12 -123
- package/lib/src/app.js +22 -250
- package/lib/src/cli.d.ts +19 -22
- package/lib/src/cli.js +143 -128
- package/lib/src/options.d.ts +46 -0
- package/lib/src/options.js +51 -0
- package/lib/src/proxy.d.ts +27 -37
- package/lib/src/proxy.js +115 -220
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -81
- package/src/app.ts +20 -282
- package/src/cli.ts +152 -162
- package/src/options.ts +85 -0
- package/src/proxy.ts +139 -261
- package/bin/dbx-tools-tunnel.ts +0 -13
- package/lib/bin/dbx-tools-tunnel.d.ts +0 -2
- package/lib/bin/dbx-tools-tunnel.js +0 -14
- package/lib/src/allowlist.d.ts +0 -32
- package/lib/src/allowlist.js +0 -57
- package/lib/src/env.d.ts +0 -57
- package/lib/src/env.js +0 -60
- package/lib/src/headers.d.ts +0 -108
- package/lib/src/headers.js +0 -140
- package/lib/src/otp.d.ts +0 -49
- package/lib/src/otp.js +0 -124
- package/lib/src/plugin.d.ts +0 -147
- package/lib/src/plugin.js +0 -138
- package/lib/src/portr.d.ts +0 -40
- package/lib/src/portr.js +0 -93
- package/lib/src/rate-limit.d.ts +0 -35
- package/lib/src/rate-limit.js +0 -53
- package/lib/src/signing-key.d.ts +0 -86
- package/lib/src/signing-key.js +0 -170
- package/src/allowlist.ts +0 -60
- package/src/env.ts +0 -72
- package/src/headers.ts +0 -155
- package/src/otp.ts +0 -137
- package/src/plugin.ts +0 -269
- package/src/portr.ts +0 -113
- package/src/rate-limit.ts +0 -59
- package/src/signing-key.ts +0 -201
package/lib/src/env.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Environment-variable names the tunnel reads.
|
|
3
|
-
*
|
|
4
|
-
* Every setting is `TUNNEL_`-prefixed, matching the repo convention of naming a
|
|
5
|
-
* variable after the package that owns it (`MASTRA_*`, `TEAMS_*`,
|
|
6
|
-
* `WEB_SEARCH_*`). The gate's original names were unprefixed (`AUTH_SUBJECT`,
|
|
7
|
-
* `PUBLIC_DOMAIN`, ...), which is a real hazard for a package that runs as a
|
|
8
|
-
* WRAPPER: the tunnel and the app it wraps share one environment, so a generic
|
|
9
|
-
* name is one the wrapped app may already use for something else, and
|
|
10
|
-
* `PUBLIC_DOMAIN` in particular reads like an app-wide setting rather than a
|
|
11
|
-
* portr detail. `EMAIL_AUTH_ALLOW` was worse than generic - it sat in
|
|
12
|
-
* `@dbx-tools/email`'s `EMAIL_*` namespace while configuring the gate, not email.
|
|
13
|
-
*
|
|
14
|
-
* Each entry is an {@link EnvKey} list, EARLIEST-WINS, whose first element is the
|
|
15
|
-
* current name and whose remaining elements are the deprecated originals. A
|
|
16
|
-
* deployment set up against the old names keeps working; nothing needs a
|
|
17
|
-
* coordinated rename. Read them through `env.string` / `env.positiveInt` /
|
|
18
|
-
* `env.list`, which accept the list directly.
|
|
19
|
-
*
|
|
20
|
-
* Not renamed:
|
|
21
|
-
*
|
|
22
|
-
* - `DATABRICKS_APP_PORT` - the Databricks Apps runtime contract. The platform
|
|
23
|
-
* sets it; the gate honours it.
|
|
24
|
-
* - `PORTR_TOKEN` / `PORTR_SERVER` / `PORTR_AUTO_ADD_PATH` - upstream
|
|
25
|
-
* [portr](https://github.com/amalshaji/portr)'s own namespace, and
|
|
26
|
-
* `PORTR_AUTO_ADD_PATH` is passed straight to that binary. Renaming these
|
|
27
|
-
* would rename someone else's contract.
|
|
28
|
-
*
|
|
29
|
-
* @module
|
|
30
|
-
*/
|
|
31
|
-
/** Access allow-list patterns (domain / glob / `/regex/`). */
|
|
32
|
-
export const ALLOW_ENV = ["TUNNEL_AUTH_ALLOW", "EMAIL_AUTH_ALLOW"];
|
|
33
|
-
/** Subject line for the code email. */
|
|
34
|
-
export const SUBJECT_ENV = ["TUNNEL_AUTH_SUBJECT", "AUTH_SUBJECT"];
|
|
35
|
-
/** Display name used in the code email copy. */
|
|
36
|
-
export const BRAND_NAME_ENV = ["TUNNEL_AUTH_BRAND_NAME", "AUTH_BRAND_NAME"];
|
|
37
|
-
/** Line shown immediately above the code in the email. */
|
|
38
|
-
export const MESSAGE_ENV = ["TUNNEL_AUTH_MESSAGE", "AUTH_MESSAGE"];
|
|
39
|
-
/** Session lifetime, in seconds. */
|
|
40
|
-
export const SESSION_TTL_ENV = ["TUNNEL_AUTH_SESSION_TTL", "AUTH_SESSION_TTL"];
|
|
41
|
-
/** One-time-code lifetime, in seconds. */
|
|
42
|
-
export const CODE_TTL_ENV = ["TUNNEL_AUTH_CODE_TTL", "AUTH_CODE_TTL"];
|
|
43
|
-
/** HS256 signing secret for the session JWT. */
|
|
44
|
-
export const JWT_SECRET_ENV = ["TUNNEL_AUTH_JWT_SECRET", "AUTH_JWT_SECRET"];
|
|
45
|
-
/**
|
|
46
|
-
* Force-clear cutoff: every session issued BEFORE it stops verifying. Anything
|
|
47
|
-
* `object.toDate` accepts - a date, an ISO instant, epoch seconds/millis, or a
|
|
48
|
-
* relative duration (`-30d`, `7 days ago`).
|
|
49
|
-
*/
|
|
50
|
-
export const SESSION_CUTOFF_ENV = [
|
|
51
|
-
"TUNNEL_AUTH_SESSION_CUTOFF",
|
|
52
|
-
"TUNNEL_AUTH_SESSION_EPOCH",
|
|
53
|
-
];
|
|
54
|
-
/** The public `<subdomain>.<server>` portr should serve on. */
|
|
55
|
-
export const PUBLIC_DOMAIN_ENV = ["TUNNEL_PUBLIC_DOMAIN", "PUBLIC_DOMAIN"];
|
|
56
|
-
/** Run the tunnel OPEN, with no gate. Ignored unless truthy. */
|
|
57
|
-
export const INSECURE_ENV = "TUNNEL_INSECURE";
|
|
58
|
-
/** Extra `x-` request headers tunnel traffic may forward. */
|
|
59
|
-
export const FORWARD_HEADERS_ENV = "TUNNEL_FORWARD_HEADERS";
|
|
60
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZW52LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2Vudi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0E2Qkc7QUFJSCw4REFBOEQ7QUFDOUQsTUFBTSxDQUFDLE1BQU0sU0FBUyxHQUFXLENBQUMsbUJBQW1CLEVBQUUsa0JBQWtCLENBQUMsQ0FBQztBQUUzRSx1Q0FBdUM7QUFDdkMsTUFBTSxDQUFDLE1BQU0sV0FBVyxHQUFXLENBQUMscUJBQXFCLEVBQUUsY0FBYyxDQUFDLENBQUM7QUFFM0UsZ0RBQWdEO0FBQ2hELE1BQU0sQ0FBQyxNQUFNLGNBQWMsR0FBVyxDQUFDLHdCQUF3QixFQUFFLGlCQUFpQixDQUFDLENBQUM7QUFFcEYsMERBQTBEO0FBQzFELE1BQU0sQ0FBQyxNQUFNLFdBQVcsR0FBVyxDQUFDLHFCQUFxQixFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBRTNFLG9DQUFvQztBQUNwQyxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQVcsQ0FBQyx5QkFBeUIsRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO0FBRXZGLDBDQUEwQztBQUMxQyxNQUFNLENBQUMsTUFBTSxZQUFZLEdBQVcsQ0FBQyxzQkFBc0IsRUFBRSxlQUFlLENBQUMsQ0FBQztBQUU5RSxnREFBZ0Q7QUFDaEQsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFXLENBQUMsd0JBQXdCLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztBQUVwRjs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQVc7SUFDeEMsNEJBQTRCO0lBQzVCLDJCQUEyQjtDQUM1QixDQUFDO0FBRUYsK0RBQStEO0FBQy9ELE1BQU0sQ0FBQyxNQUFNLGlCQUFpQixHQUFXLENBQUMsc0JBQXNCLEVBQUUsZUFBZSxDQUFDLENBQUM7QUFFbkYsZ0VBQWdFO0FBQ2hFLE1BQU0sQ0FBQyxNQUFNLFlBQVksR0FBVyxpQkFBaUIsQ0FBQztBQUV0RCw2REFBNkQ7QUFDN0QsTUFBTSxDQUFDLE1BQU0sbUJBQW1CLEdBQVcsd0JBQXdCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEVudmlyb25tZW50LXZhcmlhYmxlIG5hbWVzIHRoZSB0dW5uZWwgcmVhZHMuXG4gKlxuICogRXZlcnkgc2V0dGluZyBpcyBgVFVOTkVMX2AtcHJlZml4ZWQsIG1hdGNoaW5nIHRoZSByZXBvIGNvbnZlbnRpb24gb2YgbmFtaW5nIGFcbiAqIHZhcmlhYmxlIGFmdGVyIHRoZSBwYWNrYWdlIHRoYXQgb3ducyBpdCAoYE1BU1RSQV8qYCwgYFRFQU1TXypgLFxuICogYFdFQl9TRUFSQ0hfKmApLiBUaGUgZ2F0ZSdzIG9yaWdpbmFsIG5hbWVzIHdlcmUgdW5wcmVmaXhlZCAoYEFVVEhfU1VCSkVDVGAsXG4gKiBgUFVCTElDX0RPTUFJTmAsIC4uLiksIHdoaWNoIGlzIGEgcmVhbCBoYXphcmQgZm9yIGEgcGFja2FnZSB0aGF0IHJ1bnMgYXMgYVxuICogV1JBUFBFUjogdGhlIHR1bm5lbCBhbmQgdGhlIGFwcCBpdCB3cmFwcyBzaGFyZSBvbmUgZW52aXJvbm1lbnQsIHNvIGEgZ2VuZXJpY1xuICogbmFtZSBpcyBvbmUgdGhlIHdyYXBwZWQgYXBwIG1heSBhbHJlYWR5IHVzZSBmb3Igc29tZXRoaW5nIGVsc2UsIGFuZFxuICogYFBVQkxJQ19ET01BSU5gIGluIHBhcnRpY3VsYXIgcmVhZHMgbGlrZSBhbiBhcHAtd2lkZSBzZXR0aW5nIHJhdGhlciB0aGFuIGFcbiAqIHBvcnRyIGRldGFpbC4gYEVNQUlMX0FVVEhfQUxMT1dgIHdhcyB3b3JzZSB0aGFuIGdlbmVyaWMgLSBpdCBzYXQgaW5cbiAqIGBAZGJ4LXRvb2xzL2VtYWlsYCdzIGBFTUFJTF8qYCBuYW1lc3BhY2Ugd2hpbGUgY29uZmlndXJpbmcgdGhlIGdhdGUsIG5vdCBlbWFpbC5cbiAqXG4gKiBFYWNoIGVudHJ5IGlzIGFuIHtAbGluayBFbnZLZXl9IGxpc3QsIEVBUkxJRVNULVdJTlMsIHdob3NlIGZpcnN0IGVsZW1lbnQgaXMgdGhlXG4gKiBjdXJyZW50IG5hbWUgYW5kIHdob3NlIHJlbWFpbmluZyBlbGVtZW50cyBhcmUgdGhlIGRlcHJlY2F0ZWQgb3JpZ2luYWxzLiBBXG4gKiBkZXBsb3ltZW50IHNldCB1cCBhZ2FpbnN0IHRoZSBvbGQgbmFtZXMga2VlcHMgd29ya2luZzsgbm90aGluZyBuZWVkcyBhXG4gKiBjb29yZGluYXRlZCByZW5hbWUuIFJlYWQgdGhlbSB0aHJvdWdoIGBlbnYuc3RyaW5nYCAvIGBlbnYucG9zaXRpdmVJbnRgIC9cbiAqIGBlbnYubGlzdGAsIHdoaWNoIGFjY2VwdCB0aGUgbGlzdCBkaXJlY3RseS5cbiAqXG4gKiBOb3QgcmVuYW1lZDpcbiAqXG4gKiAgIC0gYERBVEFCUklDS1NfQVBQX1BPUlRgIC0gdGhlIERhdGFicmlja3MgQXBwcyBydW50aW1lIGNvbnRyYWN0LiBUaGUgcGxhdGZvcm1cbiAqICAgICBzZXRzIGl0OyB0aGUgZ2F0ZSBob25vdXJzIGl0LlxuICogICAtIGBQT1JUUl9UT0tFTmAgLyBgUE9SVFJfU0VSVkVSYCAvIGBQT1JUUl9BVVRPX0FERF9QQVRIYCAtIHVwc3RyZWFtXG4gKiAgICAgW3BvcnRyXShodHRwczovL2dpdGh1Yi5jb20vYW1hbHNoYWppL3BvcnRyKSdzIG93biBuYW1lc3BhY2UsIGFuZFxuICogICAgIGBQT1JUUl9BVVRPX0FERF9QQVRIYCBpcyBwYXNzZWQgc3RyYWlnaHQgdG8gdGhhdCBiaW5hcnkuIFJlbmFtaW5nIHRoZXNlXG4gKiAgICAgd291bGQgcmVuYW1lIHNvbWVvbmUgZWxzZSdzIGNvbnRyYWN0LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgdHlwZSB7IEVudktleSB9IGZyb20gXCJAZGJ4LXRvb2xzL3NoYXJlZC1jb3JlXCI7XG5cbi8qKiBBY2Nlc3MgYWxsb3ctbGlzdCBwYXR0ZXJucyAoZG9tYWluIC8gZ2xvYiAvIGAvcmVnZXgvYCkuICovXG5leHBvcnQgY29uc3QgQUxMT1dfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9BTExPV1wiLCBcIkVNQUlMX0FVVEhfQUxMT1dcIl07XG5cbi8qKiBTdWJqZWN0IGxpbmUgZm9yIHRoZSBjb2RlIGVtYWlsLiAqL1xuZXhwb3J0IGNvbnN0IFNVQkpFQ1RfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9TVUJKRUNUXCIsIFwiQVVUSF9TVUJKRUNUXCJdO1xuXG4vKiogRGlzcGxheSBuYW1lIHVzZWQgaW4gdGhlIGNvZGUgZW1haWwgY29weS4gKi9cbmV4cG9ydCBjb25zdCBCUkFORF9OQU1FX0VOVjogRW52S2V5ID0gW1wiVFVOTkVMX0FVVEhfQlJBTkRfTkFNRVwiLCBcIkFVVEhfQlJBTkRfTkFNRVwiXTtcblxuLyoqIExpbmUgc2hvd24gaW1tZWRpYXRlbHkgYWJvdmUgdGhlIGNvZGUgaW4gdGhlIGVtYWlsLiAqL1xuZXhwb3J0IGNvbnN0IE1FU1NBR0VfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9NRVNTQUdFXCIsIFwiQVVUSF9NRVNTQUdFXCJdO1xuXG4vKiogU2Vzc2lvbiBsaWZldGltZSwgaW4gc2Vjb25kcy4gKi9cbmV4cG9ydCBjb25zdCBTRVNTSU9OX1RUTF9FTlY6IEVudktleSA9IFtcIlRVTk5FTF9BVVRIX1NFU1NJT05fVFRMXCIsIFwiQVVUSF9TRVNTSU9OX1RUTFwiXTtcblxuLyoqIE9uZS10aW1lLWNvZGUgbGlmZXRpbWUsIGluIHNlY29uZHMuICovXG5leHBvcnQgY29uc3QgQ09ERV9UVExfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9DT0RFX1RUTFwiLCBcIkFVVEhfQ09ERV9UVExcIl07XG5cbi8qKiBIUzI1NiBzaWduaW5nIHNlY3JldCBmb3IgdGhlIHNlc3Npb24gSldULiAqL1xuZXhwb3J0IGNvbnN0IEpXVF9TRUNSRVRfRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfQVVUSF9KV1RfU0VDUkVUXCIsIFwiQVVUSF9KV1RfU0VDUkVUXCJdO1xuXG4vKipcbiAqIEZvcmNlLWNsZWFyIGN1dG9mZjogZXZlcnkgc2Vzc2lvbiBpc3N1ZWQgQkVGT1JFIGl0IHN0b3BzIHZlcmlmeWluZy4gQW55dGhpbmdcbiAqIGBvYmplY3QudG9EYXRlYCBhY2NlcHRzIC0gYSBkYXRlLCBhbiBJU08gaW5zdGFudCwgZXBvY2ggc2Vjb25kcy9taWxsaXMsIG9yIGFcbiAqIHJlbGF0aXZlIGR1cmF0aW9uIChgLTMwZGAsIGA3IGRheXMgYWdvYCkuXG4gKi9cbmV4cG9ydCBjb25zdCBTRVNTSU9OX0NVVE9GRl9FTlY6IEVudktleSA9IFtcbiAgXCJUVU5ORUxfQVVUSF9TRVNTSU9OX0NVVE9GRlwiLFxuICBcIlRVTk5FTF9BVVRIX1NFU1NJT05fRVBPQ0hcIixcbl07XG5cbi8qKiBUaGUgcHVibGljIGA8c3ViZG9tYWluPi48c2VydmVyPmAgcG9ydHIgc2hvdWxkIHNlcnZlIG9uLiAqL1xuZXhwb3J0IGNvbnN0IFBVQkxJQ19ET01BSU5fRU5WOiBFbnZLZXkgPSBbXCJUVU5ORUxfUFVCTElDX0RPTUFJTlwiLCBcIlBVQkxJQ19ET01BSU5cIl07XG5cbi8qKiBSdW4gdGhlIHR1bm5lbCBPUEVOLCB3aXRoIG5vIGdhdGUuIElnbm9yZWQgdW5sZXNzIHRydXRoeS4gKi9cbmV4cG9ydCBjb25zdCBJTlNFQ1VSRV9FTlY6IEVudktleSA9IFwiVFVOTkVMX0lOU0VDVVJFXCI7XG5cbi8qKiBFeHRyYSBgeC1gIHJlcXVlc3QgaGVhZGVycyB0dW5uZWwgdHJhZmZpYyBtYXkgZm9yd2FyZC4gKi9cbmV4cG9ydCBjb25zdCBGT1JXQVJEX0hFQURFUlNfRU5WOiBFbnZLZXkgPSBcIlRVTk5FTF9GT1JXQVJEX0hFQURFUlNcIjtcbiJdfQ==
|
package/lib/src/headers.d.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inbound header policy for tunnel traffic.
|
|
3
|
-
*
|
|
4
|
-
* Everything the gate forwards arrives from the PUBLIC internet through the
|
|
5
|
-
* portr client, so every header on it is attacker-controlled. The headers an app
|
|
6
|
-
* trusts are precisely the ones a caller must not be able to write, because the
|
|
7
|
-
* app cannot tell a header the Databricks front door set from one a browser
|
|
8
|
-
* typed.
|
|
9
|
-
*
|
|
10
|
-
* ## Policy shape: strip by default, allow by pattern
|
|
11
|
-
*
|
|
12
|
-
* Enumerating what to remove is a losing game - a deny-list is only correct until
|
|
13
|
-
* the platform adds a header or a library starts trusting another one - so the
|
|
14
|
-
* policy is inverted. EVERY `x-`-prefixed request header is dropped from tunnel
|
|
15
|
-
* traffic unless it matches a configured pattern. That fails CLOSED: a header
|
|
16
|
-
* nobody thought about is removed rather than trusted.
|
|
17
|
-
*
|
|
18
|
-
* The allow-list is zero-to-many literals, globs, or `/regex/`es, compiled by
|
|
19
|
-
* shared-core's {@link pattern.toPatternMatcher}, and is UNIONED with
|
|
20
|
-
* {@link DEFAULT_FORWARD_HEADERS} so extending it never silently breaks the
|
|
21
|
-
* built-in surfaces. Configure it with `forwardHeaders` /
|
|
22
|
-
* `TUNNEL_FORWARD_HEADERS`.
|
|
23
|
-
*
|
|
24
|
-
* Non-`x-` headers are untouched. Standard ones (`content-type`, `accept`,
|
|
25
|
-
* `authorization`, `cookie`, ...) are the app's normal input and the gate has no
|
|
26
|
-
* business rewriting them.
|
|
27
|
-
*
|
|
28
|
-
* ## The headers no pattern can forward
|
|
29
|
-
*
|
|
30
|
-
* {@link PROTECTED_HEADERS} is stripped BEFORE the allow-list is consulted, so a
|
|
31
|
-
* permissive pattern (`x-*`, or a careless `*`) cannot re-open impersonation:
|
|
32
|
-
*
|
|
33
|
-
* | Header | What an app does with it | Why spoofing it matters |
|
|
34
|
-
* | -------------------------------- | ------------------------------- | ----------------------- |
|
|
35
|
-
* | `x-forwarded-access-token` | OBO auth (AppKit `asUser`) | Paste any workspace token and every call runs as its owner. The gate's verified email says nothing about who a pasted credential belongs to. |
|
|
36
|
-
* | `x-forwarded-user` | Caller identity | Impersonate another user. The gate sets this itself, from a verified session. |
|
|
37
|
-
* | `x-forwarded-email` | Caller identity | Same. |
|
|
38
|
-
* | `x-forwarded-preferred-username` | Display name from the IdP | Same. |
|
|
39
|
-
* | `x-forwarded-host` | The originally-requested host | Poison absolute URLs the app builds (the classic reset-link attack). |
|
|
40
|
-
* | `x-forwarded-proto` / `-port` | Original scheme / port | Convince the app a plaintext request arrived over TLS. |
|
|
41
|
-
* | `x-forwarded-for` | Client IP | Forge the audit trail, and fan out per-IP rate-limit keys (see below). |
|
|
42
|
-
* | `x-real-ip` | Client IP | Same. |
|
|
43
|
-
* | `x-request-id` | Request correlation UUID | Forge or collide trace ids, making logs unreliable. |
|
|
44
|
-
*
|
|
45
|
-
* The identity four are AppKit's OBO contract; the rest are the `X-Forwarded-*`
|
|
46
|
-
* set the Databricks Apps reverse proxy documents passing to an app
|
|
47
|
-
* ({@link https://docs.databricks.com/aws/en/dev-tools/databricks-apps/http-headers}),
|
|
48
|
-
* plus the conventional `x-forwarded-proto`/`-port`/`x-real-ip` an app or one of
|
|
49
|
-
* its libraries may read even though the table omits them.
|
|
50
|
-
*
|
|
51
|
-
* Stripping the `x-forwarded-*` transport trio is safe because `http-proxy-3` is
|
|
52
|
-
* configured with `xfwd: true` and re-adds them AFTER this policy runs - from the
|
|
53
|
-
* real socket, not from the caller's claim. The app therefore sees the honest
|
|
54
|
-
* (loopback) values instead of whatever the internet asserted. The gate reads the
|
|
55
|
-
* client IP for rate limiting from the raw inbound headers BEFORE stripping, and
|
|
56
|
-
* takes the RIGHTMOST `x-forwarded-for` entry, which is the only one a proxy
|
|
57
|
-
* appended rather than a client supplied.
|
|
58
|
-
*
|
|
59
|
-
* @module
|
|
60
|
-
*/
|
|
61
|
-
/**
|
|
62
|
-
* Headers the gate strips UNCONDITIONALLY, before the allow-list is consulted -
|
|
63
|
-
* the platform-shaped set documented in this module's table.
|
|
64
|
-
*
|
|
65
|
-
* These answer WHO a request is and WHERE it came from, and on tunnel traffic
|
|
66
|
-
* only the gate may answer that: it injects {@link token.USER_ID_HEADER} /
|
|
67
|
-
* {@link token.USER_EMAIL_HEADER} itself after verifying a session, and the proxy
|
|
68
|
-
* re-derives the transport headers from the real socket.
|
|
69
|
-
*
|
|
70
|
-
* The identity names come from the shared `token` constants, so a renamed wire
|
|
71
|
-
* contract cannot leave a stale spelling here. `x-forwarded-preferred-username`
|
|
72
|
-
* has no constant because nothing in this repo READS it - it is listed precisely
|
|
73
|
-
* so a host app that does read it cannot be fed one.
|
|
74
|
-
*/
|
|
75
|
-
export declare const PROTECTED_HEADERS: readonly string[];
|
|
76
|
-
/**
|
|
77
|
-
* The `x-` headers forwarded when a deployment configures nothing.
|
|
78
|
-
*
|
|
79
|
-
* These are the header NAMESPACES this repo's own UI sends and its own server
|
|
80
|
-
* reads - Mastra thread/model routing (`x-mastra-thread-id`, `x-mastra-model`)
|
|
81
|
-
* and MLflow trace correlation (`x-mlflow-trace-id`) - so a dbx-tools app keeps
|
|
82
|
-
* working behind the tunnel with no configuration.
|
|
83
|
-
*
|
|
84
|
-
* Deliberately GLOBS rather than the exact constants from
|
|
85
|
-
* `@dbx-tools/shared-mastra`: importing them would make the gate - a
|
|
86
|
-
* transport-level component that has no other opinion about Mastra - depend on
|
|
87
|
-
* an agent package for three strings, and a namespace glob also covers the next
|
|
88
|
-
* header those packages add. The namespaces are the stable part of the contract.
|
|
89
|
-
*/
|
|
90
|
-
export declare const DEFAULT_FORWARD_HEADERS: readonly string[];
|
|
91
|
-
/** A compiled inbound-header policy. Build one with {@link toHeaderPolicy}. */
|
|
92
|
-
export interface HeaderPolicy {
|
|
93
|
-
/** The allow-list entries backing this policy (for diagnostics and tests). */
|
|
94
|
-
readonly patterns: readonly string[];
|
|
95
|
-
/** Whether `name` survives on tunnel traffic. */
|
|
96
|
-
forwards(name: string): boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Delete every disallowed header from a mutable Node header bag, returning the
|
|
99
|
-
* lower-cased names removed (for debug logging).
|
|
100
|
-
*/
|
|
101
|
-
apply(headers: Record<string, unknown>): string[];
|
|
102
|
-
}
|
|
103
|
-
/**
|
|
104
|
-
* Compile the inbound-header policy. `configured` entries are UNIONED with
|
|
105
|
-
* {@link DEFAULT_FORWARD_HEADERS}; each may be a literal name, a glob
|
|
106
|
-
* (`x-myapp-*`), or a `/regex/`. Matching is case-insensitive.
|
|
107
|
-
*/
|
|
108
|
-
export declare function toHeaderPolicy(configured?: readonly string[]): HeaderPolicy;
|
package/lib/src/headers.js
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Inbound header policy for tunnel traffic.
|
|
3
|
-
*
|
|
4
|
-
* Everything the gate forwards arrives from the PUBLIC internet through the
|
|
5
|
-
* portr client, so every header on it is attacker-controlled. The headers an app
|
|
6
|
-
* trusts are precisely the ones a caller must not be able to write, because the
|
|
7
|
-
* app cannot tell a header the Databricks front door set from one a browser
|
|
8
|
-
* typed.
|
|
9
|
-
*
|
|
10
|
-
* ## Policy shape: strip by default, allow by pattern
|
|
11
|
-
*
|
|
12
|
-
* Enumerating what to remove is a losing game - a deny-list is only correct until
|
|
13
|
-
* the platform adds a header or a library starts trusting another one - so the
|
|
14
|
-
* policy is inverted. EVERY `x-`-prefixed request header is dropped from tunnel
|
|
15
|
-
* traffic unless it matches a configured pattern. That fails CLOSED: a header
|
|
16
|
-
* nobody thought about is removed rather than trusted.
|
|
17
|
-
*
|
|
18
|
-
* The allow-list is zero-to-many literals, globs, or `/regex/`es, compiled by
|
|
19
|
-
* shared-core's {@link pattern.toPatternMatcher}, and is UNIONED with
|
|
20
|
-
* {@link DEFAULT_FORWARD_HEADERS} so extending it never silently breaks the
|
|
21
|
-
* built-in surfaces. Configure it with `forwardHeaders` /
|
|
22
|
-
* `TUNNEL_FORWARD_HEADERS`.
|
|
23
|
-
*
|
|
24
|
-
* Non-`x-` headers are untouched. Standard ones (`content-type`, `accept`,
|
|
25
|
-
* `authorization`, `cookie`, ...) are the app's normal input and the gate has no
|
|
26
|
-
* business rewriting them.
|
|
27
|
-
*
|
|
28
|
-
* ## The headers no pattern can forward
|
|
29
|
-
*
|
|
30
|
-
* {@link PROTECTED_HEADERS} is stripped BEFORE the allow-list is consulted, so a
|
|
31
|
-
* permissive pattern (`x-*`, or a careless `*`) cannot re-open impersonation:
|
|
32
|
-
*
|
|
33
|
-
* | Header | What an app does with it | Why spoofing it matters |
|
|
34
|
-
* | -------------------------------- | ------------------------------- | ----------------------- |
|
|
35
|
-
* | `x-forwarded-access-token` | OBO auth (AppKit `asUser`) | Paste any workspace token and every call runs as its owner. The gate's verified email says nothing about who a pasted credential belongs to. |
|
|
36
|
-
* | `x-forwarded-user` | Caller identity | Impersonate another user. The gate sets this itself, from a verified session. |
|
|
37
|
-
* | `x-forwarded-email` | Caller identity | Same. |
|
|
38
|
-
* | `x-forwarded-preferred-username` | Display name from the IdP | Same. |
|
|
39
|
-
* | `x-forwarded-host` | The originally-requested host | Poison absolute URLs the app builds (the classic reset-link attack). |
|
|
40
|
-
* | `x-forwarded-proto` / `-port` | Original scheme / port | Convince the app a plaintext request arrived over TLS. |
|
|
41
|
-
* | `x-forwarded-for` | Client IP | Forge the audit trail, and fan out per-IP rate-limit keys (see below). |
|
|
42
|
-
* | `x-real-ip` | Client IP | Same. |
|
|
43
|
-
* | `x-request-id` | Request correlation UUID | Forge or collide trace ids, making logs unreliable. |
|
|
44
|
-
*
|
|
45
|
-
* The identity four are AppKit's OBO contract; the rest are the `X-Forwarded-*`
|
|
46
|
-
* set the Databricks Apps reverse proxy documents passing to an app
|
|
47
|
-
* ({@link https://docs.databricks.com/aws/en/dev-tools/databricks-apps/http-headers}),
|
|
48
|
-
* plus the conventional `x-forwarded-proto`/`-port`/`x-real-ip` an app or one of
|
|
49
|
-
* its libraries may read even though the table omits them.
|
|
50
|
-
*
|
|
51
|
-
* Stripping the `x-forwarded-*` transport trio is safe because `http-proxy-3` is
|
|
52
|
-
* configured with `xfwd: true` and re-adds them AFTER this policy runs - from the
|
|
53
|
-
* real socket, not from the caller's claim. The app therefore sees the honest
|
|
54
|
-
* (loopback) values instead of whatever the internet asserted. The gate reads the
|
|
55
|
-
* client IP for rate limiting from the raw inbound headers BEFORE stripping, and
|
|
56
|
-
* takes the RIGHTMOST `x-forwarded-for` entry, which is the only one a proxy
|
|
57
|
-
* appended rather than a client supplied.
|
|
58
|
-
*
|
|
59
|
-
* @module
|
|
60
|
-
*/
|
|
61
|
-
import { pattern, token } from "@dbx-tools/shared-core";
|
|
62
|
-
/**
|
|
63
|
-
* Headers the gate strips UNCONDITIONALLY, before the allow-list is consulted -
|
|
64
|
-
* the platform-shaped set documented in this module's table.
|
|
65
|
-
*
|
|
66
|
-
* These answer WHO a request is and WHERE it came from, and on tunnel traffic
|
|
67
|
-
* only the gate may answer that: it injects {@link token.USER_ID_HEADER} /
|
|
68
|
-
* {@link token.USER_EMAIL_HEADER} itself after verifying a session, and the proxy
|
|
69
|
-
* re-derives the transport headers from the real socket.
|
|
70
|
-
*
|
|
71
|
-
* The identity names come from the shared `token` constants, so a renamed wire
|
|
72
|
-
* contract cannot leave a stale spelling here. `x-forwarded-preferred-username`
|
|
73
|
-
* has no constant because nothing in this repo READS it - it is listed precisely
|
|
74
|
-
* so a host app that does read it cannot be fed one.
|
|
75
|
-
*/
|
|
76
|
-
export const PROTECTED_HEADERS = [
|
|
77
|
-
token.ACCESS_TOKEN_HEADER,
|
|
78
|
-
token.USER_ID_HEADER,
|
|
79
|
-
token.USER_EMAIL_HEADER,
|
|
80
|
-
"x-forwarded-preferred-username",
|
|
81
|
-
"x-forwarded-host",
|
|
82
|
-
"x-forwarded-proto",
|
|
83
|
-
"x-forwarded-port",
|
|
84
|
-
"x-forwarded-for",
|
|
85
|
-
"x-real-ip",
|
|
86
|
-
"x-request-id",
|
|
87
|
-
];
|
|
88
|
-
/**
|
|
89
|
-
* The `x-` headers forwarded when a deployment configures nothing.
|
|
90
|
-
*
|
|
91
|
-
* These are the header NAMESPACES this repo's own UI sends and its own server
|
|
92
|
-
* reads - Mastra thread/model routing (`x-mastra-thread-id`, `x-mastra-model`)
|
|
93
|
-
* and MLflow trace correlation (`x-mlflow-trace-id`) - so a dbx-tools app keeps
|
|
94
|
-
* working behind the tunnel with no configuration.
|
|
95
|
-
*
|
|
96
|
-
* Deliberately GLOBS rather than the exact constants from
|
|
97
|
-
* `@dbx-tools/shared-mastra`: importing them would make the gate - a
|
|
98
|
-
* transport-level component that has no other opinion about Mastra - depend on
|
|
99
|
-
* an agent package for three strings, and a namespace glob also covers the next
|
|
100
|
-
* header those packages add. The namespaces are the stable part of the contract.
|
|
101
|
-
*/
|
|
102
|
-
export const DEFAULT_FORWARD_HEADERS = [
|
|
103
|
-
"x-mastra-*",
|
|
104
|
-
"x-mlflow-*",
|
|
105
|
-
// Sent by fetch/XHR wrappers to mark an AJAX request; harmless and widely read.
|
|
106
|
-
"x-requested-with",
|
|
107
|
-
];
|
|
108
|
-
/**
|
|
109
|
-
* Compile the inbound-header policy. `configured` entries are UNIONED with
|
|
110
|
-
* {@link DEFAULT_FORWARD_HEADERS}; each may be a literal name, a glob
|
|
111
|
-
* (`x-myapp-*`), or a `/regex/`. Matching is case-insensitive.
|
|
112
|
-
*/
|
|
113
|
-
export function toHeaderPolicy(configured = []) {
|
|
114
|
-
const patterns = [...DEFAULT_FORWARD_HEADERS, ...configured];
|
|
115
|
-
const allowed = pattern.toPatternMatcher(patterns);
|
|
116
|
-
const protectedNames = new Set(PROTECTED_HEADERS);
|
|
117
|
-
const forwards = (name) => {
|
|
118
|
-
const lower = name.toLowerCase();
|
|
119
|
-
if (protectedNames.has(lower))
|
|
120
|
-
return false;
|
|
121
|
-
if (!lower.startsWith("x-"))
|
|
122
|
-
return true;
|
|
123
|
-
return allowed(lower);
|
|
124
|
-
};
|
|
125
|
-
return {
|
|
126
|
-
patterns,
|
|
127
|
-
forwards,
|
|
128
|
-
apply: (headers) => {
|
|
129
|
-
const removed = [];
|
|
130
|
-
for (const name of Object.keys(headers)) {
|
|
131
|
-
if (forwards(name))
|
|
132
|
-
continue;
|
|
133
|
-
delete headers[name];
|
|
134
|
-
removed.push(name.toLowerCase());
|
|
135
|
-
}
|
|
136
|
-
return removed;
|
|
137
|
-
},
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaGVhZGVycy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9oZWFkZXJzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTJERztBQUVILE9BQU8sRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFrQixNQUFNLHdCQUF3QixDQUFDO0FBRXhFOzs7Ozs7Ozs7Ozs7O0dBYUc7QUFDSCxNQUFNLENBQUMsTUFBTSxpQkFBaUIsR0FBc0I7SUFDbEQsS0FBSyxDQUFDLG1CQUFtQjtJQUN6QixLQUFLLENBQUMsY0FBYztJQUNwQixLQUFLLENBQUMsaUJBQWlCO0lBQ3ZCLGdDQUFnQztJQUNoQyxrQkFBa0I7SUFDbEIsbUJBQW1CO0lBQ25CLGtCQUFrQjtJQUNsQixpQkFBaUI7SUFDakIsV0FBVztJQUNYLGNBQWM7Q0FDZixDQUFDO0FBRUY7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFzQjtJQUN4RCxZQUFZO0lBQ1osWUFBWTtJQUNaLGdGQUFnRjtJQUNoRixrQkFBa0I7Q0FDbkIsQ0FBQztBQWVGOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsY0FBYyxDQUFDLGFBQWdDLEVBQUU7SUFDL0QsTUFBTSxRQUFRLEdBQUcsQ0FBQyxHQUFHLHVCQUF1QixFQUFFLEdBQUcsVUFBVSxDQUFDLENBQUM7SUFDN0QsTUFBTSxPQUFPLEdBQXNCLE9BQU8sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUN0RSxNQUFNLGNBQWMsR0FBRyxJQUFJLEdBQUcsQ0FBQyxpQkFBaUIsQ0FBQyxDQUFDO0lBRWxELE1BQU0sUUFBUSxHQUFHLENBQUMsSUFBWSxFQUFXLEVBQUU7UUFDekMsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1FBQ2pDLElBQUksY0FBYyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUM1QyxJQUFJLENBQUMsS0FBSyxDQUFDLFVBQVUsQ0FBQyxJQUFJLENBQUM7WUFBRSxPQUFPLElBQUksQ0FBQztRQUN6QyxPQUFPLE9BQU8sQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN4QixDQUFDLENBQUM7SUFFRixPQUFPO1FBQ0wsUUFBUTtRQUNSLFFBQVE7UUFDUixLQUFLLEVBQUUsQ0FBQyxPQUFPLEVBQUUsRUFBRTtZQUNqQixNQUFNLE9BQU8sR0FBYSxFQUFFLENBQUM7WUFDN0IsS0FBSyxNQUFNLElBQUksSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ3hDLElBQUksUUFBUSxDQUFDLElBQUksQ0FBQztvQkFBRSxTQUFTO2dCQUM3QixPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztnQkFDckIsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUMsQ0FBQztZQUNuQyxDQUFDO1lBQ0QsT0FBTyxPQUFPLENBQUM7UUFDakIsQ0FBQztLQUNGLENBQUM7QUFDSixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBJbmJvdW5kIGhlYWRlciBwb2xpY3kgZm9yIHR1bm5lbCB0cmFmZmljLlxuICpcbiAqIEV2ZXJ5dGhpbmcgdGhlIGdhdGUgZm9yd2FyZHMgYXJyaXZlcyBmcm9tIHRoZSBQVUJMSUMgaW50ZXJuZXQgdGhyb3VnaCB0aGVcbiAqIHBvcnRyIGNsaWVudCwgc28gZXZlcnkgaGVhZGVyIG9uIGl0IGlzIGF0dGFja2VyLWNvbnRyb2xsZWQuIFRoZSBoZWFkZXJzIGFuIGFwcFxuICogdHJ1c3RzIGFyZSBwcmVjaXNlbHkgdGhlIG9uZXMgYSBjYWxsZXIgbXVzdCBub3QgYmUgYWJsZSB0byB3cml0ZSwgYmVjYXVzZSB0aGVcbiAqIGFwcCBjYW5ub3QgdGVsbCBhIGhlYWRlciB0aGUgRGF0YWJyaWNrcyBmcm9udCBkb29yIHNldCBmcm9tIG9uZSBhIGJyb3dzZXJcbiAqIHR5cGVkLlxuICpcbiAqICMjIFBvbGljeSBzaGFwZTogc3RyaXAgYnkgZGVmYXVsdCwgYWxsb3cgYnkgcGF0dGVyblxuICpcbiAqIEVudW1lcmF0aW5nIHdoYXQgdG8gcmVtb3ZlIGlzIGEgbG9zaW5nIGdhbWUgLSBhIGRlbnktbGlzdCBpcyBvbmx5IGNvcnJlY3QgdW50aWxcbiAqIHRoZSBwbGF0Zm9ybSBhZGRzIGEgaGVhZGVyIG9yIGEgbGlicmFyeSBzdGFydHMgdHJ1c3RpbmcgYW5vdGhlciBvbmUgLSBzbyB0aGVcbiAqIHBvbGljeSBpcyBpbnZlcnRlZC4gRVZFUlkgYHgtYC1wcmVmaXhlZCByZXF1ZXN0IGhlYWRlciBpcyBkcm9wcGVkIGZyb20gdHVubmVsXG4gKiB0cmFmZmljIHVubGVzcyBpdCBtYXRjaGVzIGEgY29uZmlndXJlZCBwYXR0ZXJuLiBUaGF0IGZhaWxzIENMT1NFRDogYSBoZWFkZXJcbiAqIG5vYm9keSB0aG91Z2h0IGFib3V0IGlzIHJlbW92ZWQgcmF0aGVyIHRoYW4gdHJ1c3RlZC5cbiAqXG4gKiBUaGUgYWxsb3ctbGlzdCBpcyB6ZXJvLXRvLW1hbnkgbGl0ZXJhbHMsIGdsb2JzLCBvciBgL3JlZ2V4L2BlcywgY29tcGlsZWQgYnlcbiAqIHNoYXJlZC1jb3JlJ3Mge0BsaW5rIHBhdHRlcm4udG9QYXR0ZXJuTWF0Y2hlcn0sIGFuZCBpcyBVTklPTkVEIHdpdGhcbiAqIHtAbGluayBERUZBVUxUX0ZPUldBUkRfSEVBREVSU30gc28gZXh0ZW5kaW5nIGl0IG5ldmVyIHNpbGVudGx5IGJyZWFrcyB0aGVcbiAqIGJ1aWx0LWluIHN1cmZhY2VzLiBDb25maWd1cmUgaXQgd2l0aCBgZm9yd2FyZEhlYWRlcnNgIC9cbiAqIGBUVU5ORUxfRk9SV0FSRF9IRUFERVJTYC5cbiAqXG4gKiBOb24tYHgtYCBoZWFkZXJzIGFyZSB1bnRvdWNoZWQuIFN0YW5kYXJkIG9uZXMgKGBjb250ZW50LXR5cGVgLCBgYWNjZXB0YCxcbiAqIGBhdXRob3JpemF0aW9uYCwgYGNvb2tpZWAsIC4uLikgYXJlIHRoZSBhcHAncyBub3JtYWwgaW5wdXQgYW5kIHRoZSBnYXRlIGhhcyBub1xuICogYnVzaW5lc3MgcmV3cml0aW5nIHRoZW0uXG4gKlxuICogIyMgVGhlIGhlYWRlcnMgbm8gcGF0dGVybiBjYW4gZm9yd2FyZFxuICpcbiAqIHtAbGluayBQUk9URUNURURfSEVBREVSU30gaXMgc3RyaXBwZWQgQkVGT1JFIHRoZSBhbGxvdy1saXN0IGlzIGNvbnN1bHRlZCwgc28gYVxuICogcGVybWlzc2l2ZSBwYXR0ZXJuIChgeC0qYCwgb3IgYSBjYXJlbGVzcyBgKmApIGNhbm5vdCByZS1vcGVuIGltcGVyc29uYXRpb246XG4gKlxuICogfCBIZWFkZXIgICAgICAgICAgICAgICAgICAgICAgICAgICB8IFdoYXQgYW4gYXBwIGRvZXMgd2l0aCBpdCAgICAgICAgfCBXaHkgc3Bvb2ZpbmcgaXQgbWF0dGVycyB8XG4gKiB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHwgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSB8IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIHxcbiAqIHwgYHgtZm9yd2FyZGVkLWFjY2Vzcy10b2tlbmAgICAgICAgfCBPQk8gYXV0aCAoQXBwS2l0IGBhc1VzZXJgKSAgICAgIHwgUGFzdGUgYW55IHdvcmtzcGFjZSB0b2tlbiBhbmQgZXZlcnkgY2FsbCBydW5zIGFzIGl0cyBvd25lci4gVGhlIGdhdGUncyB2ZXJpZmllZCBlbWFpbCBzYXlzIG5vdGhpbmcgYWJvdXQgd2hvIGEgcGFzdGVkIGNyZWRlbnRpYWwgYmVsb25ncyB0by4gfFxuICogfCBgeC1mb3J3YXJkZWQtdXNlcmAgICAgICAgICAgICAgICB8IENhbGxlciBpZGVudGl0eSAgICAgICAgICAgICAgICAgfCBJbXBlcnNvbmF0ZSBhbm90aGVyIHVzZXIuIFRoZSBnYXRlIHNldHMgdGhpcyBpdHNlbGYsIGZyb20gYSB2ZXJpZmllZCBzZXNzaW9uLiB8XG4gKiB8IGB4LWZvcndhcmRlZC1lbWFpbGAgICAgICAgICAgICAgIHwgQ2FsbGVyIGlkZW50aXR5ICAgICAgICAgICAgICAgICB8IFNhbWUuIHxcbiAqIHwgYHgtZm9yd2FyZGVkLXByZWZlcnJlZC11c2VybmFtZWAgfCBEaXNwbGF5IG5hbWUgZnJvbSB0aGUgSWRQICAgICAgIHwgU2FtZS4gfFxuICogfCBgeC1mb3J3YXJkZWQtaG9zdGAgICAgICAgICAgICAgICB8IFRoZSBvcmlnaW5hbGx5LXJlcXVlc3RlZCBob3N0ICAgIHwgUG9pc29uIGFic29sdXRlIFVSTHMgdGhlIGFwcCBidWlsZHMgKHRoZSBjbGFzc2ljIHJlc2V0LWxpbmsgYXR0YWNrKS4gfFxuICogfCBgeC1mb3J3YXJkZWQtcHJvdG9gIC8gYC1wb3J0YCAgICB8IE9yaWdpbmFsIHNjaGVtZSAvIHBvcnQgICAgICAgICAgfCBDb252aW5jZSB0aGUgYXBwIGEgcGxhaW50ZXh0IHJlcXVlc3QgYXJyaXZlZCBvdmVyIFRMUy4gfFxuICogfCBgeC1mb3J3YXJkZWQtZm9yYCAgICAgICAgICAgICAgICB8IENsaWVudCBJUCAgICAgICAgICAgICAgICAgICAgICAgfCBGb3JnZSB0aGUgYXVkaXQgdHJhaWwsIGFuZCBmYW4gb3V0IHBlci1JUCByYXRlLWxpbWl0IGtleXMgKHNlZSBiZWxvdykuIHxcbiAqIHwgYHgtcmVhbC1pcGAgICAgICAgICAgICAgICAgICAgICAgfCBDbGllbnQgSVAgICAgICAgICAgICAgICAgICAgICAgIHwgU2FtZS4gfFxuICogfCBgeC1yZXF1ZXN0LWlkYCAgICAgICAgICAgICAgICAgICB8IFJlcXVlc3QgY29ycmVsYXRpb24gVVVJRCAgICAgICAgfCBGb3JnZSBvciBjb2xsaWRlIHRyYWNlIGlkcywgbWFraW5nIGxvZ3MgdW5yZWxpYWJsZS4gfFxuICpcbiAqIFRoZSBpZGVudGl0eSBmb3VyIGFyZSBBcHBLaXQncyBPQk8gY29udHJhY3Q7IHRoZSByZXN0IGFyZSB0aGUgYFgtRm9yd2FyZGVkLSpgXG4gKiBzZXQgdGhlIERhdGFicmlja3MgQXBwcyByZXZlcnNlIHByb3h5IGRvY3VtZW50cyBwYXNzaW5nIHRvIGFuIGFwcFxuICogKHtAbGluayBodHRwczovL2RvY3MuZGF0YWJyaWNrcy5jb20vYXdzL2VuL2Rldi10b29scy9kYXRhYnJpY2tzLWFwcHMvaHR0cC1oZWFkZXJzfSksXG4gKiBwbHVzIHRoZSBjb252ZW50aW9uYWwgYHgtZm9yd2FyZGVkLXByb3RvYC9gLXBvcnRgL2B4LXJlYWwtaXBgIGFuIGFwcCBvciBvbmUgb2ZcbiAqIGl0cyBsaWJyYXJpZXMgbWF5IHJlYWQgZXZlbiB0aG91Z2ggdGhlIHRhYmxlIG9taXRzIHRoZW0uXG4gKlxuICogU3RyaXBwaW5nIHRoZSBgeC1mb3J3YXJkZWQtKmAgdHJhbnNwb3J0IHRyaW8gaXMgc2FmZSBiZWNhdXNlIGBodHRwLXByb3h5LTNgIGlzXG4gKiBjb25maWd1cmVkIHdpdGggYHhmd2Q6IHRydWVgIGFuZCByZS1hZGRzIHRoZW0gQUZURVIgdGhpcyBwb2xpY3kgcnVucyAtIGZyb20gdGhlXG4gKiByZWFsIHNvY2tldCwgbm90IGZyb20gdGhlIGNhbGxlcidzIGNsYWltLiBUaGUgYXBwIHRoZXJlZm9yZSBzZWVzIHRoZSBob25lc3RcbiAqIChsb29wYmFjaykgdmFsdWVzIGluc3RlYWQgb2Ygd2hhdGV2ZXIgdGhlIGludGVybmV0IGFzc2VydGVkLiBUaGUgZ2F0ZSByZWFkcyB0aGVcbiAqIGNsaWVudCBJUCBmb3IgcmF0ZSBsaW1pdGluZyBmcm9tIHRoZSByYXcgaW5ib3VuZCBoZWFkZXJzIEJFRk9SRSBzdHJpcHBpbmcsIGFuZFxuICogdGFrZXMgdGhlIFJJR0hUTU9TVCBgeC1mb3J3YXJkZWQtZm9yYCBlbnRyeSwgd2hpY2ggaXMgdGhlIG9ubHkgb25lIGEgcHJveHlcbiAqIGFwcGVuZGVkIHJhdGhlciB0aGFuIGEgY2xpZW50IHN1cHBsaWVkLlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBwYXR0ZXJuLCB0b2tlbiwgdHlwZSBQcmVkaWNhdGUgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuXG4vKipcbiAqIEhlYWRlcnMgdGhlIGdhdGUgc3RyaXBzIFVOQ09ORElUSU9OQUxMWSwgYmVmb3JlIHRoZSBhbGxvdy1saXN0IGlzIGNvbnN1bHRlZCAtXG4gKiB0aGUgcGxhdGZvcm0tc2hhcGVkIHNldCBkb2N1bWVudGVkIGluIHRoaXMgbW9kdWxlJ3MgdGFibGUuXG4gKlxuICogVGhlc2UgYW5zd2VyIFdITyBhIHJlcXVlc3QgaXMgYW5kIFdIRVJFIGl0IGNhbWUgZnJvbSwgYW5kIG9uIHR1bm5lbCB0cmFmZmljXG4gKiBvbmx5IHRoZSBnYXRlIG1heSBhbnN3ZXIgdGhhdDogaXQgaW5qZWN0cyB7QGxpbmsgdG9rZW4uVVNFUl9JRF9IRUFERVJ9IC9cbiAqIHtAbGluayB0b2tlbi5VU0VSX0VNQUlMX0hFQURFUn0gaXRzZWxmIGFmdGVyIHZlcmlmeWluZyBhIHNlc3Npb24sIGFuZCB0aGUgcHJveHlcbiAqIHJlLWRlcml2ZXMgdGhlIHRyYW5zcG9ydCBoZWFkZXJzIGZyb20gdGhlIHJlYWwgc29ja2V0LlxuICpcbiAqIFRoZSBpZGVudGl0eSBuYW1lcyBjb21lIGZyb20gdGhlIHNoYXJlZCBgdG9rZW5gIGNvbnN0YW50cywgc28gYSByZW5hbWVkIHdpcmVcbiAqIGNvbnRyYWN0IGNhbm5vdCBsZWF2ZSBhIHN0YWxlIHNwZWxsaW5nIGhlcmUuIGB4LWZvcndhcmRlZC1wcmVmZXJyZWQtdXNlcm5hbWVgXG4gKiBoYXMgbm8gY29uc3RhbnQgYmVjYXVzZSBub3RoaW5nIGluIHRoaXMgcmVwbyBSRUFEUyBpdCAtIGl0IGlzIGxpc3RlZCBwcmVjaXNlbHlcbiAqIHNvIGEgaG9zdCBhcHAgdGhhdCBkb2VzIHJlYWQgaXQgY2Fubm90IGJlIGZlZCBvbmUuXG4gKi9cbmV4cG9ydCBjb25zdCBQUk9URUNURURfSEVBREVSUzogcmVhZG9ubHkgc3RyaW5nW10gPSBbXG4gIHRva2VuLkFDQ0VTU19UT0tFTl9IRUFERVIsXG4gIHRva2VuLlVTRVJfSURfSEVBREVSLFxuICB0b2tlbi5VU0VSX0VNQUlMX0hFQURFUixcbiAgXCJ4LWZvcndhcmRlZC1wcmVmZXJyZWQtdXNlcm5hbWVcIixcbiAgXCJ4LWZvcndhcmRlZC1ob3N0XCIsXG4gIFwieC1mb3J3YXJkZWQtcHJvdG9cIixcbiAgXCJ4LWZvcndhcmRlZC1wb3J0XCIsXG4gIFwieC1mb3J3YXJkZWQtZm9yXCIsXG4gIFwieC1yZWFsLWlwXCIsXG4gIFwieC1yZXF1ZXN0LWlkXCIsXG5dO1xuXG4vKipcbiAqIFRoZSBgeC1gIGhlYWRlcnMgZm9yd2FyZGVkIHdoZW4gYSBkZXBsb3ltZW50IGNvbmZpZ3VyZXMgbm90aGluZy5cbiAqXG4gKiBUaGVzZSBhcmUgdGhlIGhlYWRlciBOQU1FU1BBQ0VTIHRoaXMgcmVwbydzIG93biBVSSBzZW5kcyBhbmQgaXRzIG93biBzZXJ2ZXJcbiAqIHJlYWRzIC0gTWFzdHJhIHRocmVhZC9tb2RlbCByb3V0aW5nIChgeC1tYXN0cmEtdGhyZWFkLWlkYCwgYHgtbWFzdHJhLW1vZGVsYClcbiAqIGFuZCBNTGZsb3cgdHJhY2UgY29ycmVsYXRpb24gKGB4LW1sZmxvdy10cmFjZS1pZGApIC0gc28gYSBkYngtdG9vbHMgYXBwIGtlZXBzXG4gKiB3b3JraW5nIGJlaGluZCB0aGUgdHVubmVsIHdpdGggbm8gY29uZmlndXJhdGlvbi5cbiAqXG4gKiBEZWxpYmVyYXRlbHkgR0xPQlMgcmF0aGVyIHRoYW4gdGhlIGV4YWN0IGNvbnN0YW50cyBmcm9tXG4gKiBgQGRieC10b29scy9zaGFyZWQtbWFzdHJhYDogaW1wb3J0aW5nIHRoZW0gd291bGQgbWFrZSB0aGUgZ2F0ZSAtIGFcbiAqIHRyYW5zcG9ydC1sZXZlbCBjb21wb25lbnQgdGhhdCBoYXMgbm8gb3RoZXIgb3BpbmlvbiBhYm91dCBNYXN0cmEgLSBkZXBlbmQgb25cbiAqIGFuIGFnZW50IHBhY2thZ2UgZm9yIHRocmVlIHN0cmluZ3MsIGFuZCBhIG5hbWVzcGFjZSBnbG9iIGFsc28gY292ZXJzIHRoZSBuZXh0XG4gKiBoZWFkZXIgdGhvc2UgcGFja2FnZXMgYWRkLiBUaGUgbmFtZXNwYWNlcyBhcmUgdGhlIHN0YWJsZSBwYXJ0IG9mIHRoZSBjb250cmFjdC5cbiAqL1xuZXhwb3J0IGNvbnN0IERFRkFVTFRfRk9SV0FSRF9IRUFERVJTOiByZWFkb25seSBzdHJpbmdbXSA9IFtcbiAgXCJ4LW1hc3RyYS0qXCIsXG4gIFwieC1tbGZsb3ctKlwiLFxuICAvLyBTZW50IGJ5IGZldGNoL1hIUiB3cmFwcGVycyB0byBtYXJrIGFuIEFKQVggcmVxdWVzdDsgaGFybWxlc3MgYW5kIHdpZGVseSByZWFkLlxuICBcIngtcmVxdWVzdGVkLXdpdGhcIixcbl07XG5cbi8qKiBBIGNvbXBpbGVkIGluYm91bmQtaGVhZGVyIHBvbGljeS4gQnVpbGQgb25lIHdpdGgge0BsaW5rIHRvSGVhZGVyUG9saWN5fS4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgSGVhZGVyUG9saWN5IHtcbiAgLyoqIFRoZSBhbGxvdy1saXN0IGVudHJpZXMgYmFja2luZyB0aGlzIHBvbGljeSAoZm9yIGRpYWdub3N0aWNzIGFuZCB0ZXN0cykuICovXG4gIHJlYWRvbmx5IHBhdHRlcm5zOiByZWFkb25seSBzdHJpbmdbXTtcbiAgLyoqIFdoZXRoZXIgYG5hbWVgIHN1cnZpdmVzIG9uIHR1bm5lbCB0cmFmZmljLiAqL1xuICBmb3J3YXJkcyhuYW1lOiBzdHJpbmcpOiBib29sZWFuO1xuICAvKipcbiAgICogRGVsZXRlIGV2ZXJ5IGRpc2FsbG93ZWQgaGVhZGVyIGZyb20gYSBtdXRhYmxlIE5vZGUgaGVhZGVyIGJhZywgcmV0dXJuaW5nIHRoZVxuICAgKiBsb3dlci1jYXNlZCBuYW1lcyByZW1vdmVkIChmb3IgZGVidWcgbG9nZ2luZykuXG4gICAqL1xuICBhcHBseShoZWFkZXJzOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPik6IHN0cmluZ1tdO1xufVxuXG4vKipcbiAqIENvbXBpbGUgdGhlIGluYm91bmQtaGVhZGVyIHBvbGljeS4gYGNvbmZpZ3VyZWRgIGVudHJpZXMgYXJlIFVOSU9ORUQgd2l0aFxuICoge0BsaW5rIERFRkFVTFRfRk9SV0FSRF9IRUFERVJTfTsgZWFjaCBtYXkgYmUgYSBsaXRlcmFsIG5hbWUsIGEgZ2xvYlxuICogKGB4LW15YXBwLSpgKSwgb3IgYSBgL3JlZ2V4L2AuIE1hdGNoaW5nIGlzIGNhc2UtaW5zZW5zaXRpdmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB0b0hlYWRlclBvbGljeShjb25maWd1cmVkOiByZWFkb25seSBzdHJpbmdbXSA9IFtdKTogSGVhZGVyUG9saWN5IHtcbiAgY29uc3QgcGF0dGVybnMgPSBbLi4uREVGQVVMVF9GT1JXQVJEX0hFQURFUlMsIC4uLmNvbmZpZ3VyZWRdO1xuICBjb25zdCBhbGxvd2VkOiBQcmVkaWNhdGU8c3RyaW5nPiA9IHBhdHRlcm4udG9QYXR0ZXJuTWF0Y2hlcihwYXR0ZXJucyk7XG4gIGNvbnN0IHByb3RlY3RlZE5hbWVzID0gbmV3IFNldChQUk9URUNURURfSEVBREVSUyk7XG5cbiAgY29uc3QgZm9yd2FyZHMgPSAobmFtZTogc3RyaW5nKTogYm9vbGVhbiA9PiB7XG4gICAgY29uc3QgbG93ZXIgPSBuYW1lLnRvTG93ZXJDYXNlKCk7XG4gICAgaWYgKHByb3RlY3RlZE5hbWVzLmhhcyhsb3dlcikpIHJldHVybiBmYWxzZTtcbiAgICBpZiAoIWxvd2VyLnN0YXJ0c1dpdGgoXCJ4LVwiKSkgcmV0dXJuIHRydWU7XG4gICAgcmV0dXJuIGFsbG93ZWQobG93ZXIpO1xuICB9O1xuXG4gIHJldHVybiB7XG4gICAgcGF0dGVybnMsXG4gICAgZm9yd2FyZHMsXG4gICAgYXBwbHk6IChoZWFkZXJzKSA9PiB7XG4gICAgICBjb25zdCByZW1vdmVkOiBzdHJpbmdbXSA9IFtdO1xuICAgICAgZm9yIChjb25zdCBuYW1lIG9mIE9iamVjdC5rZXlzKGhlYWRlcnMpKSB7XG4gICAgICAgIGlmIChmb3J3YXJkcyhuYW1lKSkgY29udGludWU7XG4gICAgICAgIGRlbGV0ZSBoZWFkZXJzW25hbWVdO1xuICAgICAgICByZW1vdmVkLnB1c2gobmFtZS50b0xvd2VyQ2FzZSgpKTtcbiAgICAgIH1cbiAgICAgIHJldHVybiByZW1vdmVkO1xuICAgIH0sXG4gIH07XG59XG4iXX0=
|
package/lib/src/otp.d.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One-time-code store + session JWT for the email-OTP tunnel gate.
|
|
3
|
-
*
|
|
4
|
-
* The code store is backed by AppKit's `CacheManager` (auto-configured to memory,
|
|
5
|
-
* or Lakebase when the app wires a persistent `CacheStorage`), so TTL EXPIRY and
|
|
6
|
-
* eviction are the cache's job - no hand-rolled Map or timers. A 6-digit code is
|
|
7
|
-
* generated with `crypto.randomInt` and stored as a SHA-256 hash with an attempt
|
|
8
|
-
* counter (never the plaintext, never in the JWT); `verify` is constant-time on
|
|
9
|
-
* the hash, and the entry is deleted on success or once attempts are exhausted.
|
|
10
|
-
*
|
|
11
|
-
* The session JWT is an HS256 token (via `jose`) carrying only the email. Its
|
|
12
|
-
* signing key is resolved by `./signing-key.ts`: `TUNNEL_AUTH_JWT_SECRET` when
|
|
13
|
-
* set, else a key persisted in the cache for 30 days so cookies survive the
|
|
14
|
-
* restarts a tunnel sees whenever the app it wraps reloads. A cookie signed
|
|
15
|
-
* before `TUNNEL_AUTH_SESSION_CUTOFF` is refused here as well as being orphaned
|
|
16
|
-
* by the cache key, so the force-clear switch holds even for a still-current key.
|
|
17
|
-
*
|
|
18
|
-
* @module
|
|
19
|
-
*/
|
|
20
|
-
/** Result of {@link CodeStore.verify}. */
|
|
21
|
-
export type VerifyOutcome = "ok" | "invalid" | "expired" | "too-many-attempts";
|
|
22
|
-
/**
|
|
23
|
-
* Pending one-time codes, stored in AppKit's cache keyed by lowercased email.
|
|
24
|
-
* Expiry is the cache's TTL (no manual clock); a miss means expired-or-never.
|
|
25
|
-
*/
|
|
26
|
-
export declare class CodeStore {
|
|
27
|
-
private readonly ttlSeconds;
|
|
28
|
-
private readonly maxAttempts;
|
|
29
|
-
constructor(ttlSeconds: number, maxAttempts: number);
|
|
30
|
-
private cache;
|
|
31
|
-
private key;
|
|
32
|
-
/**
|
|
33
|
-
* Generate, store (hashed, with the cache TTL), and RETURN a fresh 6-digit
|
|
34
|
-
* code. The caller emails the returned plaintext; only the hash is retained.
|
|
35
|
-
* Replaces any pending code for the address.
|
|
36
|
-
*/
|
|
37
|
-
issue(email: string): Promise<string>;
|
|
38
|
-
/**
|
|
39
|
-
* Check `code` for `email`. A cache miss is `expired` (TTL elapsed or never
|
|
40
|
-
* issued). Deletes the entry on success or when attempts are exhausted, so a
|
|
41
|
-
* code is single-use and can't be brute-forced past the cap. An attempt
|
|
42
|
-
* increments the stored counter (re-persisted with a fresh TTL window).
|
|
43
|
-
*/
|
|
44
|
-
verify(email: string, code: string): Promise<VerifyOutcome>;
|
|
45
|
-
}
|
|
46
|
-
/** Mint a session JWT for `email`, expiring in `ttlSeconds`. */
|
|
47
|
-
export declare function signSession(email: string, ttlSeconds: number): Promise<string>;
|
|
48
|
-
/** Validate a session JWT, returning the email it was minted for, or `undefined`. */
|
|
49
|
-
export declare function verifySession(token: string | undefined): Promise<string | undefined>;
|
package/lib/src/otp.js
DELETED
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One-time-code store + session JWT for the email-OTP tunnel gate.
|
|
3
|
-
*
|
|
4
|
-
* The code store is backed by AppKit's `CacheManager` (auto-configured to memory,
|
|
5
|
-
* or Lakebase when the app wires a persistent `CacheStorage`), so TTL EXPIRY and
|
|
6
|
-
* eviction are the cache's job - no hand-rolled Map or timers. A 6-digit code is
|
|
7
|
-
* generated with `crypto.randomInt` and stored as a SHA-256 hash with an attempt
|
|
8
|
-
* counter (never the plaintext, never in the JWT); `verify` is constant-time on
|
|
9
|
-
* the hash, and the entry is deleted on success or once attempts are exhausted.
|
|
10
|
-
*
|
|
11
|
-
* The session JWT is an HS256 token (via `jose`) carrying only the email. Its
|
|
12
|
-
* signing key is resolved by `./signing-key.ts`: `TUNNEL_AUTH_JWT_SECRET` when
|
|
13
|
-
* set, else a key persisted in the cache for 30 days so cookies survive the
|
|
14
|
-
* restarts a tunnel sees whenever the app it wraps reloads. A cookie signed
|
|
15
|
-
* before `TUNNEL_AUTH_SESSION_CUTOFF` is refused here as well as being orphaned
|
|
16
|
-
* by the cache key, so the force-clear switch holds even for a still-current key.
|
|
17
|
-
*
|
|
18
|
-
* @module
|
|
19
|
-
*/
|
|
20
|
-
import { createHash, randomInt, timingSafeEqual } from "node:crypto";
|
|
21
|
-
import { CacheManager } from "@databricks/appkit";
|
|
22
|
-
import { jwtVerify, SignJWT } from "jose";
|
|
23
|
-
import { signingKey } from "./signing-key.js";
|
|
24
|
-
/** JWT issuer/audience so a token minted for this gate isn't accepted elsewhere. */
|
|
25
|
-
const JWT_AUD = "dbx-tools-tunnel-auth";
|
|
26
|
-
/** Cache-key prefix for pending codes, namespaced away from any other cache use. */
|
|
27
|
-
const CODE_PREFIX = "tunnel:otp:";
|
|
28
|
-
/** SHA-256 hex of a value. */
|
|
29
|
-
function sha256(value) {
|
|
30
|
-
return createHash("sha256").update(value).digest("hex");
|
|
31
|
-
}
|
|
32
|
-
/** Constant-time compare of two equal-length hex digests. */
|
|
33
|
-
function safeEqualHex(a, b) {
|
|
34
|
-
if (a.length !== b.length)
|
|
35
|
-
return false;
|
|
36
|
-
return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Pending one-time codes, stored in AppKit's cache keyed by lowercased email.
|
|
40
|
-
* Expiry is the cache's TTL (no manual clock); a miss means expired-or-never.
|
|
41
|
-
*/
|
|
42
|
-
export class CodeStore {
|
|
43
|
-
ttlSeconds;
|
|
44
|
-
maxAttempts;
|
|
45
|
-
constructor(ttlSeconds, maxAttempts) {
|
|
46
|
-
this.ttlSeconds = ttlSeconds;
|
|
47
|
-
this.maxAttempts = maxAttempts;
|
|
48
|
-
}
|
|
49
|
-
cache() {
|
|
50
|
-
return CacheManager.getInstanceSync();
|
|
51
|
-
}
|
|
52
|
-
key(email) {
|
|
53
|
-
return `${CODE_PREFIX}${email.toLowerCase()}`;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Generate, store (hashed, with the cache TTL), and RETURN a fresh 6-digit
|
|
57
|
-
* code. The caller emails the returned plaintext; only the hash is retained.
|
|
58
|
-
* Replaces any pending code for the address.
|
|
59
|
-
*/
|
|
60
|
-
async issue(email) {
|
|
61
|
-
const code = String(randomInt(0, 1_000_000)).padStart(6, "0");
|
|
62
|
-
const entry = { hash: sha256(code), attempts: 0 };
|
|
63
|
-
await this.cache().set(this.key(email), entry, { ttl: this.ttlSeconds });
|
|
64
|
-
return code;
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Check `code` for `email`. A cache miss is `expired` (TTL elapsed or never
|
|
68
|
-
* issued). Deletes the entry on success or when attempts are exhausted, so a
|
|
69
|
-
* code is single-use and can't be brute-forced past the cap. An attempt
|
|
70
|
-
* increments the stored counter (re-persisted with a fresh TTL window).
|
|
71
|
-
*/
|
|
72
|
-
async verify(email, code) {
|
|
73
|
-
const key = this.key(email);
|
|
74
|
-
const entry = await this.cache().get(key);
|
|
75
|
-
if (!entry)
|
|
76
|
-
return "expired";
|
|
77
|
-
const attempts = entry.attempts + 1;
|
|
78
|
-
if (safeEqualHex(entry.hash, sha256(code))) {
|
|
79
|
-
await this.cache().delete(key);
|
|
80
|
-
return "ok";
|
|
81
|
-
}
|
|
82
|
-
if (attempts >= this.maxAttempts) {
|
|
83
|
-
await this.cache().delete(key);
|
|
84
|
-
return "too-many-attempts";
|
|
85
|
-
}
|
|
86
|
-
await this.cache().set(key, { ...entry, attempts }, { ttl: this.ttlSeconds });
|
|
87
|
-
return "invalid";
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
/** Mint a session JWT for `email`, expiring in `ttlSeconds`. */
|
|
91
|
-
export async function signSession(email, ttlSeconds) {
|
|
92
|
-
const { key } = await signingKey();
|
|
93
|
-
return new SignJWT({ email })
|
|
94
|
-
.setProtectedHeader({ alg: "HS256" })
|
|
95
|
-
.setSubject(email)
|
|
96
|
-
.setAudience(JWT_AUD)
|
|
97
|
-
.setIssuedAt()
|
|
98
|
-
.setExpirationTime(`${ttlSeconds}s`)
|
|
99
|
-
.sign(key);
|
|
100
|
-
}
|
|
101
|
-
/** Validate a session JWT, returning the email it was minted for, or `undefined`. */
|
|
102
|
-
export async function verifySession(token) {
|
|
103
|
-
if (!token)
|
|
104
|
-
return undefined;
|
|
105
|
-
try {
|
|
106
|
-
const { key, cutoffMs } = await signingKey();
|
|
107
|
-
const { payload } = await jwtVerify(token, key, { audience: JWT_AUD });
|
|
108
|
-
// Belt and braces with the cutoff-scoped cache key: that alone already
|
|
109
|
-
// orphans older keys, but an operator who moved the cutoff while
|
|
110
|
-
// TUNNEL_AUTH_JWT_SECRET is set has no key rotation to rely on, and this
|
|
111
|
-
// check is what makes the switch work in that case too.
|
|
112
|
-
// Compared in whole SECONDS because `iat` has no finer resolution: against a
|
|
113
|
-
// millisecond cutoff, a cookie minted in the same second as it would be
|
|
114
|
-
// refused depending on sub-second rounding.
|
|
115
|
-
if (cutoffMs > 0 && (payload.iat === undefined || payload.iat < Math.floor(cutoffMs / 1000))) {
|
|
116
|
-
return undefined;
|
|
117
|
-
}
|
|
118
|
-
return typeof payload.email === "string" ? payload.email : undefined;
|
|
119
|
-
}
|
|
120
|
-
catch {
|
|
121
|
-
return undefined;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3RwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL290cC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsZUFBZSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUMxQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFOUMsb0ZBQW9GO0FBQ3BGLE1BQU0sT0FBTyxHQUFHLHVCQUF1QixDQUFDO0FBRXhDLG9GQUFvRjtBQUNwRixNQUFNLFdBQVcsR0FBRyxhQUFhLENBQUM7QUFFbEMsOEJBQThCO0FBQzlCLFNBQVMsTUFBTSxDQUFDLEtBQWE7SUFDM0IsT0FBTyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUMxRCxDQUFDO0FBRUQsNkRBQTZEO0FBQzdELFNBQVMsWUFBWSxDQUFDLENBQVMsRUFBRSxDQUFTO0lBQ3hDLElBQUksQ0FBQyxDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsTUFBTTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3hDLE9BQU8sZUFBZSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQVVEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxTQUFTO0lBRUQ7SUFDQTtJQUZuQixZQUNtQixVQUFrQixFQUNsQixXQUFtQjtRQURuQixlQUFVLEdBQVYsVUFBVSxDQUFRO1FBQ2xCLGdCQUFX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LE1BQU0sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxVQUFVLEVBQUUsQ0FBQztRQUM3QyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxTQUFTLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZFLHVFQUF1RTtRQUN2RSxpRUFBaUU7UUFDakUseUVBQXlFO1FBQ3pFLHdEQUF3RDtRQUN4RCw2RUFBNkU7UUFDN0Usd0VBQXdFO1FBQ3hFLDRDQUE0QztRQUM1QyxJQUFJLFFBQVEsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLENBQUMsR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUM3RixPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0QsT0FBTyxPQUFPLE9BQU8sQ0FBQyxLQUFLLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDdkUsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBPbmUtdGltZS1jb2RlIHN0b3JlICsgc2Vzc2lvbiBKV1QgZm9yIHRoZSBlbWFpbC1PVFAgdHVubmVsIGdhdGUuXG4gKlxuICogVGhlIGNvZGUgc3RvcmUgaXMgYmFja2VkIGJ5IEFwcEtpdCdzIGBDYWNoZU1hbmFnZXJgIChhdXRvLWNvbmZpZ3VyZWQgdG8gbWVtb3J5LFxuICogb3IgTGFrZWJhc2Ugd2hlbiB0aGUgYXBwIHdpcmVzIGEgcGVyc2lzdGVudCBgQ2FjaGVTdG9yYWdlYCksIHNvIFRUTCBFWFBJUlkgYW5kXG4gKiBldmljdGlvbiBhcmUgdGhlIGNhY2hlJ3Mgam9iIC0gbm8gaGFuZC1yb2xsZWQgTWFwIG9yIHRpbWVycy4gQSA2LWRpZ2l0IGNvZGUgaXNcbiAqIGdlbmVyYXRlZCB3aXRoIGBjcnlwdG8ucmFuZG9tSW50YCBhbmQgc3RvcmVkIGFzIGEgU0hBLTI1NiBoYXNoIHdpdGggYW4gYXR0ZW1wdFxuICogY291bnRlciAobmV2ZXIgdGhlIHBsYWludGV4dCwgbmV2ZXIgaW4gdGhlIEpXVCk7IGB2ZXJpZnlgIGlzIGNvbnN0YW50LXRpbWUgb25cbiAqIHRoZSBoYXNoLCBhbmQgdGhlIGVudHJ5IGlzIGRlbGV0ZWQgb24gc3VjY2VzcyBvciBvbmNlIGF0dGVtcHRzIGFyZSBleGhhdXN0ZWQuXG4gKlxuICogVGhlIHNlc3Npb24gSldUIGlzIGFuIEhTMjU2IHRva2VuICh2aWEgYGpvc2VgKSBjYXJyeWluZyBvbmx5IHRoZSBlbWFpbC4gSXRzXG4gKiBzaWduaW5nIGtleSBpcyByZXNvbHZlZCBieSBgLi9zaWduaW5nLWtleS50c2A6IGBUVU5ORUxfQVVUSF9KV1RfU0VDUkVUYCB3aGVuXG4gKiBzZXQsIGVsc2UgYSBrZXkgcGVyc2lzdGVkIGluIHRoZSBjYWNoZSBmb3IgMzAgZGF5cyBzbyBjb29raWVzIHN1cnZpdmUgdGhlXG4gKiByZXN0YXJ0cyBhIHR1bm5lbCBzZWVzIHdoZW5ldmVyIHRoZSBhcHAgaXQgd3JhcHMgcmVsb2Fkcy4gQSBjb29raWUgc2lnbmVkXG4gKiBiZWZvcmUgYFRVTk5FTF9BVVRIX1NFU1NJT05fQ1VUT0ZGYCBpcyByZWZ1c2VkIGhlcmUgYXMgd2VsbCBhcyBiZWluZyBvcnBoYW5lZFxuICogYnkgdGhlIGNhY2hlIGtleSwgc28gdGhlIGZvcmNlLWNsZWFyIHN3aXRjaCBob2xkcyBldmVuIGZvciBhIHN0aWxsLWN1cnJlbnQga2V5LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVIYXNoLCByYW5kb21JbnQsIHRpbWluZ1NhZmVFcXVhbCB9IGZyb20gXCJub2RlOmNyeXB0b1wiO1xuaW1wb3J0IHsgQ2FjaGVNYW5hZ2VyIH0gZnJvbSBcIkBkYXRhYnJpY2tzL2FwcGtpdFwiO1xuaW1wb3J0IHsgand0VmVyaWZ5LCBTaWduSldUIH0gZnJvbSBcImpvc2VcIjtcbmltcG9ydCB7IHNpZ25pbmdLZXkgfSBmcm9tIFwiLi9zaWduaW5nLWtleS50c1wiO1xuXG4vKiogSldUIGlzc3Vlci9hdWRpZW5jZSBzbyBhIHRva2VuIG1pbnRlZCBmb3IgdGhpcyBnYXRlIGlzbid0IGFjY2VwdGVkIGVsc2V3aGVyZS4gKi9cbmNvbnN0IEpXVF9BVUQgPSBcImRieC10b29scy10dW5uZWwtYXV0aFwiO1xuXG4vKiogQ2FjaGUta2V5IHByZWZpeCBmb3IgcGVuZGluZyBjb2RlcywgbmFtZXNwYWNlZCBhd2F5IGZyb20gYW55IG90aGVyIGNhY2hlIHVzZS4gKi9cbmNvbnN0IENPREVfUFJFRklYID0gXCJ0dW5uZWw6b3RwOlwiO1xuXG4vKiogU0hBLTI1NiBoZXggb2YgYSB2YWx1ZS4gKi9cbmZ1bmN0aW9uIHNoYTI1Nih2YWx1ZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIGNyZWF0ZUhhc2goXCJzaGEyNTZcIikudXBkYXRlKHZhbHVlKS5kaWdlc3QoXCJoZXhcIik7XG59XG5cbi8qKiBDb25zdGFudC10aW1lIGNvbXBhcmUgb2YgdHdvIGVxdWFsLWxlbmd0aCBoZXggZGlnZXN0cy4gKi9cbmZ1bmN0aW9uIHNhZmVFcXVhbEhleChhOiBzdHJpbmcsIGI6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBpZiAoYS5sZW5ndGggIT09IGIubGVuZ3RoKSByZXR1cm4gZmFsc2U7XG4gIHJldHVybiB0aW1pbmdTYWZlRXF1YWwoQnVmZmVyLmZyb20oYSwgXCJoZXhcIiksIEJ1ZmZlci5mcm9tKGIsIFwiaGV4XCIpKTtcbn1cblxuaW50ZXJmYWNlIENvZGVFbnRyeSB7XG4gIGhhc2g6IHN0cmluZztcbiAgYXR0ZW1wdHM6IG51bWJlcjtcbn1cblxuLyoqIFJlc3VsdCBvZiB7QGxpbmsgQ29kZVN0b3JlLnZlcmlmeX0uICovXG5leHBvcnQgdHlwZSBWZXJpZnlPdXRjb21lID0gXCJva1wiIHwgXCJpbnZhbGlkXCIgfCBcImV4cGlyZWRcIiB8IFwidG9vLW1hbnktYXR0ZW1wdHNcIjtcblxuLyoqXG4gKiBQZW5kaW5nIG9uZS10aW1lIGNvZGVzLCBzdG9yZWQgaW4gQXBwS2l0J3MgY2FjaGUga2V5ZWQgYnkgbG93ZXJjYXNlZCBlbWFpbC5cbiAqIEV4cGlyeSBpcyB0aGUgY2FjaGUncyBUVEwgKG5vIG1hbnVhbCBjbG9jayk7IGEgbWlzcyBtZWFucyBleHBpcmVkLW9yLW5ldmVyLlxuICovXG5leHBvcnQgY2xhc3MgQ29kZVN0b3JlIHtcbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSB0dGxTZWNvbmRzOiBudW1iZXIsXG4gICAgcHJpdmF0ZSByZWFkb25seSBtYXhBdHRlbXB0czogbnVtYmVyLFxuICApIHt9XG5cbiAgcHJpdmF0ZSBjYWNoZSgpOiBDYWNoZU1hbmFnZXIge1xuICAgIHJldHVybiBDYWNoZU1hbmFnZXIuZ2V0SW5zdGFuY2VTeW5jKCk7XG4gIH1cblxuICBwcml2YXRlIGtleShlbWFpbDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYCR7Q09ERV9QUkVGSVh9JHtlbWFpbC50b0xvd2VyQ2FzZSgpfWA7XG4gIH1cblxuICAvKipcbiAgICogR2VuZXJhdGUsIHN0b3JlIChoYXNoZWQsIHdpdGggdGhlIGNhY2hlIFRUTCksIGFuZCBSRVRVUk4gYSBmcmVzaCA2LWRpZ2l0XG4gICAqIGNvZGUuIFRoZSBjYWxsZXIgZW1haWxzIHRoZSByZXR1cm5lZCBwbGFpbnRleHQ7IG9ubHkgdGhlIGhhc2ggaXMgcmV0YWluZWQuXG4gICAqIFJlcGxhY2VzIGFueSBwZW5kaW5nIGNvZGUgZm9yIHRoZSBhZGRyZXNzLlxuICAgKi9cbiAgYXN5bmMgaXNzdWUoZW1haWw6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgY29kZSA9IFN0cmluZyhyYW5kb21JbnQoMCwgMV8wMDBfMDAwKSkucGFkU3RhcnQoNiwgXCIwXCIpO1xuICAgIGNvbnN0IGVudHJ5OiBDb2RlRW50cnkgPSB7IGhhc2g6IHNoYTI1Nihjb2RlKSwgYXR0ZW1wdHM6IDAgfTtcbiAgICBhd2FpdCB0aGlzLmNhY2hlKCkuc2V0KHRoaXMua2V5KGVtYWlsKSwgZW50cnksIHsgdHRsOiB0aGlzLnR0bFNlY29uZHMgfSk7XG4gICAgcmV0dXJuIGNvZGU7XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2sgYGNvZGVgIGZvciBgZW1haWxgLiBBIGNhY2hlIG1pc3MgaXMgYGV4cGlyZWRgIChUVEwgZWxhcHNlZCBvciBuZXZlclxuICAgKiBpc3N1ZWQpLiBEZWxldGVzIHRoZSBlbnRyeSBvbiBzdWNjZXNzIG9yIHdoZW4gYXR0ZW1wdHMgYXJlIGV4aGF1c3RlZCwgc28gYVxuICAgKiBjb2RlIGlzIHNpbmdsZS11c2UgYW5kIGNhbid0IGJlIGJydXRlLWZvcmNlZCBwYXN0IHRoZSBjYXAuIEFuIGF0dGVtcHRcbiAgICogaW5jcmVtZW50cyB0aGUgc3RvcmVkIGNvdW50ZXIgKHJlLXBlcnNpc3RlZCB3aXRoIGEgZnJlc2ggVFRMIHdpbmRvdykuXG4gICAqL1xuICBhc3luYyB2ZXJpZnkoZW1haWw6IHN0cmluZywgY29kZTogc3RyaW5nKTogUHJvbWlzZTxWZXJpZnlPdXRjb21lPiB7XG4gICAgY29uc3Qga2V5ID0gdGhpcy5rZXkoZW1haWwpO1xuICAgIGNvbnN0IGVudHJ5ID0gYXdhaXQgdGhpcy5jYWNoZSgpLmdldDxDb2RlRW50cnk+KGtleSk7XG4gICAgaWYgKCFlbnRyeSkgcmV0dXJuIFwiZXhwaXJlZFwiO1xuICAgIGNvbnN0IGF0dGVtcHRzID0gZW50cnkuYXR0ZW1wdHMgKyAxO1xuICAgIGlmIChzYWZlRXF1YWxIZXgoZW50cnkuaGFzaCwgc2hhMjU2KGNvZGUpKSkge1xuICAgICAgYXdhaXQgdGhpcy5jYWNoZSgpLmRlbGV0ZShrZXkpO1xuICAgICAgcmV0dXJuIFwib2tcIjtcbiAgICB9XG4gICAgaWYgKGF0dGVtcHRzID49IHRoaXMubWF4QXR0ZW1wdHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuY2FjaGUoKS5kZWxldGUoa2V5KTtcbiAgICAgIHJldHVybiBcInRvby1tYW55LWF0dGVtcHRzXCI7XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuY2FjaGUoKS5zZXQoa2V5LCB7IC4uLmVudHJ5LCBhdHRlbXB0cyB9LCB7IHR0bDogdGhpcy50dGxTZWNvbmRzIH0pO1xuICAgIHJldHVybiBcImludmFsaWRcIjtcbiAgfVxufVxuXG4vKiogTWludCBhIHNlc3Npb24gSldUIGZvciBgZW1haWxgLCBleHBpcmluZyBpbiBgdHRsU2Vjb25kc2AuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2lnblNlc3Npb24oZW1haWw6IHN0cmluZywgdHRsU2Vjb25kczogbnVtYmVyKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgeyBrZXkgfSA9IGF3YWl0IHNpZ25pbmdLZXkoKTtcbiAgcmV0dXJuIG5ldyBTaWduSldUKHsgZW1haWwgfSlcbiAgICAuc2V0UHJvdGVjdGVkSGVhZGVyKHsgYWxnOiBcIkhTMjU2XCIgfSlcbiAgICAuc2V0U3ViamVjdChlbWFpbClcbiAgICAuc2V0QXVkaWVuY2UoSldUX0FVRClcbiAgICAuc2V0SXNzdWVkQXQoKVxuICAgIC5zZXRFeHBpcmF0aW9uVGltZShgJHt0dGxTZWNvbmRzfXNgKVxuICAgIC5zaWduKGtleSk7XG59XG5cbi8qKiBWYWxpZGF0ZSBhIHNlc3Npb24gSldULCByZXR1cm5pbmcgdGhlIGVtYWlsIGl0IHdhcyBtaW50ZWQgZm9yLCBvciBgdW5kZWZpbmVkYC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB2ZXJpZnlTZXNzaW9uKHRva2VuOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICBpZiAoIXRva2VuKSByZXR1cm4gdW5kZWZpbmVkO1xuICB0cnkge1xuICAgIGNvbnN0IHsga2V5LCBjdXRvZmZNcyB9ID0gYXdhaXQgc2lnbmluZ0tleSgpO1xuICAgIGNvbnN0IHsgcGF5bG9hZCB9ID0gYXdhaXQgand0VmVyaWZ5KHRva2VuLCBrZXksIHsgYXVkaWVuY2U6IEpXVF9BVUQgfSk7XG4gICAgLy8gQmVsdCBhbmQgYnJhY2VzIHdpdGggdGhlIGN1dG9mZi1zY29wZWQgY2FjaGUga2V5OiB0aGF0IGFsb25lIGFscmVhZHlcbiAgICAvLyBvcnBoYW5zIG9sZGVyIGtleXMsIGJ1dCBhbiBvcGVyYXRvciB3aG8gbW92ZWQgdGhlIGN1dG9mZiB3aGlsZVxuICAgIC8vIFRVTk5FTF9BVVRIX0pXVF9TRUNSRVQgaXMgc2V0IGhhcyBubyBrZXkgcm90YXRpb24gdG8gcmVseSBvbiwgYW5kIHRoaXNcbiAgICAvLyBjaGVjayBpcyB3aGF0IG1ha2VzIHRoZSBzd2l0Y2ggd29yayBpbiB0aGF0IGNhc2UgdG9vLlxuICAgIC8vIENvbXBhcmVkIGluIHdob2xlIFNFQ09ORFMgYmVjYXVzZSBgaWF0YCBoYXMgbm8gZmluZXIgcmVzb2x1dGlvbjogYWdhaW5zdCBhXG4gICAgLy8gbWlsbGlzZWNvbmQgY3V0b2ZmLCBhIGNvb2tpZSBtaW50ZWQgaW4gdGhlIHNhbWUgc2Vjb25kIGFzIGl0IHdvdWxkIGJlXG4gICAgLy8gcmVmdXNlZCBkZXBlbmRpbmcgb24gc3ViLXNlY29uZCByb3VuZGluZy5cbiAgICBpZiAoY3V0b2ZmTXMgPiAwICYmIChwYXlsb2FkLmlhdCA9PT0gdW5kZWZpbmVkIHx8IHBheWxvYWQuaWF0IDwgTWF0aC5mbG9vcihjdXRvZmZNcyAvIDEwMDApKSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHR5cGVvZiBwYXlsb2FkLmVtYWlsID09PSBcInN0cmluZ1wiID8gcGF5bG9hZC5lbWFpbCA6IHVuZGVmaW5lZDtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxufVxuIl19
|