@fnndsc/porter 0.5.5 → 0.6.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/tokens.js ADDED
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @file Door tokens: how a script comes through the door without a password.
3
+ *
4
+ * A token is minted once by a human login (a browser code or a password)
5
+ * and carried by scripts afterwards as `Authorization: Bearer …`. The door
6
+ * keeps only its hash, with the identity it is for, a name the operator
7
+ * can read (`cron@titan`), when it was minted and when it dies. A token
8
+ * dies on its day: no sliding refresh, so a leaked key in steady use still
9
+ * ends, and every script is re-blessed by a human on the horizon the
10
+ * operator set (`PORTER_TOKEN_DAYS`). Revocation is by name.
11
+ *
12
+ * Device codes are the browser half of `chell auth login`: chell asks for
13
+ * a short one-time code, the human opens the door's login page on any
14
+ * device, logs in with the code, and the door hands chell the token it
15
+ * minted. A code lives ten minutes and is taken once.
16
+ *
17
+ * The store is one JSON file at mode 0600 in the porter's own state, read
18
+ * at start and written whole on every change through a same-directory
19
+ * atomic rename; the plaintext is never in it.
20
+ *
21
+ * @module
22
+ */
23
+ import { createHash, randomBytes, timingSafeEqual } from 'node:crypto';
24
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
25
+ import { dirname } from 'node:path';
26
+ /** What the plaintext starts with, so a token is recognisable in a log or a leak. */
27
+ export const DOOR_TOKEN_PREFIX = 'pdt_';
28
+ /** How long a device code waits to be authorised, in ms. */
29
+ export const DEVICE_CODE_LIFE_MS = 10 * 60 * 1000;
30
+ /** Makes a fresh plaintext token: 32 random bytes, base64url, prefixed. */
31
+ export function token_make() {
32
+ return `${DOOR_TOKEN_PREFIX}${randomBytes(32).toString('base64url')}`;
33
+ }
34
+ /** Hashes a plaintext token the way the store keeps it. */
35
+ export function token_hash(plain) {
36
+ return createHash('sha256').update(plain).digest('hex');
37
+ }
38
+ /** Whether a string looks like a door token, before any lookup. */
39
+ export function token_looksLike(value) {
40
+ return value.startsWith(DOOR_TOKEN_PREFIX) && value.length > DOOR_TOKEN_PREFIX.length + 20;
41
+ }
42
+ /**
43
+ * The door's token store: minted, checked, revoked, listed, persisted.
44
+ */
45
+ export class TokenStore {
46
+ path;
47
+ days;
48
+ now;
49
+ tokens = [];
50
+ /** The file as last read or written (mtime and size), so a change made by another process is noticed. */
51
+ stamp = '';
52
+ /**
53
+ * @param path - The JSON file; its directory is made (0700) when missing.
54
+ * @param days - How long a minted token lives.
55
+ * @param now - The clock, replaceable in tests.
56
+ */
57
+ constructor(path, days, now = () => new Date()) {
58
+ this.path = path;
59
+ this.days = days;
60
+ this.now = now;
61
+ }
62
+ /**
63
+ * Reads the file. A missing file is an empty store; a file that does not
64
+ * parse is refused loudly, since silently starting empty would let every
65
+ * script's key stop working with no word why.
66
+ */
67
+ load() {
68
+ if (!existsSync(this.path)) {
69
+ this.tokens = [];
70
+ this.stamp = '';
71
+ return;
72
+ }
73
+ const parsed = JSON.parse(readFileSync(this.path, 'utf8'));
74
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.tokens)) {
75
+ throw new Error(`the door's token store at ${this.path} is not a token file`);
76
+ }
77
+ this.tokens = parsed.tokens;
78
+ this.stamp = this.stamp_read();
79
+ }
80
+ /** The file's mtime and size as one string; empty when there is no file. */
81
+ stamp_read() {
82
+ try {
83
+ const facts = statSync(this.path);
84
+ return `${facts.mtimeMs}:${facts.size}`;
85
+ }
86
+ catch {
87
+ return '';
88
+ }
89
+ }
90
+ /**
91
+ * Re-reads the file when another process changed it: the porter's CLI
92
+ * (`--revoke`, `--mint`) writes the store from outside the running door,
93
+ * and a revocation the door did not notice would be no revocation.
94
+ */
95
+ refresh() {
96
+ if (this.stamp_read() !== this.stamp)
97
+ this.load();
98
+ }
99
+ /**
100
+ * Mints a token for an identity. A token of the same name for the same
101
+ * identity is replaced, so `login` again on the same host never leaves
102
+ * a forgotten key behind.
103
+ *
104
+ * @param identity - The canonical identity.
105
+ * @param user - The CUBE username.
106
+ * @param name - The operator-readable name.
107
+ * @returns The plaintext, shown once, and the record kept.
108
+ */
109
+ mint(identity, user, name) {
110
+ this.refresh();
111
+ const token = token_make();
112
+ const created = this.now();
113
+ const record = {
114
+ name,
115
+ identity,
116
+ user,
117
+ hash: token_hash(token),
118
+ created: created.toISOString(),
119
+ expires: new Date(created.getTime() + this.days * 24 * 3600 * 1000).toISOString(),
120
+ lastUsed: null,
121
+ };
122
+ this.tokens = this.tokens.filter((held) => !(held.identity === identity && held.name === name));
123
+ this.tokens.push(record);
124
+ this.save();
125
+ return { token, record };
126
+ }
127
+ /**
128
+ * Checks a presented plaintext against the store, in constant time per
129
+ * record, and says why when it fails.
130
+ *
131
+ * @param plain - The token as presented.
132
+ * @returns The record when it lets someone in.
133
+ */
134
+ check(plain) {
135
+ this.refresh();
136
+ const presented = Buffer.from(token_hash(plain), 'hex');
137
+ for (const record of this.tokens) {
138
+ const kept = Buffer.from(record.hash, 'hex');
139
+ if (kept.length !== presented.length || !timingSafeEqual(kept, presented))
140
+ continue;
141
+ if (Date.parse(record.expires) <= this.now().getTime())
142
+ return { ok: false, why: 'expired', record };
143
+ return { ok: true, record };
144
+ }
145
+ return { ok: false, why: 'unknown' };
146
+ }
147
+ /**
148
+ * Notes that a token just let someone in.
149
+ *
150
+ * @param record - The token that did.
151
+ */
152
+ touch(record) {
153
+ this.refresh();
154
+ record.lastUsed = this.now().toISOString();
155
+ this.save();
156
+ }
157
+ /**
158
+ * Revokes a user's tokens: one by name, or all of them.
159
+ *
160
+ * @param user - The CUBE username.
161
+ * @param name - The token's name, or undefined for every one of the user's.
162
+ * @returns How many went.
163
+ */
164
+ revoke(user, name) {
165
+ this.refresh();
166
+ const before = this.tokens.length;
167
+ this.tokens = this.tokens.filter((held) => !(held.user === user && (name === undefined || held.name === name)));
168
+ if (this.tokens.length !== before)
169
+ this.save();
170
+ return before - this.tokens.length;
171
+ }
172
+ /** @returns Every token the door knows, as kept (no plaintext exists to show). */
173
+ list() {
174
+ this.refresh();
175
+ return [...this.tokens];
176
+ }
177
+ /** Writes the whole store, mode 0600, through a same-directory atomic rename. */
178
+ save() {
179
+ const file = { schemaVersion: 1, tokens: this.tokens };
180
+ mkdirSync(dirname(this.path), { recursive: true, mode: 0o700 });
181
+ const temporary = `${this.path}.${process.pid}.tmp`;
182
+ writeFileSync(temporary, `${JSON.stringify(file, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
183
+ renameSync(temporary, this.path);
184
+ chmodSync(this.path, 0o600);
185
+ this.stamp = this.stamp_read();
186
+ }
187
+ }
188
+ /** The letters a code is made of: no 0/O, 1/I/L, so it survives being read aloud. */
189
+ const CODE_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789';
190
+ /** Makes a code like `K7PD-3MXQ`. */
191
+ export function deviceCode_make() {
192
+ const bytes = randomBytes(8);
193
+ let code = '';
194
+ for (let i = 0; i < 8; i++) {
195
+ if (i === 4)
196
+ code += '-';
197
+ code += CODE_ALPHABET[bytes[i] % CODE_ALPHABET.length];
198
+ }
199
+ return code;
200
+ }
201
+ /** Normalises a code as a human might type it: upper case, the dash optional. */
202
+ export function deviceCode_normalise(typed) {
203
+ const letters = typed.toUpperCase().replace(/[^A-Z0-9]/g, '');
204
+ return letters.length === 8 ? `${letters.slice(0, 4)}-${letters.slice(4)}` : typed.trim().toUpperCase();
205
+ }
206
+ /**
207
+ * The device codes the door has out: issued by chell, authorised by a human
208
+ * on the login page, taken once by chell's poll.
209
+ */
210
+ export class DeviceCodes {
211
+ now;
212
+ codes = new Map();
213
+ constructor(now = () => Date.now()) {
214
+ this.now = now;
215
+ }
216
+ /**
217
+ * Issues a code for a chell on a host.
218
+ *
219
+ * @param host - Where chell runs.
220
+ * @returns The code and when it stops being accepted.
221
+ */
222
+ issue(host) {
223
+ this.sweep();
224
+ let code = deviceCode_make();
225
+ while (this.codes.has(code))
226
+ code = deviceCode_make();
227
+ const issued = this.now();
228
+ this.codes.set(code, { host, issued, grant: null });
229
+ return { code, expires: new Date(issued + DEVICE_CODE_LIFE_MS).toISOString() };
230
+ }
231
+ /**
232
+ * Looks a code up for the login page.
233
+ *
234
+ * @param code - As typed.
235
+ * @returns The host chell runs on, or null when the code is not out.
236
+ */
237
+ host_of(code) {
238
+ this.sweep();
239
+ const held = this.codes.get(deviceCode_normalise(code));
240
+ return held === undefined || held.grant !== null ? null : held.host;
241
+ }
242
+ /**
243
+ * Authorises a code: a human logged in with it.
244
+ *
245
+ * @param code - As typed.
246
+ * @param grant - What chell gets.
247
+ * @returns Whether the code was waiting.
248
+ */
249
+ authorise(code, grant) {
250
+ this.sweep();
251
+ const held = this.codes.get(deviceCode_normalise(code));
252
+ if (held === undefined || held.grant !== null)
253
+ return false;
254
+ held.grant = grant;
255
+ return true;
256
+ }
257
+ /**
258
+ * Chell's poll: the grant once, pending while the human has not yet
259
+ * logged in, unknown after the code died or was taken.
260
+ *
261
+ * @param code - As chell was given it.
262
+ */
263
+ take(code) {
264
+ this.sweep();
265
+ const held = this.codes.get(code);
266
+ if (held === undefined)
267
+ return { state: 'unknown' };
268
+ if (held.grant === null)
269
+ return { state: 'pending' };
270
+ this.codes.delete(code);
271
+ return { state: 'authorised', grant: held.grant };
272
+ }
273
+ /** Forgets codes past their life. */
274
+ sweep() {
275
+ const cutoff = this.now() - DEVICE_CODE_LIFE_MS;
276
+ for (const [code, held] of this.codes) {
277
+ if (held.issued < cutoff)
278
+ this.codes.delete(code);
279
+ }
280
+ }
281
+ }
282
+ //# sourceMappingURL=tokens.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fnndsc/porter",
3
- "version": "0.5.5",
3
+ "version": "0.6.0",
4
4
  "description": "PORTER: the mise display manager — logs an operator in, finds or starts their calypso, and shows them to argus",
5
5
  "type": "module",
6
6
  "main": "dist/porter.js",
@@ -19,7 +19,7 @@
19
19
  "@fastify/formbody": "^9.0.0",
20
20
  "@fastify/reply-from": "^12.6.5",
21
21
  "@fnndsc/calypso": "^0.19.6",
22
- "@fnndsc/chell": "^5.10.3",
22
+ "@fnndsc/chell": "^5.11.0",
23
23
  "@fnndsc/menu": "^0.19.0",
24
24
  "fastify": "^5.12.5",
25
25
  "ws": "^8.18.0",