@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.
Files changed (45) hide show
  1. package/README.md +1 -364
  2. package/index.ts +2 -19
  3. package/lib/index.d.ts +2 -19
  4. package/lib/index.js +2 -15
  5. package/lib/src/app.d.ts +12 -123
  6. package/lib/src/app.js +22 -250
  7. package/lib/src/cli.d.ts +19 -22
  8. package/lib/src/cli.js +143 -128
  9. package/lib/src/options.d.ts +46 -0
  10. package/lib/src/options.js +51 -0
  11. package/lib/src/proxy.d.ts +27 -37
  12. package/lib/src/proxy.js +115 -220
  13. package/lib/tsconfig.tsbuildinfo +1 -1
  14. package/package.json +15 -81
  15. package/src/app.ts +20 -282
  16. package/src/cli.ts +152 -162
  17. package/src/options.ts +85 -0
  18. package/src/proxy.ts +139 -261
  19. package/bin/dbx-tools-tunnel.ts +0 -13
  20. package/lib/bin/dbx-tools-tunnel.d.ts +0 -2
  21. package/lib/bin/dbx-tools-tunnel.js +0 -14
  22. package/lib/src/allowlist.d.ts +0 -32
  23. package/lib/src/allowlist.js +0 -57
  24. package/lib/src/env.d.ts +0 -57
  25. package/lib/src/env.js +0 -60
  26. package/lib/src/headers.d.ts +0 -108
  27. package/lib/src/headers.js +0 -140
  28. package/lib/src/otp.d.ts +0 -49
  29. package/lib/src/otp.js +0 -124
  30. package/lib/src/plugin.d.ts +0 -147
  31. package/lib/src/plugin.js +0 -138
  32. package/lib/src/portr.d.ts +0 -40
  33. package/lib/src/portr.js +0 -93
  34. package/lib/src/rate-limit.d.ts +0 -35
  35. package/lib/src/rate-limit.js +0 -53
  36. package/lib/src/signing-key.d.ts +0 -86
  37. package/lib/src/signing-key.js +0 -170
  38. package/src/allowlist.ts +0 -60
  39. package/src/env.ts +0 -72
  40. package/src/headers.ts +0 -155
  41. package/src/otp.ts +0 -137
  42. package/src/plugin.ts +0 -269
  43. package/src/portr.ts +0 -113
  44. package/src/rate-limit.ts +0 -59
  45. 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==
