@atlasauth/js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/attempt.d.ts +71 -0
- package/dist/attempt.js +89 -0
- package/dist/attempt.js.map +1 -0
- package/dist/check-session.d.ts +56 -0
- package/dist/check-session.js +106 -0
- package/dist/check-session.js.map +1 -0
- package/dist/connect.d.ts +50 -0
- package/dist/connect.js +72 -0
- package/dist/connect.js.map +1 -0
- package/dist/fapi.d.ts +123 -0
- package/dist/fapi.js +314 -0
- package/dist/fapi.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/native.d.ts +112 -0
- package/dist/native.js +201 -0
- package/dist/native.js.map +1 -0
- package/dist/passkey.d.ts +70 -0
- package/dist/passkey.js +126 -0
- package/dist/passkey.js.map +1 -0
- package/dist/password-reset.d.ts +41 -0
- package/dist/password-reset.js +80 -0
- package/dist/password-reset.js.map +1 -0
- package/dist/reauth.d.ts +29 -0
- package/dist/reauth.js +45 -0
- package/dist/reauth.js.map +1 -0
- package/dist/redirect.d.ts +38 -0
- package/dist/redirect.js +56 -0
- package/dist/redirect.js.map +1 -0
- package/dist/siwe.d.ts +23 -0
- package/dist/siwe.js +28 -0
- package/dist/siwe.js.map +1 -0
- package/dist/tab-election.d.ts +68 -0
- package/dist/tab-election.js +111 -0
- package/dist/tab-election.js.map +1 -0
- package/dist/telegram.d.ts +25 -0
- package/dist/telegram.js +18 -0
- package/dist/telegram.js.map +1 -0
- package/dist/telemetry.d.ts +125 -0
- package/dist/telemetry.js +357 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/token-cache.d.ts +74 -0
- package/dist/token-cache.js +104 -0
- package/dist/token-cache.js.map +1 -0
- package/package.json +26 -0
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* §bot — client-side signal collection for the anti-bot pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Two collectors, both privacy-preserving and both fully best-effort (they never
|
|
6
|
+
* throw, and degrade to nulls in any environment):
|
|
7
|
+
*
|
|
8
|
+
* - `collectDeviceSignals()` — a snapshot of the device/environment: screen,
|
|
9
|
+
* timezone, hardware, a coarse canvas/WebGL fingerprint, and AUTOMATION
|
|
10
|
+
* TELLS (navigator.webdriver, headless markers, Selenium/Puppeteer traces).
|
|
11
|
+
* - `BehaviorRecorder` — accumulates interaction TIMING AGGREGATES on the auth
|
|
12
|
+
* form: mouse kinematics, keystroke dwell/flight, time-to-submit, scrolls.
|
|
13
|
+
* It records COUNTS AND TIMINGS ONLY — never which keys were pressed, never
|
|
14
|
+
* field values. There is nothing sensitive to leak.
|
|
15
|
+
*
|
|
16
|
+
* `sendTelemetry()` posts a snapshot to `/v1/client/telemetry`. It is
|
|
17
|
+
* fire-and-forget: a failure resolves `false` and is swallowed, so telemetry can
|
|
18
|
+
* never interfere with a real sign-in.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.BehaviorRecorder = void 0;
|
|
22
|
+
exports.collectDeviceSignals = collectDeviceSignals;
|
|
23
|
+
exports.deviceId = deviceId;
|
|
24
|
+
exports.deviceFingerprint = deviceFingerprint;
|
|
25
|
+
exports.sendTelemetry = sendTelemetry;
|
|
26
|
+
const hasWindow = typeof window !== 'undefined';
|
|
27
|
+
const nav = () => (typeof navigator !== 'undefined' ? navigator : undefined);
|
|
28
|
+
function safe(fn, fallback) {
|
|
29
|
+
try {
|
|
30
|
+
return fn();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** WebGL unmasked vendor/renderer, if exposed. */
|
|
37
|
+
function webglInfo() {
|
|
38
|
+
return safe(() => {
|
|
39
|
+
if (!hasWindow || typeof document === 'undefined')
|
|
40
|
+
return { vendor: null, renderer: null };
|
|
41
|
+
const canvas = document.createElement('canvas');
|
|
42
|
+
const gl = (canvas.getContext('webgl') ||
|
|
43
|
+
canvas.getContext('experimental-webgl'));
|
|
44
|
+
if (!gl)
|
|
45
|
+
return { vendor: null, renderer: null };
|
|
46
|
+
const ext = gl.getExtension('WEBGL_debug_renderer_info');
|
|
47
|
+
const vendor = ext ? gl.getParameter(ext.UNMASKED_VENDOR_WEBGL) : null;
|
|
48
|
+
const renderer = ext ? gl.getParameter(ext.UNMASKED_RENDERER_WEBGL) : null;
|
|
49
|
+
return { vendor: vendor ?? null, renderer: renderer ?? null };
|
|
50
|
+
}, { vendor: null, renderer: null });
|
|
51
|
+
}
|
|
52
|
+
/** A short non-crypto canvas fingerprint (a bucket key, never a credential). */
|
|
53
|
+
function canvasHash() {
|
|
54
|
+
return safe(() => {
|
|
55
|
+
if (typeof document === 'undefined')
|
|
56
|
+
return null;
|
|
57
|
+
const canvas = document.createElement('canvas');
|
|
58
|
+
canvas.width = 220;
|
|
59
|
+
canvas.height = 40;
|
|
60
|
+
const ctx = canvas.getContext('2d');
|
|
61
|
+
if (!ctx)
|
|
62
|
+
return null;
|
|
63
|
+
ctx.textBaseline = 'top';
|
|
64
|
+
ctx.font = "14px 'Arial'";
|
|
65
|
+
ctx.fillStyle = '#f60';
|
|
66
|
+
ctx.fillRect(0, 0, 60, 20);
|
|
67
|
+
ctx.fillStyle = '#069';
|
|
68
|
+
ctx.fillText('Atlas anti-bot ✈ 1.0', 2, 15);
|
|
69
|
+
return djb2(canvas.toDataURL());
|
|
70
|
+
}, null);
|
|
71
|
+
}
|
|
72
|
+
/** Detect common headless/automation markers without being fooled by UA alone. */
|
|
73
|
+
function automationTells() {
|
|
74
|
+
const n = nav();
|
|
75
|
+
const w = hasWindow ? window : {};
|
|
76
|
+
const ua = n?.userAgent ?? '';
|
|
77
|
+
const webdriver = safe(() => n?.webdriver === true, false);
|
|
78
|
+
const hasChrome = safe(() => 'chrome' in w && !!w.chrome, false);
|
|
79
|
+
const headless = /headless/i.test(ua) ||
|
|
80
|
+
// A Chrome UA with no chrome object and no plugins is a classic headless tell.
|
|
81
|
+
(/chrome/i.test(ua) && !hasChrome && safe(() => (n?.plugins?.length ?? 0) === 0, false));
|
|
82
|
+
const automationHint = safe(() => webdriver ||
|
|
83
|
+
'_phantom' in w ||
|
|
84
|
+
'callPhantom' in w ||
|
|
85
|
+
'__nightmare' in w ||
|
|
86
|
+
'domAutomation' in w ||
|
|
87
|
+
'domAutomationController' in w ||
|
|
88
|
+
// chromedriver injects cdc_* properties on document.
|
|
89
|
+
(typeof document !== 'undefined' &&
|
|
90
|
+
Object.keys(document).some((k) => k.startsWith('cdc_') || k.startsWith('$cdc_'))) ||
|
|
91
|
+
/electron|phantomjs|slimerjs/i.test(ua), webdriver);
|
|
92
|
+
return { webdriver, headless, hasChrome, automationHint };
|
|
93
|
+
}
|
|
94
|
+
function collectDeviceSignals() {
|
|
95
|
+
const n = nav();
|
|
96
|
+
const gl = webglInfo();
|
|
97
|
+
const tells = automationTells();
|
|
98
|
+
const languages = safe(() => Array.from(n?.languages ?? []), []);
|
|
99
|
+
const renderer = gl.renderer;
|
|
100
|
+
return {
|
|
101
|
+
screenWidth: safe(() => window.screen?.width ?? null, null),
|
|
102
|
+
screenHeight: safe(() => window.screen?.height ?? null, null),
|
|
103
|
+
colorDepth: safe(() => window.screen?.colorDepth ?? null, null),
|
|
104
|
+
pixelRatio: safe(() => window.devicePixelRatio ?? null, null),
|
|
105
|
+
timezone: safe(() => Intl.DateTimeFormat().resolvedOptions().timeZone ?? null, null),
|
|
106
|
+
timezoneOffset: safe(() => new Date().getTimezoneOffset(), null),
|
|
107
|
+
language: safe(() => n?.language ?? null, null),
|
|
108
|
+
languages,
|
|
109
|
+
platform: safe(() => n?.platform ?? null, null),
|
|
110
|
+
hardwareConcurrency: safe(() => n?.hardwareConcurrency ?? null, null),
|
|
111
|
+
deviceMemory: safe(() => n?.deviceMemory ?? null, null),
|
|
112
|
+
maxTouchPoints: safe(() => n?.maxTouchPoints ?? null, null),
|
|
113
|
+
touchSupport: safe(() => (n?.maxTouchPoints ?? 0) > 0 || 'ontouchstart' in window, false),
|
|
114
|
+
cookieEnabled: safe(() => n?.cookieEnabled ?? null, null),
|
|
115
|
+
doNotTrack: safe(() => n?.doNotTrack === '1' || n?.doNotTrack === 'yes', null),
|
|
116
|
+
webglVendor: gl.vendor,
|
|
117
|
+
webglRenderer: renderer,
|
|
118
|
+
pluginCount: safe(() => n?.plugins?.length ?? null, null),
|
|
119
|
+
pointerType: safe(() => (window.matchMedia?.('(pointer: fine)')?.matches ? 'fine' : 'coarse'), null),
|
|
120
|
+
webdriver: tells.webdriver,
|
|
121
|
+
headless: tells.headless,
|
|
122
|
+
hasChrome: tells.hasChrome,
|
|
123
|
+
webglSoftware: renderer ? /swiftshader|llvmpipe|software|basic render/i.test(renderer) : false,
|
|
124
|
+
automationHint: tells.automationHint,
|
|
125
|
+
// canvasHash folds into the device fingerprint below; kept off the wire shape
|
|
126
|
+
// to stay small, but influences deviceFingerprint().
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** A stable pseudonymous client id, persisted in localStorage. */
|
|
130
|
+
function deviceId() {
|
|
131
|
+
return safe(() => {
|
|
132
|
+
if (typeof localStorage === 'undefined')
|
|
133
|
+
return null;
|
|
134
|
+
const KEY = '__atlas_did';
|
|
135
|
+
let id = localStorage.getItem(KEY);
|
|
136
|
+
if (!id) {
|
|
137
|
+
id = 'd_' + djb2(String(Math.random()) + String(Date.now()) + (nav()?.userAgent ?? ''));
|
|
138
|
+
localStorage.setItem(KEY, id);
|
|
139
|
+
}
|
|
140
|
+
return id;
|
|
141
|
+
}, null);
|
|
142
|
+
}
|
|
143
|
+
/** A short client-side fingerprint over the stable device signals. */
|
|
144
|
+
function deviceFingerprint(device) {
|
|
145
|
+
const parts = [
|
|
146
|
+
device.timezone,
|
|
147
|
+
device.platform,
|
|
148
|
+
device.languages.join(','),
|
|
149
|
+
device.webglVendor,
|
|
150
|
+
device.webglRenderer,
|
|
151
|
+
device.screenWidth,
|
|
152
|
+
device.screenHeight,
|
|
153
|
+
device.colorDepth,
|
|
154
|
+
device.hardwareConcurrency,
|
|
155
|
+
canvasHash(),
|
|
156
|
+
];
|
|
157
|
+
return djb2(parts.map((p) => (p == null ? '' : String(p))).join('|'));
|
|
158
|
+
}
|
|
159
|
+
/** A tiny deterministic string hash (djb2) → 8-hex. Not a credential. */
|
|
160
|
+
function djb2(s) {
|
|
161
|
+
let h = 5381;
|
|
162
|
+
for (let i = 0; i < s.length; i += 1)
|
|
163
|
+
h = ((h << 5) + h + s.charCodeAt(i)) >>> 0;
|
|
164
|
+
return h.toString(16).padStart(8, '0');
|
|
165
|
+
}
|
|
166
|
+
const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null);
|
|
167
|
+
const variance = (xs) => {
|
|
168
|
+
const m = mean(xs);
|
|
169
|
+
if (m === null)
|
|
170
|
+
return null;
|
|
171
|
+
return xs.reduce((a, b) => a + (b - m) ** 2, 0) / xs.length;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Records interaction TIMINGS on the page/form. Start it when the auth form
|
|
175
|
+
* mounts; call `snapshot()` at submit. It listens passively and captures no
|
|
176
|
+
* content — only counts, distances and millisecond timings.
|
|
177
|
+
*/
|
|
178
|
+
class BehaviorRecorder {
|
|
179
|
+
start = Date.now();
|
|
180
|
+
firstInteraction = null;
|
|
181
|
+
moves = 0;
|
|
182
|
+
distance = 0;
|
|
183
|
+
maxSpeed = 0;
|
|
184
|
+
speeds = [];
|
|
185
|
+
lastX = null;
|
|
186
|
+
lastY = null;
|
|
187
|
+
lastMoveT = 0;
|
|
188
|
+
netDx = 0;
|
|
189
|
+
netDy = 0;
|
|
190
|
+
keyCount = 0;
|
|
191
|
+
downAt = new Map();
|
|
192
|
+
dwell = [];
|
|
193
|
+
flight = [];
|
|
194
|
+
lastUpAt = null;
|
|
195
|
+
pastes = 0;
|
|
196
|
+
backspaces = 0;
|
|
197
|
+
focusChanges = 0;
|
|
198
|
+
scrolls = 0;
|
|
199
|
+
target;
|
|
200
|
+
bound = [];
|
|
201
|
+
constructor(target) {
|
|
202
|
+
this.target = target ?? (typeof document !== 'undefined' ? document : undefined);
|
|
203
|
+
this.attach();
|
|
204
|
+
}
|
|
205
|
+
mark() {
|
|
206
|
+
if (this.firstInteraction === null)
|
|
207
|
+
this.firstInteraction = Date.now() - this.start;
|
|
208
|
+
}
|
|
209
|
+
attach() {
|
|
210
|
+
const t = this.target;
|
|
211
|
+
if (!t)
|
|
212
|
+
return;
|
|
213
|
+
const on = (type, fn) => {
|
|
214
|
+
safe(() => t.addEventListener(type, fn, { passive: true }), undefined);
|
|
215
|
+
this.bound.push([type, fn]);
|
|
216
|
+
};
|
|
217
|
+
on('pointermove', (e) => this.onMove(e));
|
|
218
|
+
on('mousemove', (e) => this.onMove(e));
|
|
219
|
+
on('keydown', (e) => this.onKeyDown(e));
|
|
220
|
+
on('keyup', (e) => this.onKeyUp(e));
|
|
221
|
+
on('paste', () => {
|
|
222
|
+
this.mark();
|
|
223
|
+
this.pastes += 1;
|
|
224
|
+
});
|
|
225
|
+
on('focusin', () => {
|
|
226
|
+
this.mark();
|
|
227
|
+
this.focusChanges += 1;
|
|
228
|
+
});
|
|
229
|
+
on('scroll', () => {
|
|
230
|
+
this.mark();
|
|
231
|
+
this.scrolls += 1;
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
onMove(e) {
|
|
235
|
+
this.mark();
|
|
236
|
+
const x = e.clientX ?? 0;
|
|
237
|
+
const y = e.clientY ?? 0;
|
|
238
|
+
const now = Date.now();
|
|
239
|
+
if (this.lastX !== null && this.lastY !== null) {
|
|
240
|
+
const dx = x - this.lastX;
|
|
241
|
+
const dy = y - this.lastY;
|
|
242
|
+
const d = Math.hypot(dx, dy);
|
|
243
|
+
this.distance += d;
|
|
244
|
+
this.netDx += dx;
|
|
245
|
+
this.netDy += dy;
|
|
246
|
+
const dt = now - this.lastMoveT;
|
|
247
|
+
if (dt > 0) {
|
|
248
|
+
const speed = d / dt;
|
|
249
|
+
this.speeds.push(speed);
|
|
250
|
+
if (speed > this.maxSpeed)
|
|
251
|
+
this.maxSpeed = speed;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
this.moves += 1;
|
|
255
|
+
this.lastX = x;
|
|
256
|
+
this.lastY = y;
|
|
257
|
+
this.lastMoveT = now;
|
|
258
|
+
}
|
|
259
|
+
onKeyDown(e) {
|
|
260
|
+
this.mark();
|
|
261
|
+
this.keyCount += 1;
|
|
262
|
+
if (e.key === 'Backspace')
|
|
263
|
+
this.backspaces += 1;
|
|
264
|
+
// Keyed by physical code only — never the character. Content is never read.
|
|
265
|
+
this.downAt.set(e.code || e.key || String(this.keyCount), Date.now());
|
|
266
|
+
if (this.lastUpAt !== null)
|
|
267
|
+
this.flight.push(Date.now() - this.lastUpAt);
|
|
268
|
+
}
|
|
269
|
+
onKeyUp(e) {
|
|
270
|
+
const k = e.code || e.key || '';
|
|
271
|
+
const down = this.downAt.get(k);
|
|
272
|
+
const now = Date.now();
|
|
273
|
+
if (down !== undefined) {
|
|
274
|
+
this.dwell.push(now - down);
|
|
275
|
+
this.downAt.delete(k);
|
|
276
|
+
}
|
|
277
|
+
this.lastUpAt = now;
|
|
278
|
+
}
|
|
279
|
+
/** A snapshot of aggregates so far. Safe to call repeatedly. */
|
|
280
|
+
snapshot() {
|
|
281
|
+
const netDist = Math.hypot(this.netDx, this.netDy);
|
|
282
|
+
return {
|
|
283
|
+
mouseMoves: this.moves,
|
|
284
|
+
mouseDistance: Math.round(this.distance),
|
|
285
|
+
mouseAvgSpeed: mean(this.speeds),
|
|
286
|
+
mouseMaxSpeed: Math.round(this.maxSpeed * 1000) / 1000,
|
|
287
|
+
mouseSpeedVariance: variance(this.speeds),
|
|
288
|
+
mouseStraightLineRatio: this.distance > 0 ? Math.min(1, netDist / this.distance) : null,
|
|
289
|
+
keyCount: this.keyCount,
|
|
290
|
+
keyAvgDwellMs: mean(this.dwell),
|
|
291
|
+
keyAvgFlightMs: mean(this.flight),
|
|
292
|
+
keyDwellVariance: variance(this.dwell),
|
|
293
|
+
keyFlightVariance: variance(this.flight),
|
|
294
|
+
pasteCount: this.pastes,
|
|
295
|
+
backspaceCount: this.backspaces,
|
|
296
|
+
timeToFirstInteractionMs: this.firstInteraction,
|
|
297
|
+
timeToSubmitMs: Date.now() - this.start,
|
|
298
|
+
focusChanges: this.focusChanges,
|
|
299
|
+
scrollCount: this.scrolls,
|
|
300
|
+
hadPointerMove: this.moves > 0,
|
|
301
|
+
hadKeydown: this.keyCount > 0,
|
|
302
|
+
interactionCount: this.moves + this.keyCount + this.scrolls + this.focusChanges,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/** Detach all listeners. */
|
|
306
|
+
stop() {
|
|
307
|
+
const t = this.target;
|
|
308
|
+
if (!t)
|
|
309
|
+
return;
|
|
310
|
+
for (const [type, fn] of this.bound)
|
|
311
|
+
safe(() => t.removeEventListener(type, fn), undefined);
|
|
312
|
+
this.bound = [];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
exports.BehaviorRecorder = BehaviorRecorder;
|
|
316
|
+
/**
|
|
317
|
+
* Collect a device snapshot (+ behaviour from `recorder`, if given) and POST it.
|
|
318
|
+
* Resolves `true` when the beacon was accepted, `false` otherwise — never
|
|
319
|
+
* throws. Uses `navigator.sendBeacon` when possible so it survives navigation.
|
|
320
|
+
*/
|
|
321
|
+
async function sendTelemetry(opts) {
|
|
322
|
+
return safe(async () => {
|
|
323
|
+
const device = collectDeviceSignals();
|
|
324
|
+
const payload = {
|
|
325
|
+
attempt_id: opts.attemptId,
|
|
326
|
+
kind: opts.kind ?? 'telemetry',
|
|
327
|
+
device_id: deviceId(),
|
|
328
|
+
device_fingerprint: deviceFingerprint(device),
|
|
329
|
+
device,
|
|
330
|
+
behavior: opts.recorder?.snapshot(),
|
|
331
|
+
};
|
|
332
|
+
const url = `${opts.api}/v1/client/telemetry?publishable_key=${encodeURIComponent(opts.publishableKey)}`;
|
|
333
|
+
const body = JSON.stringify(payload);
|
|
334
|
+
// Prefer sendBeacon in production: it is fire-and-forget and survives the
|
|
335
|
+
// page unloading on submit. It cannot set the publishable-key HEADER, hence
|
|
336
|
+
// the query param above. A caller that INJECTS a fetchImpl (tests, custom
|
|
337
|
+
// transports) means "use exactly this" — so sendBeacon is skipped then.
|
|
338
|
+
const n = nav();
|
|
339
|
+
if (!opts.fetchImpl && n && typeof n.sendBeacon === 'function' && typeof Blob !== 'undefined') {
|
|
340
|
+
const ok = n.sendBeacon(url, new Blob([body], { type: 'application/json' }));
|
|
341
|
+
if (ok)
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
const doFetch = opts.fetchImpl ?? (typeof fetch !== 'undefined' ? fetch : undefined);
|
|
345
|
+
if (!doFetch)
|
|
346
|
+
return false;
|
|
347
|
+
const res = await doFetch(url, {
|
|
348
|
+
method: 'POST',
|
|
349
|
+
headers: { 'content-type': 'application/json', 'x-publishable-key': opts.publishableKey },
|
|
350
|
+
body,
|
|
351
|
+
keepalive: true,
|
|
352
|
+
credentials: 'include',
|
|
353
|
+
});
|
|
354
|
+
return res.ok;
|
|
355
|
+
}, Promise.resolve(false));
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* §7.4 step 1: "the SDK calls POST /v1/client/sessions/:sid/tokens with the
|
|
3
|
+
* refresh cookie whenever its cached JWT is within 10 s of expiry (proactive,
|
|
4
|
+
* jittered to avoid thundering herds across tabs)."
|
|
5
|
+
*
|
|
6
|
+
* The scheduling is the whole problem, and every parameter here is chosen
|
|
7
|
+
* against a specific failure:
|
|
8
|
+
*
|
|
9
|
+
* Refreshing at expiry rather than before it means every request in the
|
|
10
|
+
* window between "token died" and "new token arrived" gets a 401. Users
|
|
11
|
+
* experience that as random logouts on slow connections.
|
|
12
|
+
*
|
|
13
|
+
* Refreshing with no jitter means twenty tabs wake at the same millisecond
|
|
14
|
+
* and hit the API together — which is fine at ten users and an outage at ten
|
|
15
|
+
* thousand.
|
|
16
|
+
*
|
|
17
|
+
* Jittering too WIDELY is worse than not jittering: a token with 60 seconds
|
|
18
|
+
* of life, refreshed 10 seconds early with ±15 seconds of jitter, sometimes
|
|
19
|
+
* refreshes after it has already expired. The jitter window is therefore
|
|
20
|
+
* bounded to strictly less than the lead time, and there is a test that
|
|
21
|
+
* pins it.
|
|
22
|
+
*/
|
|
23
|
+
/** §7.4: refresh once the token is within this of expiring. */
|
|
24
|
+
export declare const REFRESH_LEAD_MS = 10000;
|
|
25
|
+
/**
|
|
26
|
+
* Jitter is subtracted from the delay only, never added. Adding could push the
|
|
27
|
+
* refresh past expiry; subtracting only ever makes it earlier, which is safe.
|
|
28
|
+
*/
|
|
29
|
+
export declare const MAX_JITTER_MS = 5000;
|
|
30
|
+
export interface CachedToken {
|
|
31
|
+
jwt: string;
|
|
32
|
+
/** Absolute expiry, epoch ms. */
|
|
33
|
+
expiresAt: number;
|
|
34
|
+
sessionId: string;
|
|
35
|
+
}
|
|
36
|
+
export interface ScheduleInput {
|
|
37
|
+
token: CachedToken;
|
|
38
|
+
now: number;
|
|
39
|
+
leadMs?: number;
|
|
40
|
+
/** Injected so the schedule is deterministic under test. */
|
|
41
|
+
jitter?: () => number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Milliseconds until this token should be refreshed.
|
|
45
|
+
*
|
|
46
|
+
* Zero means "refresh now" — the token is already inside its lead window, or
|
|
47
|
+
* past expiry. Never negative, because a negative delay in a `setTimeout` is
|
|
48
|
+
* silently treated as zero anyway and the explicit clamp is clearer than
|
|
49
|
+
* relying on that.
|
|
50
|
+
*/
|
|
51
|
+
export declare function msUntilRefresh(input: ScheduleInput): number;
|
|
52
|
+
/** Whether the cached token can still be handed to a caller. */
|
|
53
|
+
export declare function isUsable(token: CachedToken | null, now: number, skewMs?: number): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Read `exp` out of a JWT without verifying it.
|
|
56
|
+
*
|
|
57
|
+
* Deliberately does NOT verify. The client cannot verify — it has no key, and
|
|
58
|
+
* shipping one would be absurd — so this is a scheduling hint and nothing more.
|
|
59
|
+
* Every security decision is made by the server against a signature; a token
|
|
60
|
+
* whose `exp` a hostile party edited only causes the client to refresh at the
|
|
61
|
+
* wrong moment, which the server then rejects on its own terms.
|
|
62
|
+
*/
|
|
63
|
+
export declare function readExpiry(jwt: string): number | null;
|
|
64
|
+
export declare class TokenCache {
|
|
65
|
+
private readonly now;
|
|
66
|
+
private token;
|
|
67
|
+
constructor(now?: () => number);
|
|
68
|
+
set(token: CachedToken): void;
|
|
69
|
+
/** The cached token, or null if it is gone or too close to expiry to use. */
|
|
70
|
+
get(skewMs?: number): CachedToken | null;
|
|
71
|
+
/** Present regardless of usability — for deciding whether to refresh or sign in. */
|
|
72
|
+
peek(): CachedToken | null;
|
|
73
|
+
clear(): void;
|
|
74
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* §7.4 step 1: "the SDK calls POST /v1/client/sessions/:sid/tokens with the
|
|
4
|
+
* refresh cookie whenever its cached JWT is within 10 s of expiry (proactive,
|
|
5
|
+
* jittered to avoid thundering herds across tabs)."
|
|
6
|
+
*
|
|
7
|
+
* The scheduling is the whole problem, and every parameter here is chosen
|
|
8
|
+
* against a specific failure:
|
|
9
|
+
*
|
|
10
|
+
* Refreshing at expiry rather than before it means every request in the
|
|
11
|
+
* window between "token died" and "new token arrived" gets a 401. Users
|
|
12
|
+
* experience that as random logouts on slow connections.
|
|
13
|
+
*
|
|
14
|
+
* Refreshing with no jitter means twenty tabs wake at the same millisecond
|
|
15
|
+
* and hit the API together — which is fine at ten users and an outage at ten
|
|
16
|
+
* thousand.
|
|
17
|
+
*
|
|
18
|
+
* Jittering too WIDELY is worse than not jittering: a token with 60 seconds
|
|
19
|
+
* of life, refreshed 10 seconds early with ±15 seconds of jitter, sometimes
|
|
20
|
+
* refreshes after it has already expired. The jitter window is therefore
|
|
21
|
+
* bounded to strictly less than the lead time, and there is a test that
|
|
22
|
+
* pins it.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.TokenCache = exports.MAX_JITTER_MS = exports.REFRESH_LEAD_MS = void 0;
|
|
26
|
+
exports.msUntilRefresh = msUntilRefresh;
|
|
27
|
+
exports.isUsable = isUsable;
|
|
28
|
+
exports.readExpiry = readExpiry;
|
|
29
|
+
/** §7.4: refresh once the token is within this of expiring. */
|
|
30
|
+
exports.REFRESH_LEAD_MS = 10_000;
|
|
31
|
+
/**
|
|
32
|
+
* Jitter is subtracted from the delay only, never added. Adding could push the
|
|
33
|
+
* refresh past expiry; subtracting only ever makes it earlier, which is safe.
|
|
34
|
+
*/
|
|
35
|
+
exports.MAX_JITTER_MS = 5_000;
|
|
36
|
+
/**
|
|
37
|
+
* Milliseconds until this token should be refreshed.
|
|
38
|
+
*
|
|
39
|
+
* Zero means "refresh now" — the token is already inside its lead window, or
|
|
40
|
+
* past expiry. Never negative, because a negative delay in a `setTimeout` is
|
|
41
|
+
* silently treated as zero anyway and the explicit clamp is clearer than
|
|
42
|
+
* relying on that.
|
|
43
|
+
*/
|
|
44
|
+
function msUntilRefresh(input) {
|
|
45
|
+
const lead = input.leadMs ?? exports.REFRESH_LEAD_MS;
|
|
46
|
+
const jitterFn = input.jitter ?? (() => Math.random() * exports.MAX_JITTER_MS);
|
|
47
|
+
// Bounded to strictly less than the lead, so subtracting jitter can never
|
|
48
|
+
// move the refresh past the moment the token actually dies.
|
|
49
|
+
const jitter = Math.min(Math.max(jitterFn(), 0), Math.max(lead - 1, 0));
|
|
50
|
+
const target = input.token.expiresAt - lead - jitter;
|
|
51
|
+
return Math.max(0, target - input.now);
|
|
52
|
+
}
|
|
53
|
+
/** Whether the cached token can still be handed to a caller. */
|
|
54
|
+
function isUsable(token, now, skewMs = 0) {
|
|
55
|
+
if (!token)
|
|
56
|
+
return false;
|
|
57
|
+
return token.expiresAt - skewMs > now;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Read `exp` out of a JWT without verifying it.
|
|
61
|
+
*
|
|
62
|
+
* Deliberately does NOT verify. The client cannot verify — it has no key, and
|
|
63
|
+
* shipping one would be absurd — so this is a scheduling hint and nothing more.
|
|
64
|
+
* Every security decision is made by the server against a signature; a token
|
|
65
|
+
* whose `exp` a hostile party edited only causes the client to refresh at the
|
|
66
|
+
* wrong moment, which the server then rejects on its own terms.
|
|
67
|
+
*/
|
|
68
|
+
function readExpiry(jwt) {
|
|
69
|
+
const parts = jwt.split('.');
|
|
70
|
+
if (parts.length !== 3)
|
|
71
|
+
return null;
|
|
72
|
+
try {
|
|
73
|
+
const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
74
|
+
return typeof payload.exp === 'number' ? payload.exp * 1000 : null;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
class TokenCache {
|
|
81
|
+
now;
|
|
82
|
+
token = null;
|
|
83
|
+
constructor(now = () => Date.now()) {
|
|
84
|
+
this.now = now;
|
|
85
|
+
}
|
|
86
|
+
set(token) {
|
|
87
|
+
this.token = token;
|
|
88
|
+
}
|
|
89
|
+
/** The cached token, or null if it is gone or too close to expiry to use. */
|
|
90
|
+
get(skewMs = 0) {
|
|
91
|
+
if (!isUsable(this.token, this.now(), skewMs))
|
|
92
|
+
return null;
|
|
93
|
+
return this.token;
|
|
94
|
+
}
|
|
95
|
+
/** Present regardless of usability — for deciding whether to refresh or sign in. */
|
|
96
|
+
peek() {
|
|
97
|
+
return this.token;
|
|
98
|
+
}
|
|
99
|
+
clear() {
|
|
100
|
+
this.token = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.TokenCache = TokenCache;
|
|
104
|
+
//# sourceMappingURL=token-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-cache.js","sourceRoot":"","sources":["../src/token-cache.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAkCH,wCAUC;AAGD,4BAGC;AAWD,gCAYC;AAvED,+DAA+D;AAClD,QAAA,eAAe,GAAG,MAAM,CAAC;AAEtC;;;GAGG;AACU,QAAA,aAAa,GAAG,KAAK,CAAC;AAiBnC;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,KAAoB;IACjD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,IAAI,uBAAe,CAAC;IAC7C,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,qBAAa,CAAC,CAAC;IAEvE,0EAA0E;IAC1E,4DAA4D;IAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,GAAG,MAAM,CAAC;IACrD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,gEAAgE;AAChE,SAAgB,QAAQ,CAAC,KAAyB,EAAE,GAAW,EAAE,MAAM,GAAG,CAAC;IACzE,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACzB,OAAO,KAAK,CAAC,SAAS,GAAG,MAAM,GAAG,GAAG,CAAC;AACxC,CAAC;AAED;;;;;;;;GAQG;AACH,SAAgB,UAAU,CAAC,GAAW;IACpC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAE/E,CAAC;QACF,OAAO,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAa,UAAU;IAGQ;IAFrB,KAAK,GAAuB,IAAI,CAAC;IAEzC,YAA6B,MAAoB,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE;QAApC,QAAG,GAAH,GAAG,CAAiC;IAAG,CAAC;IAErE,GAAG,CAAC,KAAkB;QACpB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED,6EAA6E;IAC7E,GAAG,CAAC,MAAM,GAAG,CAAC;QACZ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3D,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,oFAAoF;IACpF,IAAI;QACF,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,KAAK;QACH,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;CACF;AAvBD,gCAuBC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atlasauth/js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./dist/index.js",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"devDependencies": {
|
|
7
|
+
"happy-dom": "^15.11.0",
|
|
8
|
+
"typescript": "^5.6.3",
|
|
9
|
+
"vitest": "^2.1.4"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@atlasauth/authz": "0.1.0"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"test": "vitest run"
|
|
25
|
+
}
|
|
26
|
+
}
|