@dbx-tools/cli-tunnel 0.6.51 → 0.6.52

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.
@@ -31,11 +31,11 @@
31
31
  *
32
32
  * ## Forcing every session to end
33
33
  *
34
- * {@link resolveSessionEpoch} reads a date from `TUNNEL_AUTH_SESSION_EPOCH` (or
35
- * `--session-epoch`), and that date is part of the cache KEY. Moving it forward
34
+ * {@link resolveSessionCutoff} reads a date from `TUNNEL_AUTH_SESSION_CUTOFF` (or
35
+ * `--session-cutoff`), and that date is part of the cache KEY. Moving it forward
36
36
  * makes every prior key unreachable, so every cookie signed against it stops
37
37
  * verifying - the log-everyone-out switch, without having to find and flush a
38
- * cache entry. The epoch is also asserted against each token's `iat`, so a
38
+ * cache entry. The cutoff is also asserted against each token's `iat`, so a
39
39
  * cookie that predates it is refused even if it was signed with the key that is
40
40
  * somehow still current.
41
41
  *
@@ -50,25 +50,28 @@
50
50
  */
51
51
  export declare const KEY_TTL_SECONDS: number;
52
52
  /**
53
- * Resolve the force-clear epoch as epoch MILLISECONDS, or `0` when unset.
53
+ * Resolve the force-clear cutoff as epoch MILLISECONDS, or `0` when unset.
54
+ *
55
+ * Accepts whatever `object.toDate` accepts - a `Date`, `2026-08-02`, an ISO
56
+ * instant, epoch seconds or millis from `date +%s`, or a relative duration
57
+ * (`-30d`, `7 days ago`), which is the spelling an operator reaching for this
58
+ * usually wants: sign out everyone who signed in more than a month ago.
54
59
  *
55
- * Accepts anything `Date` parses (`2026-08-02`, an ISO timestamp) plus a bare
56
- * epoch-seconds / epoch-millis number, so a value pasted from `date +%s` works.
57
60
  * An UNPARSEABLE value is ignored with a warning rather than throwing: this is
58
61
  * the switch that logs a fleet back in, and failing to boot over a typo is worse
59
62
  * than not rotating.
60
63
  */
61
- export declare function resolveSessionEpoch(configured?: string | number | Date): number;
62
- /** A resolved key plus the epoch it is scoped to. */
64
+ export declare function resolveSessionCutoff(configured?: string | number | Date): number;
65
+ /** A resolved key plus the cutoff it is scoped to. */
63
66
  export interface SigningKey {
64
67
  key: Uint8Array;
65
- /** Force-clear epoch in ms; `0` when unset. Tokens older than this are refused. */
66
- epochMs: number;
68
+ /** Force-clear cutoff in ms; `0` when unset. Tokens older than this are refused. */
69
+ cutoffMs: number;
67
70
  }
68
71
  /**
69
72
  * The signing key for this gate, resolved once per process.
70
73
  *
71
- * Resolved ONCE, so `configuredEpoch` is honoured only on the first call - the
74
+ * Resolved ONCE, so `configuredCutoff` is honoured only on the first call - the
72
75
  * plugin's `setup()` passes its resolved value there, before any request can
73
76
  * reach the lazy path.
74
77
  *
@@ -78,6 +81,6 @@ export interface SigningKey {
78
81
  * so losing it costs sessions, never admission - a caller still needs a code
79
82
  * delivered to an allow-listed address.
80
83
  */
81
- export declare function signingKey(configuredEpoch?: string | number | Date): Promise<SigningKey>;
84
+ export declare function signingKey(configuredCutoff?: string | number | Date): Promise<SigningKey>;
82
85
  /** Reset the per-process key (tests, or after changing the env in-process). */
83
86
  export declare function resetSigningKey(): void;
@@ -31,11 +31,11 @@
31
31
  *
32
32
  * ## Forcing every session to end
33
33
  *
34
- * {@link resolveSessionEpoch} reads a date from `TUNNEL_AUTH_SESSION_EPOCH` (or
35
- * `--session-epoch`), and that date is part of the cache KEY. Moving it forward
34
+ * {@link resolveSessionCutoff} reads a date from `TUNNEL_AUTH_SESSION_CUTOFF` (or
35
+ * `--session-cutoff`), and that date is part of the cache KEY. Moving it forward
36
36
  * makes every prior key unreachable, so every cookie signed against it stops
37
37
  * verifying - the log-everyone-out switch, without having to find and flush a
38
- * cache entry. The epoch is also asserted against each token's `iat`, so a
38
+ * cache entry. The cutoff is also asserted against each token's `iat`, so a
39
39
  * cookie that predates it is refused even if it was signed with the key that is
40
40
  * somehow still current.
41
41
  *
@@ -43,8 +43,8 @@
43
43
  */
44
44
  import { randomBytes } from "node:crypto";
45
45
  import { CacheManager } from "@databricks/appkit";
46
- import { env, log } from "@dbx-tools/shared-core";
47
- import { JWT_SECRET_ENV, SESSION_EPOCH_ENV } from "./env.js";
46
+ import { env, log, object } from "@dbx-tools/shared-core";
47
+ import { JWT_SECRET_ENV, SESSION_CUTOFF_ENV } from "./env.js";
48
48
  const logger = log.logger("tunnel:signing-key");
49
49
  /**
50
50
  * How long a cached signing key lives: 30 days.
@@ -59,69 +59,58 @@ const KEY_PREFIX = "tunnel:auth:signing-key:";
59
59
  /** Bytes of entropy in a generated key (256-bit, matching HS256's hash width). */
60
60
  const KEY_BYTES = 32;
61
61
  /**
62
- * Resolve the force-clear epoch as epoch MILLISECONDS, or `0` when unset.
62
+ * Resolve the force-clear cutoff as epoch MILLISECONDS, or `0` when unset.
63
+ *
64
+ * Accepts whatever `object.toDate` accepts - a `Date`, `2026-08-02`, an ISO
65
+ * instant, epoch seconds or millis from `date +%s`, or a relative duration
66
+ * (`-30d`, `7 days ago`), which is the spelling an operator reaching for this
67
+ * usually wants: sign out everyone who signed in more than a month ago.
63
68
  *
64
- * Accepts anything `Date` parses (`2026-08-02`, an ISO timestamp) plus a bare
65
- * epoch-seconds / epoch-millis number, so a value pasted from `date +%s` works.
66
69
  * An UNPARSEABLE value is ignored with a warning rather than throwing: this is
67
70
  * the switch that logs a fleet back in, and failing to boot over a typo is worse
68
71
  * than not rotating.
69
72
  */
70
- export function resolveSessionEpoch(configured) {
71
- const raw = configured ?? env.text(SESSION_EPOCH_ENV) ?? undefined;
73
+ export function resolveSessionCutoff(configured) {
74
+ const raw = configured ?? env.text(SESSION_CUTOFF_ENV) ?? undefined;
72
75
  if (raw === undefined || raw === null || raw === "")
73
76
  return 0;
74
- return clampToPast(parseEpoch(raw));
75
- }
76
- /** Parse the accepted epoch spellings to ms, or `0` when unusable. */
77
- function parseEpoch(raw) {
78
- if (raw instanceof Date)
79
- return Number.isNaN(raw.getTime()) ? 0 : raw.getTime();
80
- const text = String(raw).trim();
81
- // A bare number is a timestamp, not a date string: `Date.parse("1785...")`
82
- // would read it as a YEAR. Values below ~1e11 are seconds (any millis
83
- // timestamp since 1973 is larger), which is what `date +%s` prints.
84
- if (/^\d+$/.test(text)) {
85
- const numeric = Number(text);
86
- return numeric < 1e11 ? numeric * 1000 : numeric;
87
- }
88
- const parsed = Date.parse(text);
89
- if (Number.isNaN(parsed)) {
90
- logger.warn(`ignoring unparseable ${env.name(SESSION_EPOCH_ENV)}`, { value: text });
77
+ const date = object.toDate(raw);
78
+ if (!date) {
79
+ logger.warn(`ignoring unparseable ${env.name(SESSION_CUTOFF_ENV)}`, { value: String(raw) });
91
80
  return 0;
92
81
  }
93
- return parsed;
82
+ return clampToPast(date.getTime());
94
83
  }
95
84
  /**
96
- * Hold the cutoff at "now", because a FUTURE epoch would refuse the sessions it
85
+ * Hold the cutoff at "now", because a FUTURE cutoff would refuse the sessions it
97
86
  * is about to mint as well as the old ones - an app nobody can sign in to, from a
98
87
  * mistyped year, with the fix hidden behind understanding this flag. Clamped, a
99
88
  * future date means what an operator setting it always meant: clear everything
100
89
  * outstanding, then carry on.
101
90
  */
102
- function clampToPast(epochMs) {
91
+ function clampToPast(cutoffMs) {
103
92
  const now = Date.now();
104
- if (epochMs <= now)
105
- return epochMs;
106
- logger.warn(`${env.name(SESSION_EPOCH_ENV)} is in the future - clamping to now`, {
107
- configured: new Date(epochMs).toISOString(),
93
+ if (cutoffMs <= now)
94
+ return cutoffMs;
95
+ logger.warn(`${env.name(SESSION_CUTOFF_ENV)} is in the future - clamping to now`, {
96
+ configured: new Date(cutoffMs).toISOString(),
108
97
  });
109
98
  return now;
110
99
  }
111
- /** The cache key for one epoch, so moving the epoch orphans every earlier key. */
112
- function cacheKey(epochMs) {
113
- return `${KEY_PREFIX}${epochMs}`;
100
+ /** The cache key for one cutoff, so moving the cutoff orphans every earlier key. */
101
+ function cacheKey(cutoffMs) {
102
+ return `${KEY_PREFIX}${cutoffMs}`;
114
103
  }
115
104
  function decode(stored) {
116
105
  return new Uint8Array(Buffer.from(stored.secret, "base64url"));
117
106
  }
118
107
  /**
119
- * Load the signing key for `epochMs` from the cache, minting and storing one when
120
- * absent. See the module docs for why the write is followed by a re-read.
108
+ * Load the signing key for `cutoffMs` from the cache, minting and storing one
109
+ * when absent. See the module docs for why the write is followed by a re-read.
121
110
  */
122
- async function loadFromCache(epochMs) {
111
+ async function loadFromCache(cutoffMs) {
123
112
  const cache = CacheManager.getInstanceSync();
124
- const key = cacheKey(epochMs);
113
+ const key = cacheKey(cutoffMs);
125
114
  const existing = await cache.get(key);
126
115
  if (existing?.secret) {
127
116
  logger.info("reusing cached signing key", { createdAt: existing.createdAt });
@@ -147,7 +136,7 @@ let pending;
147
136
  /**
148
137
  * The signing key for this gate, resolved once per process.
149
138
  *
150
- * Resolved ONCE, so `configuredEpoch` is honoured only on the first call - the
139
+ * Resolved ONCE, so `configuredCutoff` is honoured only on the first call - the
151
140
  * plugin's `setup()` passes its resolved value there, before any request can
152
141
  * reach the lazy path.
153
142
  *
@@ -157,19 +146,19 @@ let pending;
157
146
  * so losing it costs sessions, never admission - a caller still needs a code
158
147
  * delivered to an allow-listed address.
159
148
  */
160
- export function signingKey(configuredEpoch) {
149
+ export function signingKey(configuredCutoff) {
161
150
  pending ??= (async () => {
162
- const epochMs = resolveSessionEpoch(configuredEpoch);
151
+ const cutoffMs = resolveSessionCutoff(configuredCutoff);
163
152
  const configured = env.text(JWT_SECRET_ENV);
164
153
  if (configured) {
165
- return { key: new TextEncoder().encode(configured), epochMs };
154
+ return { key: new TextEncoder().encode(configured), cutoffMs };
166
155
  }
167
156
  try {
168
- return { key: await loadFromCache(epochMs), epochMs };
157
+ return { key: await loadFromCache(cutoffMs), cutoffMs };
169
158
  }
170
159
  catch (error) {
171
160
  logger.warn(`no ${env.name(JWT_SECRET_ENV)} and the cache is unavailable - using an ephemeral per-process key; sessions will not survive a restart`, { error });
172
- return { key: new Uint8Array(randomBytes(KEY_BYTES)), epochMs };
161
+ return { key: new Uint8Array(randomBytes(KEY_BYTES)), cutoffMs };
173
162
  }
174
163
  })();
175
164
  return pending;
@@ -178,4 +167,4 @@ export function signingKey(configuredEpoch) {
178
167
  export function resetSigningKey() {
179
168
  pending = undefined;
180
169
  }
181
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbmluZy1rZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2lnbmluZy1rZXkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBDRztBQUVILE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDMUMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDbEQsT0FBTyxFQUFFLGNBQWMsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLFVBQVUsQ0FBQztBQUU3RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLG9CQUFvQixDQUFDLENBQUM7QUFFaEQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZUFBZSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsQ0FBQztBQUVqRCxrRkFBa0Y7QUFDbEYsTUFBTSxVQUFVLEdBQUcsMEJBQTBCLENBQUM7QUFFOUMsa0ZBQWtGO0FBQ2xGLE1BQU0sU0FBUyxHQUFHLEVBQUUsQ0FBQztBQVVyQjs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxtQkFBbUIsQ0FBQyxVQUFtQztJQUNyRSxNQUFNLEdBQUcsR0FBRyxVQUFVLElBQUksR0FBRyxDQUFDLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLFNBQVMsQ0FBQztJQUNuRSxJQUFJLEdBQUcsS0FBSyxTQUFTLElBQUksR0FBRyxLQUFLLElBQUksSUFBSSxHQUFHLEtBQUssRUFBRTtRQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzlELE9BQU8sV0FBVyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0FBQ3RDLENBQUM7QUFFRCxzRUFBc0U7QUFDdEUsU0FBUyxVQUFVLENBQUMs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RyxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMseUdBQXlHLEVBQ3ZJLEVBQUUsS0FBSyxFQUFFLENBQ1YsQ0FBQztZQUNGLE9BQU8sRUFBRSxHQUFHLEVBQUUsSUFBSSxVQUFVLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLENBQUM7UUFDbEUsQ0FBQztJQUNILENBQUMsQ0FBQyxFQUFFLENBQUM7SUFDTCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQsK0VBQStFO0FBQy9FLE1BQU0sVUFBVSxlQUFlO0lBQzdCLE9BQU8sR0FBRyxTQUFTLENBQUM7QUFDdEIsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogVGhlIGdhdGUncyBIUzI1NiBzZXNzaW9uLXNpZ25pbmcga2V5LCBwZXJzaXN0ZWQgaW4gQXBwS2l0J3MgY2FjaGUuXG4gKlxuICogVGhlIGtleSBkZWNpZGVzIHdoZXRoZXIgYSBzZXNzaW9uIENPT0tJRSBzdGlsbCB2ZXJpZmllcy4gQmVmb3JlIHRoaXMgbW9kdWxlIHRoZVxuICogZmFsbGJhY2sgd2FzIGByYW5kb21CeXRlcygzMilgIHBlciBwcm9jZXNzLCBzbyBldmVyeSByZXN0YXJ0IGludmFsaWRhdGVkIGV2ZXJ5XG4gKiBvdXRzdGFuZGluZyBjb29raWUgYW5kIGVhY2ggc2lnbmVkLWluIHVzZXIgaGFkIHRvIHJlcXVlc3QgYSBuZXcgY29kZSAtIHBhaW5mdWxcbiAqIGZvciBhIHR1bm5lbCwgd2hpY2ggcmVzdGFydHMgd2hlbmV2ZXIgdGhlIGFwcCBpdCB3cmFwcyBkb2VzLiBOb3cgdGhlIGtleSBpc1xuICogc3RvcmVkIGluIHRoZSBjYWNoZSBBcHBLaXQgYWxyZWFkeSBjb25maWd1cmVkIChtZW1vcnksIG9yIExha2ViYXNlIHdoZW4gdGhlXG4gKiBob3N0IHdpcmVzIGEgcGVyc2lzdGVudCBgQ2FjaGVTdG9yYWdlYCksIHNvIHdpdGggcGVyc2lzdGVudCBzdG9yYWdlIGEgcmVzdGFydFxuICoga2VlcHMgc2Vzc2lvbnMgYWxpdmUgZm9yIHtAbGluayBLRVlfVFRMX1NFQ09ORFN9LlxuICpcbiAqIEFuIGV4cGxpY2l0bHkgY29uZmlndXJlZCBgVFVOTkVMX0FVVEhfSldUX1NFQ1JFVGAgc3RpbGwgd2lucyBvdXRyaWdodC4gVGhhdCBpc1xuICogdGhlIHJpZ2h0IGFuc3dlciBmb3IgYSBmbGVldDogYW4gb3BlcmF0b3ItaGVsZCBzZWNyZXQgbmVlZHMgbm8gc2hhcmVkIGNhY2hlIGFuZFxuICogbm8gY29udmVyZ2VuY2UsIGFuZCBpdCBzdXJ2aXZlcyBhIGNhY2hlIGZsdXNoLlxuICpcbiAqICMjIGdldCAvIGdlbmVyYXRlIC8gZ2V0XG4gKlxuICogVHdvIGluc3RhbmNlcyBib290aW5nIGF0IG9uY2UgYm90aCBtaXNzLCBzbyBib3RoIHdvdWxkIGdlbmVyYXRlIC0gYW5kIHRoZSBsb3NlclxuICogd291bGQgc2lnbiBjb29raWVzIHdpdGggYSBrZXkgdGhlIHdpbm5lciByZWplY3RzLiBSZXNvbHV0aW9uIGlzIGEgcmUtUkVBRCBhZnRlclxuICogdGhlIHdyaXRlOiB3aGF0ZXZlciB0aGUgY2FjaGUgaG9sZHMgYWZ0ZXJ3YXJkcyBpcyB0aGUga2V5IGV2ZXJ5b25lIGFkb3B0cywgc29cbiAqIHRoZSBpbnN0YW5jZXMgY29udmVyZ2Ugb24gT05FIHZhbHVlIGluc3RlYWQgb2YgdHJ1c3RpbmcgdGhlIG9uZSB0aGV5IG1pbnRlZC5cbiAqIChgc2V0YCBpcyBub3QgY29uZGl0aW9uYWwgaW4gdGhlIGNhY2hlIEFQSSAtIHRoZXJlIGlzIG5vIGBzZXRueGAgdG8gbGVhbiBvbiAtXG4gKiBhbmQgdGhpcyBydW5zIG9uY2UgcGVyIHByb2Nlc3MsIHNvIHRoZSBleHRyYSByb3VuZC10cmlwIGlzIGZyZWUuKVxuICpcbiAqIEEgcGF0aG9sb2dpY2FsIGludGVybGVhdmUgY2FuIHN0aWxsIGNvc3QgYSBrZXk6IGlmIEEgd3JpdGVzIGJldHdlZW4gQidzIHdyaXRlXG4gKiBhbmQgQidzIHJlLXJlYWQsIEIgYWRvcHRzIEEncyBrZXkgd2hpbGUgQSBhZG9wdHMgaXRzIG93bi4gVGhlIGNvc3QgaXMgYm91bmRlZCAtXG4gKiBhIGNvb2tpZSBtaW50ZWQgaW4gdGhhdCB3aW5kb3cgZmFpbHMgdG8gdmVyaWZ5IGFuZCB0aGUgaG9sZGVyIHNpZ25zIGluIGFnYWluIC1cbiAqIGFuZCBpdCBjYW5ub3QgcHJvZHVjZSBhIGtleSBvbmUgaW5zdGFuY2UgVFJVU1RTIGJ1dCBhbm90aGVyIHJlamVjdHMgZm9yIGxvbmdlclxuICogdGhhbiB0aGUgd2luZG93IGl0c2VsZi4gU2V0IGBUVU5ORUxfQVVUSF9KV1RfU0VDUkVUYCB0byByZW1vdmUgdGhlIHJhY2VcbiAqIGVudGlyZWx5LlxuICpcbiAqICMjIEZvcmNpbmcgZXZlcnkgc2Vzc2lvbiB0byBlbmRcbiAqXG4gKiB7QGxpbmsgcmVzb2x2ZVNlc3Npb25FcG9jaH0gcmVhZHMgYSBkYXRlIGZyb20gYFRVTk5FTF9BVVRIX1NFU1NJT05fRVBPQ0hgIChvclxuICogYC0tc2Vzc2lvbi1lcG9jaGApLCBhbmQgdGhhdCBkYXRlIGlzIHBhcnQgb2YgdGhlIGNhY2hlIEtFWS4gTW92aW5nIGl0IGZvcndhcmRcbiAqIG1ha2VzIGV2ZXJ5IHByaW9yIGtleSB1bnJlYWNoYWJsZSwgc28gZXZlcnkgY29va2llIHNpZ25lZCBhZ2FpbnN0IGl0IHN0b3BzXG4gKiB2ZXJpZnlpbmcgLSB0aGUgbG9nLWV2ZXJ5b25lLW91dCBzd2l0Y2gsIHdpdGhvdXQgaGF2aW5nIHRvIGZpbmQgYW5kIGZsdXNoIGFcbiAqIGNhY2hlIGVudHJ5LiBUaGUgZXBvY2ggaXMgYWxzbyBhc3NlcnRlZCBhZ2FpbnN0IGVhY2ggdG9rZW4ncyBgaWF0YCwgc28gYVxuICogY29va2llIHRoYXQgcHJlZGF0ZXMgaXQgaXMgcmVmdXNlZCBldmVuIGlmIGl0IHdhcyBzaWduZWQgd2l0aCB0aGUga2V5IHRoYXQgaXNcbiAqIHNvbWVob3cgc3RpbGwgY3VycmVudC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgcmFuZG9tQnl0ZXMgfSBmcm9tIFwibm9kZTpjcnlwdG9cIjtcbmltcG9ydCB7IENhY2hlTWFuYWdlciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGVudiwgbG9nIH0gZnJvbSBcIkBkYngtdG9vbHMvc2hhcmVkLWNvcmVcIjtcbmltcG9ydCB7IEpXVF9TRUNSRVRfRU5WLCBTRVNTSU9OX0VQT0NIX0VOViB9IGZyb20gXCIuL2Vudi50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwidHVubmVsOnNpZ25pbmcta2V5XCIpO1xuXG4vKipcbiAqIEhvdyBsb25nIGEgY2FjaGVkIHNpZ25pbmcga2V5IGxpdmVzOiAzMCBkYXlzLlxuICpcbiAqIFRoaXMgaXMgdGhlIGNlaWxpbmcgb24gaG93IGxvbmcgYSBzZXNzaW9uIGNvb2tpZSBjYW4gc3RheSB2YWxpZCBhY3Jvc3NcbiAqIHJlc3RhcnRzLCBzbyBpdCBpcyBkZWxpYmVyYXRlbHkgPj0gdGhlIGRlZmF1bHQgc2Vzc2lvbiBUVEwgLSBhIGtleSB0aGF0IGV4cGlyZWRcbiAqIGJlZm9yZSB0aGUgY29va2llcyBpdCBzaWduZWQgd291bGQgbG9nIGV2ZXJ5b25lIG91dCBmb3Igbm8gcmVhc29uLlxuICovXG5leHBvcnQgY29uc3QgS0VZX1RUTF9TRUNPTkRTID0gMzAgKiAyNCAqIDYwICogNjA7XG5cbi8qKiBDYWNoZS1rZXkgcHJlZml4IGZvciB0aGUgc2lnbmluZyBrZXksIG5hbWVzcGFjZWQgYXdheSBmcm9tIG90aGVyIGNhY2hlIHVzZS4gKi9cbmNvbnN0IEtFWV9QUkVGSVggPSBcInR1bm5lbDphdXRoOnNpZ25pbmcta2V5OlwiO1xuXG4vKiogQnl0ZXMgb2YgZW50cm9weSBpbiBhIGdlbmVyYXRlZCBrZXkgKDI1Ni1iaXQsIG1hdGNoaW5nIEhTMjU2J3MgaGFzaCB3aWR0aCkuICovXG5jb25zdCBLRVlfQllURVMgPSAzMjtcblxuLyoqIFdoYXQgdGhlIGNhY2hlIHN0b3JlczogdGhlIGtleSBwbHVzIHdoZW4gaXQgd2FzIG1pbnRlZCwgZm9yIG9ic2VydmFiaWxpdHkuICovXG5pbnRlcmZhY2UgU3RvcmVkS2V5IHtcbiAgLyoqIEJhc2U2NHVybCBvZiB0aGUgcmF3IGtleSBieXRlcy4gKi9cbiAgc2VjcmV0OiBzdHJpbmc7XG4gIC8qKiBXaGVuIHRoaXMga2V5IHdhcyBnZW5lcmF0ZWQsIElTTy04NjAxLiAqL1xuICBjcmVhdGVkQXQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBSZXNvbHZlIHRoZSBmb3JjZS1jbGVhciBlcG9jaCBhcyBlcG9jaCBNSUxMSVNFQ09ORFMsIG9yIGAwYCB3aGVuIHVuc2V0LlxuICpcbiAqIEFjY2VwdHMgYW55dGhpbmcgYERhdGVgIHBhcnNlcyAoYDIwMjYtMDgtMDJgLCBhbiBJU08gdGltZXN0YW1wKSBwbHVzIGEgYmFyZVxuICogZXBvY2gtc2Vjb25kcyAvIGVwb2NoLW1pbGxpcyBudW1iZXIsIHNvIGEgdmFsdWUgcGFzdGVkIGZyb20gYGRhdGUgKyVzYCB3b3Jrcy5cbiAqIEFuIFVOUEFSU0VBQkxFIHZhbHVlIGlzIGlnbm9yZWQgd2l0aCBhIHdhcm5pbmcgcmF0aGVyIHRoYW4gdGhyb3dpbmc6IHRoaXMgaXNcbiAqIHRoZSBzd2l0Y2ggdGhhdCBsb2dzIGEgZmxlZXQgYmFjayBpbiwgYW5kIGZhaWxpbmcgdG8gYm9vdCBvdmVyIGEgdHlwbyBpcyB3b3JzZVxuICogdGhhbiBub3Qgcm90YXRpbmcuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlU2Vzc2lvbkVwb2NoKGNvbmZpZ3VyZWQ/OiBzdHJpbmcgfCBudW1iZXIgfCBEYXRlKTogbnVtYmVyIHtcbiAgY29uc3QgcmF3ID0gY29uZmlndXJlZCA/PyBlbnYudGV4dChTRVNTSU9OX0VQT0NIX0VOVikgPz8gdW5kZWZpbmVkO1xuICBpZiAocmF3ID09PSB1bmRlZmluZWQgfHwgcmF3ID09PSBudWxsIHx8IHJhdyA9PT0gXCJcIikgcmV0dXJuIDA7XG4gIHJldHVybiBjbGFtcFRvUGFzdChwYXJzZUVwb2NoKHJhdykpO1xufVxuXG4vKiogUGFyc2UgdGhlIGFjY2VwdGVkIGVwb2NoIHNwZWxsaW5ncyB0byBtcywgb3IgYDBgIHdoZW4gdW51c2FibGUuICovXG5mdW5jdGlvbiBwYXJzZUVwb2NoKHJhdzogc3RyaW5nIHwgbnVtYmVyIHwgRGF0ZSk6IG51bWJlciB7XG4gIGlmIChyYXcgaW5zdGFuY2VvZiBEYXRlKSByZXR1cm4gTnVtYmVyLmlzTmFOKHJhdy5nZXRUaW1lKCkpID8gMCA6IHJhdy5nZXRUaW1lKCk7XG5cbiAgY29uc3QgdGV4dCA9IFN0cmluZyhyYXcpLnRyaW0oKTtcbiAgLy8gQSBiYXJlIG51bWJlciBpcyBhIHRpbWVzdGFtcCwgbm90IGEgZGF0ZSBzdHJpbmc6IGBEYXRlLnBhcnNlKFwiMTc4NS4uLlwiKWBcbiAgLy8gd291bGQgcmVhZCBpdCBhcyBhIFlFQVIuIFZhbHVlcyBiZWxvdyB+MWUxMSBhcmUgc2Vjb25kcyAoYW55IG1pbGxpc1xuICAvLyB0aW1lc3RhbXAgc2luY2UgMTk3MyBpcyBsYXJnZXIpLCB3aGljaCBpcyB3aGF0IGBkYXRlICslc2AgcHJpbnRzLlxuICBpZiAoL15cXGQrJC8udGVzdCh0ZXh0KSkge1xuICAgIGNvbnN0IG51bWVyaWMgPSBOdW1iZXIodGV4dCk7XG4gICAgcmV0dXJuIG51bWVyaWMgPCAxZTExID8gbnVtZXJpYyAqIDEwMDAgOiBudW1lcmljO1xuICB9XG4gIGNvbnN0IHBhcnNlZCA9IERhdGUucGFyc2UodGV4dCk7XG4gIGlmIChOdW1iZXIuaXNOYU4ocGFyc2VkKSkge1xuICAgIGxvZ2dlci53YXJuKGBpZ25vcmluZyB1bnBhcnNlYWJsZSAke2Vudi5uYW1lKFNFU1NJT05fRVBPQ0hfRU5WKX1gLCB7IHZhbHVlOiB0ZXh0IH0pO1xuICAgIHJldHVybiAwO1xuICB9XG4gIHJldHVybiBwYXJzZWQ7XG59XG5cbi8qKlxuICogSG9sZCB0aGUgY3V0b2ZmIGF0IFwibm93XCIsIGJlY2F1c2UgYSBGVVRVUkUgZXBvY2ggd291bGQgcmVmdXNlIHRoZSBzZXNzaW9ucyBpdFxuICogaXMgYWJvdXQgdG8gbWludCBhcyB3ZWxsIGFzIHRoZSBvbGQgb25lcyAtIGFuIGFwcCBub2JvZHkgY2FuIHNpZ24gaW4gdG8sIGZyb20gYVxuICogbWlzdHlwZWQgeWVhciwgd2l0aCB0aGUgZml4IGhpZGRlbiBiZWhpbmQgdW5kZXJzdGFuZGluZyB0aGlzIGZsYWcuIENsYW1wZWQsIGFcbiAqIGZ1dHVyZSBkYXRlIG1lYW5zIHdoYXQgYW4gb3BlcmF0b3Igc2V0dGluZyBpdCBhbHdheXMgbWVhbnQ6IGNsZWFyIGV2ZXJ5dGhpbmdcbiAqIG91dHN0YW5kaW5nLCB0aGVuIGNhcnJ5IG9uLlxuICovXG5mdW5jdGlvbiBjbGFtcFRvUGFzdChlcG9jaE1zOiBudW1iZXIpOiBudW1iZXIge1xuICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICBpZiAoZXBvY2hNcyA8PSBub3cpIHJldHVybiBlcG9jaE1zO1xuICBsb2dnZXIud2FybihgJHtlbnYubmFtZShTRVNTSU9OX0VQT0NIX0VOVil9IGlzIGluIHRoZSBmdXR1cmUgLSBjbGFtcGluZyB0byBub3dgLCB7XG4gICAgY29uZmlndXJlZDogbmV3IERhdGUoZXBvY2hNcykudG9JU09TdHJpbmcoKSxcbiAgfSk7XG4gIHJldHVybiBub3c7XG59XG5cbi8qKiBUaGUgY2FjaGUga2V5IGZvciBvbmUgZXBvY2gsIHNvIG1vdmluZyB0aGUgZXBvY2ggb3JwaGFucyBldmVyeSBlYXJsaWVyIGtleS4gKi9cbmZ1bmN0aW9uIGNhY2hlS2V5KGVwb2NoTXM6IG51bWJlcik6IHN0cmluZyB7XG4gIHJldHVybiBgJHtLRVlfUFJFRklYfSR7ZXBvY2hNc31gO1xufVxuXG5mdW5jdGlvbiBkZWNvZGUoc3RvcmVkOiBTdG9yZWRLZXkpOiBVaW50OEFycmF5IHtcbiAgcmV0dXJuIG5ldyBVaW50OEFycmF5KEJ1ZmZlci5mcm9tKHN0b3JlZC5zZWNyZXQsIFwiYmFzZTY0dXJsXCIpKTtcbn1cblxuLyoqXG4gKiBMb2FkIHRoZSBzaWduaW5nIGtleSBmb3IgYGVwb2NoTXNgIGZyb20gdGhlIGNhY2hlLCBtaW50aW5nIGFuZCBzdG9yaW5nIG9uZSB3aGVuXG4gKiBhYnNlbnQuIFNlZSB0aGUgbW9kdWxlIGRvY3MgZm9yIHdoeSB0aGUgd3JpdGUgaXMgZm9sbG93ZWQgYnkgYSByZS1yZWFkLlxuICovXG5hc3luYyBmdW5jdGlvbiBsb2FkRnJvbUNhY2hlKGVwb2NoTXM6IG51bWJlcik6IFByb21pc2U8VWludDhBcnJheT4ge1xuICBjb25zdCBjYWNoZSA9IENhY2hlTWFuYWdlci5nZXRJbnN0YW5jZVN5bmMoKTtcbiAgY29uc3Qga2V5ID0gY2FjaGVLZXkoZXBvY2hNcyk7XG5cbiAgY29uc3QgZXhpc3RpbmcgPSBhd2FpdCBjYWNoZS5nZXQ8U3RvcmVkS2V5PihrZXkpO1xuICBpZiAoZXhpc3Rpbmc/LnNlY3JldCkge1xuICAgIGxvZ2dlci5pbmZvKFwicmV1c2luZyBjYWNoZWQgc2lnbmluZyBrZXlcIiwgeyBjcmVhdGVkQXQ6IGV4aXN0aW5nLmNyZWF0ZWRBdCB9KTtcbiAgICByZXR1cm4gZGVjb2RlKGV4aXN0aW5nKTtcbiAgfVxuXG4gIGNvbnN0IG1pbnRlZDogU3RvcmVkS2V5ID0ge1xuICAgIHNlY3JldDogQnVmZmVyLmZyb20ocmFuZG9tQnl0ZXMoS0VZX0JZVEVTKSkudG9TdHJpbmcoXCJiYXNlNjR1cmxcIiksXG4gICAgY3JlYXRlZEF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gIH07XG4gIGF3YWl0IGNhY2hlLnNldChrZXksIG1pbnRlZCwgeyB0dGw6IEtFWV9UVExfU0VDT05EUyB9KTtcblxuICAvLyBSZS1yZWFkIHJhdGhlciB0aGFuIHRydXN0aW5nIGBtaW50ZWRgOiBhbm90aGVyIGluc3RhbmNlIHRoYXQgcmFjZWQgdGhpcyBib290XG4gIC8vIG1heSBoYXZlIHdyaXR0ZW4gZmlyc3QsIGFuZCBhZG9wdGluZyB3aGF0ZXZlciBpcyBzdG9yZWQgaXMgd2hhdCBtYWtlcyB0aGUgdHdvXG4gIC8vIGNvbnZlcmdlIG9uIG9uZSBrZXkuXG4gIGNvbnN0IHNldHRsZWQgPSAoYXdhaXQgY2FjaGUuZ2V0PFN0b3JlZEtleT4oa2V5KSkgPz8gbWludGVkO1xuICBsb2dnZXIuaW5mbyhcInN0b3JlZCBhIG5ldyBzaWduaW5nIGtleVwiLCB7XG4gICAgY3JlYXRlZEF0OiBzZXR0bGVkLmNyZWF0ZWRBdCxcbiAgICB0dGxTZWNvbmRzOiBLRVlfVFRMX1NFQ09ORFMsXG4gICAgYWRvcHRlZDogc2V0dGxlZC5jcmVhdGVkQXQgPT09IG1pbnRlZC5jcmVhdGVkQXQgPyBcIm93blwiIDogXCJjb25jdXJyZW50LWluc3RhbmNlXCIsXG4gIH0pO1xuICByZXR1cm4gZGVjb2RlKHNldHRsZWQpO1xufVxuXG4vKiogQSByZXNvbHZlZCBrZXkgcGx1cyB0aGUgZXBvY2ggaXQgaXMgc2NvcGVkIHRvLiAqL1xuZXhwb3J0IGludGVyZmFjZSBTaWduaW5nS2V5IHtcbiAga2V5OiBVaW50OEFycmF5O1xuICAvKiogRm9yY2UtY2xlYXIgZXBvY2ggaW4gbXM7IGAwYCB3aGVuIHVuc2V0LiBUb2tlbnMgb2xkZXIgdGhhbiB0aGlzIGFyZSByZWZ1c2VkLiAqL1xuICBlcG9jaE1zOiBudW1iZXI7XG59XG5cbmxldCBwZW5kaW5nOiBQcm9taXNlPFNpZ25pbmdLZXk+IHwgdW5kZWZpbmVkO1xuXG4vKipcbiAqIFRoZSBzaWduaW5nIGtleSBmb3IgdGhpcyBnYXRlLCByZXNvbHZlZCBvbmNlIHBlciBwcm9jZXNzLlxuICpcbiAqIFJlc29sdmVkIE9OQ0UsIHNvIGBjb25maWd1cmVkRXBvY2hgIGlzIGhvbm91cmVkIG9ubHkgb24gdGhlIGZpcnN0IGNhbGwgLSB0aGVcbiAqIHBsdWdpbidzIGBzZXR1cCgpYCBwYXNzZXMgaXRzIHJlc29sdmVkIHZhbHVlIHRoZXJlLCBiZWZvcmUgYW55IHJlcXVlc3QgY2FuXG4gKiByZWFjaCB0aGUgbGF6eSBwYXRoLlxuICpcbiAqIGBUVU5ORUxfQVVUSF9KV1RfU0VDUkVUYCB3aGVuIHNldCwgZWxzZSB0aGUgY2FjaGUtYmFja2VkIGtleS4gQSBjYWNoZSB0aGF0IGlzXG4gKiB1bmF2YWlsYWJsZSBkZWdyYWRlcyB0byBhbiBlcGhlbWVyYWwgcGVyLXByb2Nlc3Mga2V5ICh0aGUgcHJldmlvdXMgYmVoYXZpb3VyKVxuICogcmF0aGVyIHRoYW4gcmVmdXNpbmcgdG8gc2lnbjogdGhlIGtleSBvbmx5IHZhbGlkYXRlcyBhbiBBTFJFQURZLWlzc3VlZCBzZXNzaW9uLFxuICogc28gbG9zaW5nIGl0IGNvc3RzIHNlc3Npb25zLCBuZXZlciBhZG1pc3Npb24gLSBhIGNhbGxlciBzdGlsbCBuZWVkcyBhIGNvZGVcbiAqIGRlbGl2ZXJlZCB0byBhbiBhbGxvdy1saXN0ZWQgYWRkcmVzcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNpZ25pbmdLZXkoY29uZmlndXJlZEVwb2NoPzogc3RyaW5nIHwgbnVtYmVyIHwgRGF0ZSk6IFByb21pc2U8U2lnbmluZ0tleT4ge1xuICBwZW5kaW5nID8/PSAoYXN5bmMgKCkgPT4ge1xuICAgIGNvbnN0IGVwb2NoTXMgPSByZXNvbHZlU2Vzc2lvbkVwb2NoKGNvbmZpZ3VyZWRFcG9jaCk7XG4gICAgY29uc3QgY29uZmlndXJlZCA9IGVudi50ZXh0KEpXVF9TRUNSRVRfRU5WKTtcbiAgICBpZiAoY29uZmlndXJlZCkge1xuICAgICAgcmV0dXJuIHsga2V5OiBuZXcgVGV4dEVuY29kZXIoKS5lbmNvZGUoY29uZmlndXJlZCksIGVwb2NoTXMgfTtcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiB7IGtleTogYXdhaXQgbG9hZEZyb21DYWNoZShlcG9jaE1zKSwgZXBvY2hNcyB9O1xuICAgIH0gY2F0Y2ggKGVycm9yKSB7XG4gICAgICBsb2dnZXIud2FybihcbiAgICAgICAgYG5vICR7ZW52Lm5hbWUoSldUX1NFQ1JFVF9FTlYpfSBhbmQgdGhlIGNhY2hlIGlzIHVuYXZhaWxhYmxlIC0gdXNpbmcgYW4gZXBoZW1lcmFsIHBlci1wcm9jZXNzIGtleTsgc2Vzc2lvbnMgd2lsbCBub3Qgc3Vydml2ZSBhIHJlc3RhcnRgLFxuICAgICAgICB7IGVycm9yIH0sXG4gICAgICApO1xuICAgICAgcmV0dXJuIHsga2V5OiBuZXcgVWludDhBcnJheShyYW5kb21CeXRlcyhLRVlfQllURVMpKSwgZXBvY2hNcyB9O1xuICAgIH1cbiAgfSkoKTtcbiAgcmV0dXJuIHBlbmRpbmc7XG59XG5cbi8qKiBSZXNldCB0aGUgcGVyLXByb2Nlc3Mga2V5ICh0ZXN0cywgb3IgYWZ0ZXIgY2hhbmdpbmcgdGhlIGVudiBpbi1wcm9jZXNzKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNldFNpZ25pbmdLZXkoKTogdm9pZCB7XG4gIHBlbmRpbmcgPSB1bmRlZmluZWQ7XG59XG4iXX0=
170
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2lnbmluZy1rZXkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2lnbmluZy1rZXkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQTBDRztBQUVILE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDMUMsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLE1BQU0sRUFBRSxNQUFNLHdCQUF3QixDQUFDO0FBQzFELE9BQU8sRUFBRSxjQUFjLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxVQUFVLENBQUM7QUFFOUQsTUFBTSxNQUFNLEdBQUcsR0FBRyxDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO0FBRWhEOzs7Ozs7R0FNRztBQUNILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLENBQUM7QUFFakQsa0ZBQWtGO0FBQ2xGLE1BQU0sVUFBVSxHQUFHLDBCQUEwQixDQUFDO0FBRTlDLGtGQUFrRjtBQUNsRixNQUFNLFNBQVMsR0FBRyxFQUFFLENBQUM7QUFVckI7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFNLFVBQVUsb0JBQW9CLENBQUMsVUFBbUM7SUFDdEUsTUFBTSxHQUFHLEdBQUcsVUFBVSxJQUFJLEdBQUcsQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxTQUFTLENBQUM7SUFDcEUsSUFBSSxHQUFHLEtBQUssU0FBUyxJQUFJLEdBQUcsS0FBSyxJQUFJLElBQUksR0FBRyxLQUFLLEVBQUU7UUFBRSxPQUFPLENBQUMsQ0FBQztJQUU5RCxNQUFNLElBQUksR0FBRyxNQUFNLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO0lBQ2hDLElBQUksQ0FBQyxJQUFJLEVBQUUs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LE9BQU8sS0FBSyxDQUFDLEtBQUssSUFBSSxFQUFFO1FBQ3RCLE1BQU0sUUFBUSxHQUFHLG9CQUFvQixDQUFDLGdCQUFnQixDQUFDLENBQUM7UUFDeEQsTUFBTSxVQUFVLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsQ0FBQztRQUM1QyxJQUFJLFVBQVUsRUFBRSxDQUFDO1lBQ2YsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLFdBQVcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQztRQUNqRSxDQUFDO1FBQ0QsSUFBSSxDQUFDO1lBQ0gsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGFBQWEsQ0FBQyxRQUFRLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQztRQUMxRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE1BQU0sQ0FBQyxJQUFJLENBQ1QsTUFBTSxHQUFHLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyx5R0FBeUcsRUFDdkksRUFBRSxLQUFLLEVBQUUsQ0FDVixDQUFDO1lBQ0YsT0FBTyxFQUFFLEdBQUcsRUFBRSxJQUFJLFVBQVUsQ0FBQyxXQUFXLENBQUMsU0FBUyxDQUFDLENBQUMsRUFBRSxRQUFRLEVBQUUsQ0FBQztRQUNuRSxDQUFDO0lBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztJQUNMLE9BQU8sT0FBTyxDQUFDO0FBQ2pCLENBQUM7QUFFRCwrRUFBK0U7QUFDL0UsTUFBTSxVQUFVLGVBQWU7SUFDN0IsT0FBTyxHQUFHLFNBQVMsQ0FBQztBQUN0QixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUaGUgZ2F0ZSdzIEhTMjU2IHNlc3Npb24tc2lnbmluZyBrZXksIHBlcnNpc3RlZCBpbiBBcHBLaXQncyBjYWNoZS5cbiAqXG4gKiBUaGUga2V5IGRlY2lkZXMgd2hldGhlciBhIHNlc3Npb24gQ09PS0lFIHN0aWxsIHZlcmlmaWVzLiBCZWZvcmUgdGhpcyBtb2R1bGUgdGhlXG4gKiBmYWxsYmFjayB3YXMgYHJhbmRvbUJ5dGVzKDMyKWAgcGVyIHByb2Nlc3MsIHNvIGV2ZXJ5IHJlc3RhcnQgaW52YWxpZGF0ZWQgZXZlcnlcbiAqIG91dHN0YW5kaW5nIGNvb2tpZSBhbmQgZWFjaCBzaWduZWQtaW4gdXNlciBoYWQgdG8gcmVxdWVzdCBhIG5ldyBjb2RlIC0gcGFpbmZ1bFxuICogZm9yIGEgdHVubmVsLCB3aGljaCByZXN0YXJ0cyB3aGVuZXZlciB0aGUgYXBwIGl0IHdyYXBzIGRvZXMuIE5vdyB0aGUga2V5IGlzXG4gKiBzdG9yZWQgaW4gdGhlIGNhY2hlIEFwcEtpdCBhbHJlYWR5IGNvbmZpZ3VyZWQgKG1lbW9yeSwgb3IgTGFrZWJhc2Ugd2hlbiB0aGVcbiAqIGhvc3Qgd2lyZXMgYSBwZXJzaXN0ZW50IGBDYWNoZVN0b3JhZ2VgKSwgc28gd2l0aCBwZXJzaXN0ZW50IHN0b3JhZ2UgYSByZXN0YXJ0XG4gKiBrZWVwcyBzZXNzaW9ucyBhbGl2ZSBmb3Ige0BsaW5rIEtFWV9UVExfU0VDT05EU30uXG4gKlxuICogQW4gZXhwbGljaXRseSBjb25maWd1cmVkIGBUVU5ORUxfQVVUSF9KV1RfU0VDUkVUYCBzdGlsbCB3aW5zIG91dHJpZ2h0LiBUaGF0IGlzXG4gKiB0aGUgcmlnaHQgYW5zd2VyIGZvciBhIGZsZWV0OiBhbiBvcGVyYXRvci1oZWxkIHNlY3JldCBuZWVkcyBubyBzaGFyZWQgY2FjaGUgYW5kXG4gKiBubyBjb252ZXJnZW5jZSwgYW5kIGl0IHN1cnZpdmVzIGEgY2FjaGUgZmx1c2guXG4gKlxuICogIyMgZ2V0IC8gZ2VuZXJhdGUgLyBnZXRcbiAqXG4gKiBUd28gaW5zdGFuY2VzIGJvb3RpbmcgYXQgb25jZSBib3RoIG1pc3MsIHNvIGJvdGggd291bGQgZ2VuZXJhdGUgLSBhbmQgdGhlIGxvc2VyXG4gKiB3b3VsZCBzaWduIGNvb2tpZXMgd2l0aCBhIGtleSB0aGUgd2lubmVyIHJlamVjdHMuIFJlc29sdXRpb24gaXMgYSByZS1SRUFEIGFmdGVyXG4gKiB0aGUgd3JpdGU6IHdoYXRldmVyIHRoZSBjYWNoZSBob2xkcyBhZnRlcndhcmRzIGlzIHRoZSBrZXkgZXZlcnlvbmUgYWRvcHRzLCBzb1xuICogdGhlIGluc3RhbmNlcyBjb252ZXJnZSBvbiBPTkUgdmFsdWUgaW5zdGVhZCBvZiB0cnVzdGluZyB0aGUgb25lIHRoZXkgbWludGVkLlxuICogKGBzZXRgIGlzIG5vdCBjb25kaXRpb25hbCBpbiB0aGUgY2FjaGUgQVBJIC0gdGhlcmUgaXMgbm8gYHNldG54YCB0byBsZWFuIG9uIC1cbiAqIGFuZCB0aGlzIHJ1bnMgb25jZSBwZXIgcHJvY2Vzcywgc28gdGhlIGV4dHJhIHJvdW5kLXRyaXAgaXMgZnJlZS4pXG4gKlxuICogQSBwYXRob2xvZ2ljYWwgaW50ZXJsZWF2ZSBjYW4gc3RpbGwgY29zdCBhIGtleTogaWYgQSB3cml0ZXMgYmV0d2VlbiBCJ3Mgd3JpdGVcbiAqIGFuZCBCJ3MgcmUtcmVhZCwgQiBhZG9wdHMgQSdzIGtleSB3aGlsZSBBIGFkb3B0cyBpdHMgb3duLiBUaGUgY29zdCBpcyBib3VuZGVkIC1cbiAqIGEgY29va2llIG1pbnRlZCBpbiB0aGF0IHdpbmRvdyBmYWlscyB0byB2ZXJpZnkgYW5kIHRoZSBob2xkZXIgc2lnbnMgaW4gYWdhaW4gLVxuICogYW5kIGl0IGNhbm5vdCBwcm9kdWNlIGEga2V5IG9uZSBpbnN0YW5jZSBUUlVTVFMgYnV0IGFub3RoZXIgcmVqZWN0cyBmb3IgbG9uZ2VyXG4gKiB0aGFuIHRoZSB3aW5kb3cgaXRzZWxmLiBTZXQgYFRVTk5FTF9BVVRIX0pXVF9TRUNSRVRgIHRvIHJlbW92ZSB0aGUgcmFjZVxuICogZW50aXJlbHkuXG4gKlxuICogIyMgRm9yY2luZyBldmVyeSBzZXNzaW9uIHRvIGVuZFxuICpcbiAqIHtAbGluayByZXNvbHZlU2Vzc2lvbkN1dG9mZn0gcmVhZHMgYSBkYXRlIGZyb20gYFRVTk5FTF9BVVRIX1NFU1NJT05fQ1VUT0ZGYCAob3JcbiAqIGAtLXNlc3Npb24tY3V0b2ZmYCksIGFuZCB0aGF0IGRhdGUgaXMgcGFydCBvZiB0aGUgY2FjaGUgS0VZLiBNb3ZpbmcgaXQgZm9yd2FyZFxuICogbWFrZXMgZXZlcnkgcHJpb3Iga2V5IHVucmVhY2hhYmxlLCBzbyBldmVyeSBjb29raWUgc2lnbmVkIGFnYWluc3QgaXQgc3RvcHNcbiAqIHZlcmlmeWluZyAtIHRoZSBsb2ctZXZlcnlvbmUtb3V0IHN3aXRjaCwgd2l0aG91dCBoYXZpbmcgdG8gZmluZCBhbmQgZmx1c2ggYVxuICogY2FjaGUgZW50cnkuIFRoZSBjdXRvZmYgaXMgYWxzbyBhc3NlcnRlZCBhZ2FpbnN0IGVhY2ggdG9rZW4ncyBgaWF0YCwgc28gYVxuICogY29va2llIHRoYXQgcHJlZGF0ZXMgaXQgaXMgcmVmdXNlZCBldmVuIGlmIGl0IHdhcyBzaWduZWQgd2l0aCB0aGUga2V5IHRoYXQgaXNcbiAqIHNvbWVob3cgc3RpbGwgY3VycmVudC5cbiAqXG4gKiBAbW9kdWxlXG4gKi9cblxuaW1wb3J0IHsgcmFuZG9tQnl0ZXMgfSBmcm9tIFwibm9kZTpjcnlwdG9cIjtcbmltcG9ydCB7IENhY2hlTWFuYWdlciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbmltcG9ydCB7IGVudiwgbG9nLCBvYmplY3QgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuaW1wb3J0IHsgSldUX1NFQ1JFVF9FTlYsIFNFU1NJT05fQ1VUT0ZGX0VOViB9IGZyb20gXCIuL2Vudi50c1wiO1xuXG5jb25zdCBsb2dnZXIgPSBsb2cubG9nZ2VyKFwidHVubmVsOnNpZ25pbmcta2V5XCIpO1xuXG4vKipcbiAqIEhvdyBsb25nIGEgY2FjaGVkIHNpZ25pbmcga2V5IGxpdmVzOiAzMCBkYXlzLlxuICpcbiAqIFRoaXMgaXMgdGhlIGNlaWxpbmcgb24gaG93IGxvbmcgYSBzZXNzaW9uIGNvb2tpZSBjYW4gc3RheSB2YWxpZCBhY3Jvc3NcbiAqIHJlc3RhcnRzLCBzbyBpdCBpcyBkZWxpYmVyYXRlbHkgPj0gdGhlIGRlZmF1bHQgc2Vzc2lvbiBUVEwgLSBhIGtleSB0aGF0IGV4cGlyZWRcbiAqIGJlZm9yZSB0aGUgY29va2llcyBpdCBzaWduZWQgd291bGQgbG9nIGV2ZXJ5b25lIG91dCBmb3Igbm8gcmVhc29uLlxuICovXG5leHBvcnQgY29uc3QgS0VZX1RUTF9TRUNPTkRTID0gMzAgKiAyNCAqIDYwICogNjA7XG5cbi8qKiBDYWNoZS1rZXkgcHJlZml4IGZvciB0aGUgc2lnbmluZyBrZXksIG5hbWVzcGFjZWQgYXdheSBmcm9tIG90aGVyIGNhY2hlIHVzZS4gKi9cbmNvbnN0IEtFWV9QUkVGSVggPSBcInR1bm5lbDphdXRoOnNpZ25pbmcta2V5OlwiO1xuXG4vKiogQnl0ZXMgb2YgZW50cm9weSBpbiBhIGdlbmVyYXRlZCBrZXkgKDI1Ni1iaXQsIG1hdGNoaW5nIEhTMjU2J3MgaGFzaCB3aWR0aCkuICovXG5jb25zdCBLRVlfQllURVMgPSAzMjtcblxuLyoqIFdoYXQgdGhlIGNhY2hlIHN0b3JlczogdGhlIGtleSBwbHVzIHdoZW4gaXQgd2FzIG1pbnRlZCwgZm9yIG9ic2VydmFiaWxpdHkuICovXG5pbnRlcmZhY2UgU3RvcmVkS2V5IHtcbiAgLyoqIEJhc2U2NHVybCBvZiB0aGUgcmF3IGtleSBieXRlcy4gKi9cbiAgc2VjcmV0OiBzdHJpbmc7XG4gIC8qKiBXaGVuIHRoaXMga2V5IHdhcyBnZW5lcmF0ZWQsIElTTy04NjAxLiAqL1xuICBjcmVhdGVkQXQ6IHN0cmluZztcbn1cblxuLyoqXG4gKiBSZXNvbHZlIHRoZSBmb3JjZS1jbGVhciBjdXRvZmYgYXMgZXBvY2ggTUlMTElTRUNPTkRTLCBvciBgMGAgd2hlbiB1bnNldC5cbiAqXG4gKiBBY2NlcHRzIHdoYXRldmVyIGBvYmplY3QudG9EYXRlYCBhY2NlcHRzIC0gYSBgRGF0ZWAsIGAyMDI2LTA4LTAyYCwgYW4gSVNPXG4gKiBpbnN0YW50LCBlcG9jaCBzZWNvbmRzIG9yIG1pbGxpcyBmcm9tIGBkYXRlICslc2AsIG9yIGEgcmVsYXRpdmUgZHVyYXRpb25cbiAqIChgLTMwZGAsIGA3IGRheXMgYWdvYCksIHdoaWNoIGlzIHRoZSBzcGVsbGluZyBhbiBvcGVyYXRvciByZWFjaGluZyBmb3IgdGhpc1xuICogdXN1YWxseSB3YW50czogc2lnbiBvdXQgZXZlcnlvbmUgd2hvIHNpZ25lZCBpbiBtb3JlIHRoYW4gYSBtb250aCBhZ28uXG4gKlxuICogQW4gVU5QQVJTRUFCTEUgdmFsdWUgaXMgaWdub3JlZCB3aXRoIGEgd2FybmluZyByYXRoZXIgdGhhbiB0aHJvd2luZzogdGhpcyBpc1xuICogdGhlIHN3aXRjaCB0aGF0IGxvZ3MgYSBmbGVldCBiYWNrIGluLCBhbmQgZmFpbGluZyB0byBib290IG92ZXIgYSB0eXBvIGlzIHdvcnNlXG4gKiB0aGFuIG5vdCByb3RhdGluZy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVTZXNzaW9uQ3V0b2ZmKGNvbmZpZ3VyZWQ/OiBzdHJpbmcgfCBudW1iZXIgfCBEYXRlKTogbnVtYmVyIHtcbiAgY29uc3QgcmF3ID0gY29uZmlndXJlZCA/PyBlbnYudGV4dChTRVNTSU9OX0NVVE9GRl9FTlYpID8/IHVuZGVmaW5lZDtcbiAgaWYgKHJhdyA9PT0gdW5kZWZpbmVkIHx8IHJhdyA9PT0gbnVsbCB8fCByYXcgPT09IFwiXCIpIHJldHVybiAwO1xuXG4gIGNvbnN0IGRhdGUgPSBvYmplY3QudG9EYXRlKHJhdyk7XG4gIGlmICghZGF0ZSkge1xuICAgIGxvZ2dlci53YXJuKGBpZ25vcmluZyB1bnBhcnNlYWJsZSAke2Vudi5uYW1lKFNFU1NJT05fQ1VUT0ZGX0VOVil9YCwgeyB2YWx1ZTogU3RyaW5nKHJhdykgfSk7XG4gICAgcmV0dXJuIDA7XG4gIH1cbiAgcmV0dXJuIGNsYW1wVG9QYXN0KGRhdGUuZ2V0VGltZSgpKTtcbn1cblxuLyoqXG4gKiBIb2xkIHRoZSBjdXRvZmYgYXQgXCJub3dcIiwgYmVjYXVzZSBhIEZVVFVSRSBjdXRvZmYgd291bGQgcmVmdXNlIHRoZSBzZXNzaW9ucyBpdFxuICogaXMgYWJvdXQgdG8gbWludCBhcyB3ZWxsIGFzIHRoZSBvbGQgb25lcyAtIGFuIGFwcCBub2JvZHkgY2FuIHNpZ24gaW4gdG8sIGZyb20gYVxuICogbWlzdHlwZWQgeWVhciwgd2l0aCB0aGUgZml4IGhpZGRlbiBiZWhpbmQgdW5kZXJzdGFuZGluZyB0aGlzIGZsYWcuIENsYW1wZWQsIGFcbiAqIGZ1dHVyZSBkYXRlIG1lYW5zIHdoYXQgYW4gb3BlcmF0b3Igc2V0dGluZyBpdCBhbHdheXMgbWVhbnQ6IGNsZWFyIGV2ZXJ5dGhpbmdcbiAqIG91dHN0YW5kaW5nLCB0aGVuIGNhcnJ5IG9uLlxuICovXG5mdW5jdGlvbiBjbGFtcFRvUGFzdChjdXRvZmZNczogbnVtYmVyKTogbnVtYmVyIHtcbiAgY29uc3Qgbm93ID0gRGF0ZS5ub3coKTtcbiAgaWYgKGN1dG9mZk1zIDw9IG5vdykgcmV0dXJuIGN1dG9mZk1zO1xuICBsb2dnZXIud2FybihgJHtlbnYubmFtZShTRVNTSU9OX0NVVE9GRl9FTlYpfSBpcyBpbiB0aGUgZnV0dXJlIC0gY2xhbXBpbmcgdG8gbm93YCwge1xuICAgIGNvbmZpZ3VyZWQ6IG5ldyBEYXRlKGN1dG9mZk1zKS50b0lTT1N0cmluZygpLFxuICB9KTtcbiAgcmV0dXJuIG5vdztcbn1cblxuLyoqIFRoZSBjYWNoZSBrZXkgZm9yIG9uZSBjdXRvZmYsIHNvIG1vdmluZyB0aGUgY3V0b2ZmIG9ycGhhbnMgZXZlcnkgZWFybGllciBrZXkuICovXG5mdW5jdGlvbiBjYWNoZUtleShjdXRvZmZNczogbnVtYmVyKTogc3RyaW5nIHtcbiAgcmV0dXJuIGAke0tFWV9QUkVGSVh9JHtjdXRvZmZNc31gO1xufVxuXG5mdW5jdGlvbiBkZWNvZGUoc3RvcmVkOiBTdG9yZWRLZXkpOiBVaW50OEFycmF5IHtcbiAgcmV0dXJuIG5ldyBVaW50OEFycmF5KEJ1ZmZlci5mcm9tKHN0b3JlZC5zZWNyZXQsIFwiYmFzZTY0dXJsXCIpKTtcbn1cblxuLyoqXG4gKiBMb2FkIHRoZSBzaWduaW5nIGtleSBmb3IgYGN1dG9mZk1zYCBmcm9tIHRoZSBjYWNoZSwgbWludGluZyBhbmQgc3RvcmluZyBvbmVcbiAqIHdoZW4gYWJzZW50LiBTZWUgdGhlIG1vZHVsZSBkb2NzIGZvciB3aHkgdGhlIHdyaXRlIGlzIGZvbGxvd2VkIGJ5IGEgcmUtcmVhZC5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gbG9hZEZyb21DYWNoZShjdXRvZmZNczogbnVtYmVyKTogUHJvbWlzZTxVaW50OEFycmF5PiB7XG4gIGNvbnN0IGNhY2hlID0gQ2FjaGVNYW5hZ2VyLmdldEluc3RhbmNlU3luYygpO1xuICBjb25zdCBrZXkgPSBjYWNoZUtleShjdXRvZmZNcyk7XG5cbiAgY29uc3QgZXhpc3RpbmcgPSBhd2FpdCBjYWNoZS5nZXQ8U3RvcmVkS2V5PihrZXkpO1xuICBpZiAoZXhpc3Rpbmc/LnNlY3JldCkge1xuICAgIGxvZ2dlci5pbmZvKFwicmV1c2luZyBjYWNoZWQgc2lnbmluZyBrZXlcIiwgeyBjcmVhdGVkQXQ6IGV4aXN0aW5nLmNyZWF0ZWRBdCB9KTtcbiAgICByZXR1cm4gZGVjb2RlKGV4aXN0aW5nKTtcbiAgfVxuXG4gIGNvbnN0IG1pbnRlZDogU3RvcmVkS2V5ID0ge1xuICAgIHNlY3JldDogQnVmZmVyLmZyb20ocmFuZG9tQnl0ZXMoS0VZX0JZVEVTKSkudG9TdHJpbmcoXCJiYXNlNjR1cmxcIiksXG4gICAgY3JlYXRlZEF0OiBuZXcgRGF0ZSgpLnRvSVNPU3RyaW5nKCksXG4gIH07XG4gIGF3YWl0IGNhY2hlLnNldChrZXksIG1pbnRlZCwgeyB0dGw6IEtFWV9UVExfU0VDT05EUyB9KTtcblxuICAvLyBSZS1yZWFkIHJhdGhlciB0aGFuIHRydXN0aW5nIGBtaW50ZWRgOiBhbm90aGVyIGluc3RhbmNlIHRoYXQgcmFjZWQgdGhpcyBib290XG4gIC8vIG1heSBoYXZlIHdyaXR0ZW4gZmlyc3QsIGFuZCBhZG9wdGluZyB3aGF0ZXZlciBpcyBzdG9yZWQgaXMgd2hhdCBtYWtlcyB0aGUgdHdvXG4gIC8vIGNvbnZlcmdlIG9uIG9uZSBrZXkuXG4gIGNvbnN0IHNldHRsZWQgPSAoYXdhaXQgY2FjaGUuZ2V0PFN0b3JlZEtleT4oa2V5KSkgPz8gbWludGVkO1xuICBsb2dnZXIuaW5mbyhcInN0b3JlZCBhIG5ldyBzaWduaW5nIGtleVwiLCB7XG4gICAgY3JlYXRlZEF0OiBzZXR0bGVkLmNyZWF0ZWRBdCxcbiAgICB0dGxTZWNvbmRzOiBLRVlfVFRMX1NFQ09ORFMsXG4gICAgYWRvcHRlZDogc2V0dGxlZC5jcmVhdGVkQXQgPT09IG1pbnRlZC5jcmVhdGVkQXQgPyBcIm93blwiIDogXCJjb25jdXJyZW50LWluc3RhbmNlXCIsXG4gIH0pO1xuICByZXR1cm4gZGVjb2RlKHNldHRsZWQpO1xufVxuXG4vKiogQSByZXNvbHZlZCBrZXkgcGx1cyB0aGUgY3V0b2ZmIGl0IGlzIHNjb3BlZCB0by4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgU2lnbmluZ0tleSB7XG4gIGtleTogVWludDhBcnJheTtcbiAgLyoqIEZvcmNlLWNsZWFyIGN1dG9mZiBpbiBtczsgYDBgIHdoZW4gdW5zZXQuIFRva2VucyBvbGRlciB0aGFuIHRoaXMgYXJlIHJlZnVzZWQuICovXG4gIGN1dG9mZk1zOiBudW1iZXI7XG59XG5cbmxldCBwZW5kaW5nOiBQcm9taXNlPFNpZ25pbmdLZXk+IHwgdW5kZWZpbmVkO1xuXG4vKipcbiAqIFRoZSBzaWduaW5nIGtleSBmb3IgdGhpcyBnYXRlLCByZXNvbHZlZCBvbmNlIHBlciBwcm9jZXNzLlxuICpcbiAqIFJlc29sdmVkIE9OQ0UsIHNvIGBjb25maWd1cmVkQ3V0b2ZmYCBpcyBob25vdXJlZCBvbmx5IG9uIHRoZSBmaXJzdCBjYWxsIC0gdGhlXG4gKiBwbHVnaW4ncyBgc2V0dXAoKWAgcGFzc2VzIGl0cyByZXNvbHZlZCB2YWx1ZSB0aGVyZSwgYmVmb3JlIGFueSByZXF1ZXN0IGNhblxuICogcmVhY2ggdGhlIGxhenkgcGF0aC5cbiAqXG4gKiBgVFVOTkVMX0FVVEhfSldUX1NFQ1JFVGAgd2hlbiBzZXQsIGVsc2UgdGhlIGNhY2hlLWJhY2tlZCBrZXkuIEEgY2FjaGUgdGhhdCBpc1xuICogdW5hdmFpbGFibGUgZGVncmFkZXMgdG8gYW4gZXBoZW1lcmFsIHBlci1wcm9jZXNzIGtleSAodGhlIHByZXZpb3VzIGJlaGF2aW91cilcbiAqIHJhdGhlciB0aGFuIHJlZnVzaW5nIHRvIHNpZ246IHRoZSBrZXkgb25seSB2YWxpZGF0ZXMgYW4gQUxSRUFEWS1pc3N1ZWQgc2Vzc2lvbixcbiAqIHNvIGxvc2luZyBpdCBjb3N0cyBzZXNzaW9ucywgbmV2ZXIgYWRtaXNzaW9uIC0gYSBjYWxsZXIgc3RpbGwgbmVlZHMgYSBjb2RlXG4gKiBkZWxpdmVyZWQgdG8gYW4gYWxsb3ctbGlzdGVkIGFkZHJlc3MuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzaWduaW5nS2V5KGNvbmZpZ3VyZWRDdXRvZmY/OiBzdHJpbmcgfCBudW1iZXIgfCBEYXRlKTogUHJvbWlzZTxTaWduaW5nS2V5PiB7XG4gIHBlbmRpbmcgPz89IChhc3luYyAoKSA9PiB7XG4gICAgY29uc3QgY3V0b2ZmTXMgPSByZXNvbHZlU2Vzc2lvbkN1dG9mZihjb25maWd1cmVkQ3V0b2ZmKTtcbiAgICBjb25zdCBjb25maWd1cmVkID0gZW52LnRleHQoSldUX1NFQ1JFVF9FTlYpO1xuICAgIGlmIChjb25maWd1cmVkKSB7XG4gICAgICByZXR1cm4geyBrZXk6IG5ldyBUZXh0RW5jb2RlcigpLmVuY29kZShjb25maWd1cmVkKSwgY3V0b2ZmTXMgfTtcbiAgICB9XG4gICAgdHJ5IHtcbiAgICAgIHJldHVybiB7IGtleTogYXdhaXQgbG9hZEZyb21DYWNoZShjdXRvZmZNcyksIGN1dG9mZk1zIH07XG4gICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgIGxvZ2dlci53YXJuKFxuICAgICAgICBgbm8gJHtlbnYubmFtZShKV1RfU0VDUkVUX0VOVil9IGFuZCB0aGUgY2FjaGUgaXMgdW5hdmFpbGFibGUgLSB1c2luZyBhbiBlcGhlbWVyYWwgcGVyLXByb2Nlc3Mga2V5OyBzZXNzaW9ucyB3aWxsIG5vdCBzdXJ2aXZlIGEgcmVzdGFydGAsXG4gICAgICAgIHsgZXJyb3IgfSxcbiAgICAgICk7XG4gICAgICByZXR1cm4geyBrZXk6IG5ldyBVaW50OEFycmF5KHJhbmRvbUJ5dGVzKEtFWV9CWVRFUykpLCBjdXRvZmZNcyB9O1xuICAgIH1cbiAgfSkoKTtcbiAgcmV0dXJuIHBlbmRpbmc7XG59XG5cbi8qKiBSZXNldCB0aGUgcGVyLXByb2Nlc3Mga2V5ICh0ZXN0cywgb3IgYWZ0ZXIgY2hhbmdpbmcgdGhlIGVudiBpbi1wcm9jZXNzKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNldFNpZ25pbmdLZXkoKTogdm9pZCB7XG4gIHBlbmRpbmcgPSB1bmRlZmluZWQ7XG59XG4iXX0=
package/package.json CHANGED
@@ -29,11 +29,11 @@
29
29
  "dependencies": {
30
30
  "@clack/prompts": "^1.7.0",
31
31
  "@databricks/appkit": "^0.43.0",
32
- "@dbx-tools/appkit": "0.6.51",
33
- "@dbx-tools/core": "0.6.51",
34
- "@dbx-tools/email": "0.6.51",
35
- "@dbx-tools/shared-core": "0.6.51",
36
- "@dbx-tools/shared-email": "0.6.51",
32
+ "@dbx-tools/appkit": "0.6.52",
33
+ "@dbx-tools/core": "0.6.52",
34
+ "@dbx-tools/email": "0.6.52",
35
+ "@dbx-tools/shared-core": "0.6.52",
36
+ "@dbx-tools/shared-email": "0.6.52",
37
37
  "commander": "^15.0.0",
38
38
  "http-proxy-3": "^1.23.3",
39
39
  "jose": "^6.2.3"
@@ -100,7 +100,7 @@
100
100
  "./package.json": "./package.json"
101
101
  }
102
102
  },
103
- "version": "0.6.51",
103
+ "version": "0.6.52",
104
104
  "types": "./lib/index.d.ts",
105
105
  "type": "module",
106
106
  "exports": {
package/src/cli.ts CHANGED
@@ -52,7 +52,7 @@ interface TunnelOpts {
52
52
  message?: string;
53
53
  sessionTtl?: string;
54
54
  codeTtl?: string;
55
- sessionEpoch?: string;
55
+ sessionCutoff?: string;
56
56
  insecure?: boolean;
57
57
  forwardHeaders?: string;
58
58
  }
@@ -77,8 +77,8 @@ function program(): Command {
77
77
  .option("--session-ttl <seconds>", "Session lifetime (env TUNNEL_AUTH_SESSION_TTL)")
78
78
  .option("--code-ttl <seconds>", "One-time-code lifetime (env TUNNEL_AUTH_CODE_TTL)")
79
79
  .option(
80
- "--session-epoch <date>",
81
- "Invalidate sessions issued before this date, signing everyone out: a date, ISO timestamp, or epoch seconds (env TUNNEL_AUTH_SESSION_EPOCH)",
80
+ "--session-cutoff <when>",
81
+ "Invalidate sessions issued before this point, signing everyone out: a date, ISO instant, epoch seconds, or relative duration like -30d (env TUNNEL_AUTH_SESSION_CUTOFF)",
82
82
  )
83
83
  .option(
84
84
  "--forward-headers <patterns>",
@@ -140,7 +140,7 @@ export async function runCli(argv: string[]): Promise<void> {
140
140
  message: opts.message,
141
141
  sessionTtlSeconds: opts.sessionTtl ? Number(opts.sessionTtl) : undefined,
142
142
  codeTtlSeconds: opts.codeTtl ? Number(opts.codeTtl) : undefined,
143
- sessionEpoch: opts.sessionEpoch,
143
+ sessionCutoff: opts.sessionCutoff,
144
144
  };
145
145
 
146
146
  // 1. Spawn the wrapped app with the PRIVATE port. It binds loopback; only the
package/src/env.ts CHANGED
@@ -53,10 +53,14 @@ export const CODE_TTL_ENV: EnvKey = ["TUNNEL_AUTH_CODE_TTL", "AUTH_CODE_TTL"];
53
53
  export const JWT_SECRET_ENV: EnvKey = ["TUNNEL_AUTH_JWT_SECRET", "AUTH_JWT_SECRET"];
54
54
 
55
55
  /**
56
- * Force-clear date for sessions issued before it: every earlier cookie stops
57
- * verifying. Any `Date`-parseable value, or bare epoch seconds / millis.
56
+ * Force-clear cutoff: every session issued BEFORE it stops verifying. Anything
57
+ * `object.toDate` accepts - a date, an ISO instant, epoch seconds/millis, or a
58
+ * relative duration (`-30d`, `7 days ago`).
58
59
  */
59
- export const SESSION_EPOCH_ENV: EnvKey = "TUNNEL_AUTH_SESSION_EPOCH";
60
+ export const SESSION_CUTOFF_ENV: EnvKey = [
61
+ "TUNNEL_AUTH_SESSION_CUTOFF",
62
+ "TUNNEL_AUTH_SESSION_EPOCH",
63
+ ];
60
64
 
61
65
  /** The public `<subdomain>.<server>` portr should serve on. */
62
66
  export const PUBLIC_DOMAIN_ENV: EnvKey = ["TUNNEL_PUBLIC_DOMAIN", "PUBLIC_DOMAIN"];
package/src/otp.ts CHANGED
@@ -12,8 +12,8 @@
12
12
  * signing key is resolved by `./signing-key.ts`: `TUNNEL_AUTH_JWT_SECRET` when
13
13
  * set, else a key persisted in the cache for 30 days so cookies survive the
14
14
  * restarts a tunnel sees whenever the app it wraps reloads. A cookie signed
15
- * before `TUNNEL_AUTH_SESSION_EPOCH` is refused here as well as being orphaned by
16
- * the cache key, so the force-clear switch holds even for a still-current key.
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
17
  *
18
18
  * @module
19
19
  */
@@ -118,16 +118,16 @@ export async function signSession(email: string, ttlSeconds: number): Promise<st
118
118
  export async function verifySession(token: string | undefined): Promise<string | undefined> {
119
119
  if (!token) return undefined;
120
120
  try {
121
- const { key, epochMs } = await signingKey();
121
+ const { key, cutoffMs } = await signingKey();
122
122
  const { payload } = await jwtVerify(token, key, { audience: JWT_AUD });
123
- // Belt and braces with the epoch-scoped cache key: that alone already
124
- // orphans older keys, but an operator who moved the epoch while
123
+ // Belt and braces with the cutoff-scoped cache key: that alone already
124
+ // orphans older keys, but an operator who moved the cutoff while
125
125
  // TUNNEL_AUTH_JWT_SECRET is set has no key rotation to rely on, and this
126
126
  // check is what makes the switch work in that case too.
127
127
  // Compared in whole SECONDS because `iat` has no finer resolution: against a
128
- // millisecond epoch, a cookie minted in the same second as the cutoff would
129
- // be refused depending on sub-second rounding.
130
- if (epochMs > 0 && (payload.iat === undefined || payload.iat < Math.floor(epochMs / 1000))) {
128
+ // millisecond cutoff, a cookie minted in the same second as it would be
129
+ // refused depending on sub-second rounding.
130
+ if (cutoffMs > 0 && (payload.iat === undefined || payload.iat < Math.floor(cutoffMs / 1000))) {
131
131
  return undefined;
132
132
  }
133
133
  return typeof payload.email === "string" ? payload.email : undefined;
package/src/plugin.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  import { Plugin, toPlugin, type BasePluginConfig, type PluginManifest } from "@databricks/appkit";
20
- import { brand, env, log, string } from "@dbx-tools/shared-core";
20
+ import { brand, env, log, object, string } from "@dbx-tools/shared-core";
21
21
  import type { AuthStatus } from "@dbx-tools/shared-email";
22
22
  import { looksLikeEmail, matchesAllowlist } from "./allowlist.ts";
23
23
  import {
@@ -30,7 +30,7 @@ import {
30
30
  } from "./env.ts";
31
31
  import { CodeStore, signSession, verifySession } from "./otp.ts";
32
32
  import { RateLimiter } from "./rate-limit.ts";
33
- import { KEY_TTL_SECONDS, resolveSessionEpoch, signingKey } from "./signing-key.ts";
33
+ import { KEY_TTL_SECONDS, resolveSessionCutoff, signingKey } from "./signing-key.ts";
34
34
 
35
35
  const logger = log.logger("tunnel:auth");
36
36
 
@@ -76,13 +76,14 @@ export interface AuthGateConfig extends BasePluginConfig {
76
76
  /** Max verify attempts per issued code. Default 5. */
77
77
  maxAttempts?: number;
78
78
  /**
79
- * Force-clear date: every session issued BEFORE it stops verifying, so moving
80
- * it forward signs everyone out. Env TUNNEL_AUTH_SESSION_EPOCH.
79
+ * Force-clear cutoff: every session issued BEFORE it stops verifying, so moving
80
+ * it forward signs everyone out. Env TUNNEL_AUTH_SESSION_CUTOFF.
81
81
  *
82
- * Any `Date`-parseable value (`2026-08-02`, an ISO timestamp) or bare epoch
83
- * seconds / millis. Unset means no cutoff.
82
+ * Anything `object.toDate` accepts: a `Date`, `2026-08-02`, an ISO instant,
83
+ * epoch seconds/millis, or a relative duration (`-30d`, `7 days ago`). Unset
84
+ * means no cutoff.
84
85
  */
85
- sessionEpoch?: string | number | Date;
86
+ sessionCutoff?: string | number | Date;
86
87
  /** Deliver a code to an address. Wired by the app to the email plugin. */
87
88
  sendCode?: (email: string, code: string, opts: SendCodeOptions) => Promise<void>;
88
89
  }
@@ -111,7 +112,7 @@ export interface ResolvedAuthGateConfig {
111
112
  codeTtlSeconds: number;
112
113
  maxAttempts: number;
113
114
  /** Force-clear cutoff in epoch ms; `0` when unset. */
114
- sessionEpochMs: number;
115
+ sessionCutoffMs: number;
115
116
  }
116
117
 
117
118
  const DEFAULTS = {
@@ -148,7 +149,7 @@ export function resolveAuthGateConfig(config: AuthGateConfig): ResolvedAuthGateC
148
149
  ),
149
150
  codeTtlSeconds: env.positiveInt(config.codeTtlSeconds, CODE_TTL_ENV, DEFAULTS.codeTtlSeconds),
150
151
  maxAttempts: config.maxAttempts ?? DEFAULTS.maxAttempts,
151
- sessionEpochMs: resolveSessionEpoch(config.sessionEpoch),
152
+ sessionCutoffMs: resolveSessionCutoff(config.sessionCutoff),
152
153
  };
153
154
  }
154
155
 
@@ -193,11 +194,11 @@ export class AuthGatePlugin extends Plugin<AuthGateConfig> {
193
194
  // Resolve the signing key HERE rather than lazily on the first sign-in, so a
194
195
  // cache that cannot hold it (and the resulting "sessions won't survive a
195
196
  // restart" warning) shows up in the startup log, not hours later.
196
- const { epochMs } = await signingKey(this.resolved.sessionEpochMs);
197
+ const { cutoffMs } = await signingKey(this.resolved.sessionCutoffMs);
197
198
  logger.info("ready", {
198
199
  patterns: this.resolved.allow.length,
199
200
  sessionTtlSeconds: this.resolved.sessionTtlSeconds,
200
- ...(epochMs > 0 ? { sessionEpoch: new Date(epochMs).toISOString() } : {}),
201
+ ...object.optional("sessionCutoff", cutoffMs > 0 ? new Date(cutoffMs).toISOString() : null),
201
202
  });
202
203
  }
203
204