@@ -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;
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3RwLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL290cC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBRUgsT0FBTyxFQUFFLFVBQVUsRUFBRSxTQUFTLEVBQUUsZUFBZSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JFLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUMxQyxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sa0JBQWtCLENBQUM7QUFFOUMsb0ZBQW9GO0FBQ3BGLE1BQU0sT0FBTyxHQUFHLHVCQUF1QixDQUFDO0FBRXhDLG9GQUFvRjtBQUNwRixNQUFNLFdBQVcsR0FBRyxhQUFhLENBQUM7QUFFbEMsOEJBQThCO0FBQzlCLFNBQVMsTUFBTSxDQUFDLEtBQWE7SUFDM0IsT0FBTyxVQUFVLENBQUMsUUFBUSxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztBQUMxRCxDQUFDO0FBRUQsNkRBQTZEO0FBQzdELFNBQVMsWUFBWSxDQUFDLENBQVMsRUFBRSxDQUFTO0lBQ3hDLElBQUksQ0FBQyxDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUMsTUFBTTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3hDLE9BQU8sZUFBZSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQVVEOzs7R0FHRztBQUNILE1BQU0sT0FBTyxTQUFTO0lBRUQ7SUFDQTtJQUZuQixZQUNtQixVQUFrQixFQUNsQixXQUFtQjtRQURuQixlQUFVLEdBQVYsVUFBVSxDQUFRO1FBQ2xCLGdCQUFXLEdBQVgsV0FBVyxDQUFRO0lBQ25DLENBQUM7SUFFSSxLQUFLO1FBQ1gsT0FBTyxZQUFZLENBQUMsZUFBZSxFQUFFLENBQUM7SUFDeEMsQ0FBQztJQUVPLEdBQUcsQ0FBQyxLQUFhO1FBQ3ZCLE9BQU8sR0FBRyxXQUFXLEdBQUcsS0FBSyxDQUFDLFdBQVcsRUFBRSxFQUFFLENBQUM7SUFDaEQsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxLQUFLLENBQUMsS0FBSyxDQUFDLEtBQWE7UUFDdkIsTUFBTSxJQUFJLEdBQUcsTUFBTSxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsU0FBUyxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO1FBQzlELE1BQU0sS0FBSyxHQUFjLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQyxFQUFFLENBQUM7UUFDN0QsTUFBTSxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLEVBQUUsS0FBSyxFQUFFLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxVQUFVLEVBQUUsQ0FBQyxDQUFDO1FBQ3pFLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVEOzs7OztPQUtHO0lBQ0gsS0FBSyxDQUFDLE1BQU0sQ0FBQyxLQUFhLEVBQUUsSUFBWTtRQUN0QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzVCLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLEdBQUcsQ0FBWSxHQUFHLENBQUMsQ0FBQztRQUNyRCxJQUFJLENBQUMsS0FBSztZQUFFLE9BQU8sU0FBUyxDQUFDO1FBQzdCLE1BQU0sUUFBUSxHQUFHLEtBQUssQ0FBQyxRQUFRLEdBQUcsQ0FBQyxDQUFDO1FBQ3BDLElBQUksWUFBWSxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUMzQyxNQUFNLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDL0IsT0FBTyxJQUFJLENBQUM7UUFDZCxDQUFDO1FBQ0QsSUFBSSxRQUFRLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxDQUFDO1lBQ2pDLE1BQU0sSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUMvQixPQUFPLG1CQUFtQixDQUFDO1FBQzdCLENBQUM7UUFDRCxNQUFNLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUUsR0FBRyxLQUFLLEVBQUUsUUFBUSxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDLENBQUM7UUFDOUUsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztDQUNGO0FBRUQsZ0VBQWdFO0FBQ2hFLE1BQU0sQ0FBQyxLQUFLLFVBQVUsV0FBVyxDQUFDLEtBQWEsRUFBRSxVQUFrQjtJQUNqRSxNQUFNLEVBQUUsR0FBRyxFQUFFLEdBQUcsTUFBTSxVQUFVLEVBQUUsQ0FBQztJQUNuQyxPQUFPLElBQUksT0FBTyxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUM7U0FDMUIsa0JBQWtCLENBQUMsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLENBQUM7U0FDcEMsVUFBVSxDQUFDLEtBQUssQ0FBQztTQUNqQixXQUFXLENBQUMsT0FBTyxDQUFDO1NBQ3BCLFdBQVcsRUFBRTtTQUNiLGlCQUFpQixDQUFDLEdBQUcsVUFBVSxHQUFHLENBQUM7U0FDbkMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0FBQ2YsQ0FBQztBQUVELHFGQUFxRjtBQUNyRixNQUFNLENBQUMsS0FBSyxVQUFVLGFBQWEsQ0FBQyxLQUF5QjtJQUMzRCxJQUFJLENBQUMsS0FBSztRQUFFLE9BQU8sU0FBUyxDQUFDO0lBQzdCLElBQUksQ0FBQztRQUNILE1BQU0sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxVQUFVLEVBQUUsQ0FBQztRQUM3QyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsTUFBTSxTQUFTLENBQUMsS0FBSyxFQUFFLEdBQUcsRUFBRSxFQUFFLFFBQVEsRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO1FBQ3ZFLHVFQUF1RTtRQUN2RSxpRUFBaUU7UUFDakUseUVBQXlFO1FBQ3pFLHdEQUF3RDtRQUN4RCw2RUFBNkU7UUFDN0Usd0VBQXdFO1FBQ3hFLDRDQUE0QztRQUM1QyxJQUFJLFFBQVEsR0FBRyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsR0FBRyxLQUFLLFNBQVMsSUFBSSxPQUFPLENBQUMsR0FBRyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsUUFBUSxHQUFHLElBQUksQ0FBQyxDQUFDLEVBQUUsQ0FBQztZQUM3RixPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBQ0QsT0FBTyxPQUFPLE9BQU8sQ0FBQyxLQUFLLEtBQUssUUFBUSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxTQUFTLENBQUM7SUFDdkUsQ0FBQztJQUFDLE1BQU0sQ0FBQztRQUNQLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7QUFDSCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBPbmUtdGltZS1jb2RlIHN0b3JlICsgc2Vzc2lvbiBKV1QgZm9yIHRoZSBlbWFpbC1PVFAgdHVubmVsIGdhdGUuXG4gKlxuICogVGhlIGNvZGUgc3RvcmUgaXMgYmFja2VkIGJ5IEFwcEtpdCdzIGBDYWNoZU1hbmFnZXJgIChhdXRvLWNvbmZpZ3VyZWQgdG8gbWVtb3J5LFxuICogb3IgTGFrZWJhc2Ugd2hlbiB0aGUgYXBwIHdpcmVzIGEgcGVyc2lzdGVudCBgQ2FjaGVTdG9yYWdlYCksIHNvIFRUTCBFWFBJUlkgYW5kXG4gKiBldmljdGlvbiBhcmUgdGhlIGNhY2hlJ3Mgam9iIC0gbm8gaGFuZC1yb2xsZWQgTWFwIG9yIHRpbWVycy4gQSA2LWRpZ2l0IGNvZGUgaXNcbiAqIGdlbmVyYXRlZCB3aXRoIGBjcnlwdG8ucmFuZG9tSW50YCBhbmQgc3RvcmVkIGFzIGEgU0hBLTI1NiBoYXNoIHdpdGggYW4gYXR0ZW1wdFxuICogY291bnRlciAobmV2ZXIgdGhlIHBsYWludGV4dCwgbmV2ZXIgaW4gdGhlIEpXVCk7IGB2ZXJpZnlgIGlzIGNvbnN0YW50LXRpbWUgb25cbiAqIHRoZSBoYXNoLCBhbmQgdGhlIGVudHJ5IGlzIGRlbGV0ZWQgb24gc3VjY2VzcyBvciBvbmNlIGF0dGVtcHRzIGFyZSBleGhhdXN0ZWQuXG4gKlxuICogVGhlIHNlc3Npb24gSldUIGlzIGFuIEhTMjU2IHRva2VuICh2aWEgYGpvc2VgKSBjYXJyeWluZyBvbmx5IHRoZSBlbWFpbC4gSXRzXG4gKiBzaWduaW5nIGtleSBpcyByZXNvbHZlZCBieSBgLi9zaWduaW5nLWtleS50c2A6IGBUVU5ORUxfQVVUSF9KV1RfU0VDUkVUYCB3aGVuXG4gKiBzZXQsIGVsc2UgYSBrZXkgcGVyc2lzdGVkIGluIHRoZSBjYWNoZSBmb3IgMzAgZGF5cyBzbyBjb29raWVzIHN1cnZpdmUgdGhlXG4gKiByZXN0YXJ0cyBhIHR1bm5lbCBzZWVzIHdoZW5ldmVyIHRoZSBhcHAgaXQgd3JhcHMgcmVsb2Fkcy4gQSBjb29raWUgc2lnbmVkXG4gKiBiZWZvcmUgYFRVTk5FTF9BVVRIX1NFU1NJT05fQ1VUT0ZGYCBpcyByZWZ1c2VkIGhlcmUgYXMgd2VsbCBhcyBiZWluZyBvcnBoYW5lZFxuICogYnkgdGhlIGNhY2hlIGtleSwgc28gdGhlIGZvcmNlLWNsZWFyIHN3aXRjaCBob2xkcyBldmVuIGZvciBhIHN0aWxsLWN1cnJlbnQga2V5LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVIYXNoLCByYW5kb21JbnQsIHRpbWluZ1NhZmVFcXVhbCB9IGZyb20gXCJub2RlOmNyeXB0b1wiO1xuaW1wb3J0IHsgQ2FjaGVNYW5hZ2VyIH0gZnJvbSBcIkBkYXRhYnJpY2tzL2FwcGtpdFwiO1xuaW1wb3J0IHsgand0VmVyaWZ5LCBTaWduSldUIH0gZnJvbSBcImpvc2VcIjtcbmltcG9ydCB7IHNpZ25pbmdLZXkgfSBmcm9tIFwiLi9zaWduaW5nLWtleS50c1wiO1xuXG4vKiogSldUIGlzc3Vlci9hdWRpZW5jZSBzbyBhIHRva2VuIG1pbnRlZCBmb3IgdGhpcyBnYXRlIGlzbid0IGFjY2VwdGVkIGVsc2V3aGVyZS4gKi9cbmNvbnN0IEpXVF9BVUQgPSBcImRieC10b29scy10dW5uZWwtYXV0aFwiO1xuXG4vKiogQ2FjaGUta2V5IHByZWZpeCBmb3IgcGVuZGluZyBjb2RlcywgbmFtZXNwYWNlZCBhd2F5IGZyb20gYW55IG90aGVyIGNhY2hlIHVzZS4gKi9cbmNvbnN0IENPREVfUFJFRklYID0gXCJ0dW5uZWw6b3RwOlwiO1xuXG4vKiogU0hBLTI1NiBoZXggb2YgYSB2YWx1ZS4gKi9cbmZ1bmN0aW9uIHNoYTI1Nih2YWx1ZTogc3RyaW5nKTogc3RyaW5nIHtcbiAgcmV0dXJuIGNyZWF0ZUhhc2goXCJzaGEyNTZcIikudXBkYXRlKHZhbHVlKS5kaWdlc3QoXCJoZXhcIik7XG59XG5cbi8qKiBDb25zdGFudC10aW1lIGNvbXBhcmUgb2YgdHdvIGVxdWFsLWxlbmd0aCBoZXggZGlnZXN0cy4gKi9cbmZ1bmN0aW9uIHNhZmVFcXVhbEhleChhOiBzdHJpbmcsIGI6IHN0cmluZyk6IGJvb2xlYW4ge1xuICBpZiAoYS5sZW5ndGggIT09IGIubGVuZ3RoKSByZXR1cm4gZmFsc2U7XG4gIHJldHVybiB0aW1pbmdTYWZlRXF1YWwoQnVmZmVyLmZyb20oYSwgXCJoZXhcIiksIEJ1ZmZlci5mcm9tKGIsIFwiaGV4XCIpKTtcbn1cblxuaW50ZXJmYWNlIENvZGVFbnRyeSB7XG4gIGhhc2g6IHN0cmluZztcbiAgYXR0ZW1wdHM6IG51bWJlcjtcbn1cblxuLyoqIFJlc3VsdCBvZiB7QGxpbmsgQ29kZVN0b3JlLnZlcmlmeX0uICovXG5leHBvcnQgdHlwZSBWZXJpZnlPdXRjb21lID0gXCJva1wiIHwgXCJpbnZhbGlkXCIgfCBcImV4cGlyZWRcIiB8IFwidG9vLW1hbnktYXR0ZW1wdHNcIjtcblxuLyoqXG4gKiBQZW5kaW5nIG9uZS10aW1lIGNvZGVzLCBzdG9yZWQgaW4gQXBwS2l0J3MgY2FjaGUga2V5ZWQgYnkgbG93ZXJjYXNlZCBlbWFpbC5cbiAqIEV4cGlyeSBpcyB0aGUgY2FjaGUncyBUVEwgKG5vIG1hbnVhbCBjbG9jayk7IGEgbWlzcyBtZWFucyBleHBpcmVkLW9yLW5ldmVyLlxuICovXG5leHBvcnQgY2xhc3MgQ29kZVN0b3JlIHtcbiAgY29uc3RydWN0b3IoXG4gICAgcHJpdmF0ZSByZWFkb25seSB0dGxTZWNvbmRzOiBudW1iZXIsXG4gICAgcHJpdmF0ZSByZWFkb25seSBtYXhBdHRlbXB0czogbnVtYmVyLFxuICApIHt9XG5cbiAgcHJpdmF0ZSBjYWNoZSgpOiBDYWNoZU1hbmFnZXIge1xuICAgIHJldHVybiBDYWNoZU1hbmFnZXIuZ2V0SW5zdGFuY2VTeW5jKCk7XG4gIH1cblxuICBwcml2YXRlIGtleShlbWFpbDogc3RyaW5nKTogc3RyaW5nIHtcbiAgICByZXR1cm4gYCR7Q09ERV9QUkVGSVh9JHtlbWFpbC50b0xvd2VyQ2FzZSgpfWA7XG4gIH1cblxuICAvKipcbiAgICogR2VuZXJhdGUsIHN0b3JlIChoYXNoZWQsIHdpdGggdGhlIGNhY2hlIFRUTCksIGFuZCBSRVRVUk4gYSBmcmVzaCA2LWRpZ2l0XG4gICAqIGNvZGUuIFRoZSBjYWxsZXIgZW1haWxzIHRoZSByZXR1cm5lZCBwbGFpbnRleHQ7IG9ubHkgdGhlIGhhc2ggaXMgcmV0YWluZWQuXG4gICAqIFJlcGxhY2VzIGFueSBwZW5kaW5nIGNvZGUgZm9yIHRoZSBhZGRyZXNzLlxuICAgKi9cbiAgYXN5bmMgaXNzdWUoZW1haWw6IHN0cmluZyk6IFByb21pc2U8c3RyaW5nPiB7XG4gICAgY29uc3QgY29kZSA9IFN0cmluZyhyYW5kb21JbnQoMCwgMV8wMDBfMDAwKSkucGFkU3RhcnQoNiwgXCIwXCIpO1xuICAgIGNvbnN0IGVudHJ5OiBDb2RlRW50cnkgPSB7IGhhc2g6IHNoYTI1Nihjb2RlKSwgYXR0ZW1wdHM6IDAgfTtcbiAgICBhd2FpdCB0aGlzLmNhY2hlKCkuc2V0KHRoaXMua2V5KGVtYWlsKSwgZW50cnksIHsgdHRsOiB0aGlzLnR0bFNlY29uZHMgfSk7XG4gICAgcmV0dXJuIGNvZGU7XG4gIH1cblxuICAvKipcbiAgICogQ2hlY2sgYGNvZGVgIGZvciBgZW1haWxgLiBBIGNhY2hlIG1pc3MgaXMgYGV4cGlyZWRgIChUVEwgZWxhcHNlZCBvciBuZXZlclxuICAgKiBpc3N1ZWQpLiBEZWxldGVzIHRoZSBlbnRyeSBvbiBzdWNjZXNzIG9yIHdoZW4gYXR0ZW1wdHMgYXJlIGV4aGF1c3RlZCwgc28gYVxuICAgKiBjb2RlIGlzIHNpbmdsZS11c2UgYW5kIGNhbid0IGJlIGJydXRlLWZvcmNlZCBwYXN0IHRoZSBjYXAuIEFuIGF0dGVtcHRcbiAgICogaW5jcmVtZW50cyB0aGUgc3RvcmVkIGNvdW50ZXIgKHJlLXBlcnNpc3RlZCB3aXRoIGEgZnJlc2ggVFRMIHdpbmRvdykuXG4gICAqL1xuICBhc3luYyB2ZXJpZnkoZW1haWw6IHN0cmluZywgY29kZTogc3RyaW5nKTogUHJvbWlzZTxWZXJpZnlPdXRjb21lPiB7XG4gICAgY29uc3Qga2V5ID0gdGhpcy5rZXkoZW1haWwpO1xuICAgIGNvbnN0IGVudHJ5ID0gYXdhaXQgdGhpcy5jYWNoZSgpLmdldDxDb2RlRW50cnk+KGtleSk7XG4gICAgaWYgKCFlbnRyeSkgcmV0dXJuIFwiZXhwaXJlZFwiO1xuICAgIGNvbnN0IGF0dGVtcHRzID0gZW50cnkuYXR0ZW1wdHMgKyAxO1xuICAgIGlmIChzYWZlRXF1YWxIZXgoZW50cnkuaGFzaCwgc2hhMjU2KGNvZGUpKSkge1xuICAgICAgYXdhaXQgdGhpcy5jYWNoZSgpLmRlbGV0ZShrZXkpO1xuICAgICAgcmV0dXJuIFwib2tcIjtcbiAgICB9XG4gICAgaWYgKGF0dGVtcHRzID49IHRoaXMubWF4QXR0ZW1wdHMpIHtcbiAgICAgIGF3YWl0IHRoaXMuY2FjaGUoKS5kZWxldGUoa2V5KTtcbiAgICAgIHJldHVybiBcInRvby1tYW55LWF0dGVtcHRzXCI7XG4gICAgfVxuICAgIGF3YWl0IHRoaXMuY2FjaGUoKS5zZXQoa2V5LCB7IC4uLmVudHJ5LCBhdHRlbXB0cyB9LCB7IHR0bDogdGhpcy50dGxTZWNvbmRzIH0pO1xuICAgIHJldHVybiBcImludmFsaWRcIjtcbiAgfVxufVxuXG4vKiogTWludCBhIHNlc3Npb24gSldUIGZvciBgZW1haWxgLCBleHBpcmluZyBpbiBgdHRsU2Vjb25kc2AuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gc2lnblNlc3Npb24oZW1haWw6IHN0cmluZywgdHRsU2Vjb25kczogbnVtYmVyKTogUHJvbWlzZTxzdHJpbmc+IHtcbiAgY29uc3QgeyBrZXkgfSA9IGF3YWl0IHNpZ25pbmdLZXkoKTtcbiAgcmV0dXJuIG5ldyBTaWduSldUKHsgZW1haWwgfSlcbiAgICAuc2V0UHJvdGVjdGVkSGVhZGVyKHsgYWxnOiBcIkhTMjU2XCIgfSlcbiAgICAuc2V0U3ViamVjdChlbWFpbClcbiAgICAuc2V0QXVkaWVuY2UoSldUX0FVRClcbiAgICAuc2V0SXNzdWVkQXQoKVxuICAgIC5zZXRFeHBpcmF0aW9uVGltZShgJHt0dGxTZWNvbmRzfXNgKVxuICAgIC5zaWduKGtleSk7XG59XG5cbi8qKiBWYWxpZGF0ZSBhIHNlc3Npb24gSldULCByZXR1cm5pbmcgdGhlIGVtYWlsIGl0IHdhcyBtaW50ZWQgZm9yLCBvciBgdW5kZWZpbmVkYC4gKi9cbmV4cG9ydCBhc3luYyBmdW5jdGlvbiB2ZXJpZnlTZXNzaW9uKHRva2VuOiBzdHJpbmcgfCB1bmRlZmluZWQpOiBQcm9taXNlPHN0cmluZyB8IHVuZGVmaW5lZD4ge1xuICBpZiAoIXRva2VuKSByZXR1cm4gdW5kZWZpbmVkO1xuICB0cnkge1xuICAgIGNvbnN0IHsga2V5LCBjdXRvZmZNcyB9ID0gYXdhaXQgc2lnbmluZ0tleSgpO1xuICAgIGNvbnN0IHsgcGF5bG9hZCB9ID0gYXdhaXQgand0VmVyaWZ5KHRva2VuLCBrZXksIHsgYXVkaWVuY2U6IEpXVF9BVUQgfSk7XG4gICAgLy8gQmVsdCBhbmQgYnJhY2VzIHdpdGggdGhlIGN1dG9mZi1zY29wZWQgY2FjaGUga2V5OiB0aGF0IGFsb25lIGFscmVhZHlcbiAgICAvLyBvcnBoYW5zIG9sZGVyIGtleXMsIGJ1dCBhbiBvcGVyYXRvciB3aG8gbW92ZWQgdGhlIGN1dG9mZiB3aGlsZVxuICAgIC8vIFRVTk5FTF9BVVRIX0pXVF9TRUNSRVQgaXMgc2V0IGhhcyBubyBrZXkgcm90YXRpb24gdG8gcmVseSBvbiwgYW5kIHRoaXNcbiAgICAvLyBjaGVjayBpcyB3aGF0IG1ha2VzIHRoZSBzd2l0Y2ggd29yayBpbiB0aGF0IGNhc2UgdG9vLlxuICAgIC8vIENvbXBhcmVkIGluIHdob2xlIFNFQ09ORFMgYmVjYXVzZSBgaWF0YCBoYXMgbm8gZmluZXIgcmVzb2x1dGlvbjogYWdhaW5zdCBhXG4gICAgLy8gbWlsbGlzZWNvbmQgY3V0b2ZmLCBhIGNvb2tpZSBtaW50ZWQgaW4gdGhlIHNhbWUgc2Vjb25kIGFzIGl0IHdvdWxkIGJlXG4gICAgLy8gcmVmdXNlZCBkZXBlbmRpbmcgb24gc3ViLXNlY29uZCByb3VuZGluZy5cbiAgICBpZiAoY3V0b2ZmTXMgPiAwICYmIChwYXlsb2FkLmlhdCA9PT0gdW5kZWZpbmVkIHx8IHBheWxvYWQuaWF0IDwgTWF0aC5mbG9vcihjdXRvZmZNcyAvIDEwMDApKSkge1xuICAgICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgICB9XG4gICAgcmV0dXJuIHR5cGVvZiBwYXlsb2FkLmVtYWlsID09PSBcInN0cmluZ1wiID8gcGF5bG9hZC5lbWFpbCA6IHVuZGVmaW5lZDtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxufVxuIl19