@tapflowio/relay 0.25.0 → 0.26.1

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 (144) hide show
  1. package/dist/RelayServer.d.ts +15 -3
  2. package/dist/RelayServer.d.ts.map +1 -1
  3. package/dist/RelayServer.js +45 -15
  4. package/dist/RelayServer.js.map +1 -1
  5. package/dist/api/auth.d.ts +2 -1
  6. package/dist/api/auth.d.ts.map +1 -1
  7. package/dist/api/auth.js +28 -8
  8. package/dist/api/auth.js.map +1 -1
  9. package/dist/api/invitations.d.ts +3 -1
  10. package/dist/api/invitations.d.ts.map +1 -1
  11. package/dist/api/invitations.js +16 -20
  12. package/dist/api/invitations.js.map +1 -1
  13. package/dist/api/passwordReset.d.ts +14 -2
  14. package/dist/api/passwordReset.d.ts.map +1 -1
  15. package/dist/api/passwordReset.js +45 -10
  16. package/dist/api/passwordReset.js.map +1 -1
  17. package/dist/api/team.d.ts +3 -2
  18. package/dist/api/team.d.ts.map +1 -1
  19. package/dist/api/team.js +11 -5
  20. package/dist/api/team.js.map +1 -1
  21. package/dist/api/tokens.d.ts +2 -1
  22. package/dist/api/tokens.d.ts.map +1 -1
  23. package/dist/api/tokens.js +8 -2
  24. package/dist/api/tokens.js.map +1 -1
  25. package/dist/lib/adminAccount.d.ts +0 -5
  26. package/dist/lib/adminAccount.d.ts.map +1 -1
  27. package/dist/lib/adminAccount.js +6 -7
  28. package/dist/lib/adminAccount.js.map +1 -1
  29. package/dist/lib/connectionAuth.d.ts +35 -5
  30. package/dist/lib/connectionAuth.d.ts.map +1 -1
  31. package/dist/lib/connectionAuth.js +19 -3
  32. package/dist/lib/connectionAuth.js.map +1 -1
  33. package/dist/lib/email.d.ts +16 -0
  34. package/dist/lib/email.d.ts.map +1 -0
  35. package/dist/lib/email.js +18 -0
  36. package/dist/lib/email.js.map +1 -0
  37. package/dist/middleware/auth.d.ts +20 -1
  38. package/dist/middleware/auth.d.ts.map +1 -1
  39. package/dist/middleware/auth.js +33 -6
  40. package/dist/middleware/auth.js.map +1 -1
  41. package/dist/migrations/014_normalize_emails.sql +16 -0
  42. package/package.json +3 -3
  43. package/public/assets/{AppCenter-D_1rFI40.js → AppCenter-DZCsC2wa.js} +1 -1
  44. package/public/assets/AppCenter-DZCsC2wa.js.br +0 -0
  45. package/public/assets/AppCenter-DZCsC2wa.js.gz +0 -0
  46. package/public/assets/{Default-DF_X2_3I.js → Default-xD9Fmd5X.js} +1 -1
  47. package/public/assets/Default-xD9Fmd5X.js.br +0 -0
  48. package/public/assets/Default-xD9Fmd5X.js.gz +0 -0
  49. package/public/assets/Invite-29Erwv8j.js +1 -0
  50. package/public/assets/Invite-29Erwv8j.js.br +0 -0
  51. package/public/assets/Invite-29Erwv8j.js.gz +0 -0
  52. package/public/assets/{MacResources--1Enisxs.js → MacResources-B0DQ9ISE.js} +1 -1
  53. package/public/assets/MacResources-B0DQ9ISE.js.br +0 -0
  54. package/public/assets/MacResources-B0DQ9ISE.js.gz +0 -0
  55. package/public/assets/QASession-D-n6PXuD.js +150 -0
  56. package/public/assets/QASession-D-n6PXuD.js.br +0 -0
  57. package/public/assets/QASession-D-n6PXuD.js.gz +0 -0
  58. package/public/assets/{ResetPassword-B9miyXTP.js → ResetPassword-BnPIwTYH.js} +1 -1
  59. package/public/assets/ResetPassword-BnPIwTYH.js.br +0 -0
  60. package/public/assets/ResetPassword-BnPIwTYH.js.gz +0 -0
  61. package/public/assets/{Setup-CywZCqHU.js → Setup-DmUGFblg.js} +1 -1
  62. package/public/assets/Setup-DmUGFblg.js.br +0 -0
  63. package/public/assets/Setup-DmUGFblg.js.gz +0 -0
  64. package/public/assets/Team-BpvRgFfZ.js +6 -0
  65. package/public/assets/Team-BpvRgFfZ.js.br +0 -0
  66. package/public/assets/Team-BpvRgFfZ.js.gz +0 -0
  67. package/public/assets/Tokens-x6BtCcL7.js +1 -0
  68. package/public/assets/Tokens-x6BtCcL7.js.br +0 -0
  69. package/public/assets/Tokens-x6BtCcL7.js.gz +0 -0
  70. package/public/assets/{alert-dialog-CuPdBkxF.js → alert-dialog-C8wDimW-.js} +1 -1
  71. package/public/assets/alert-dialog-C8wDimW-.js.br +0 -0
  72. package/public/assets/alert-dialog-C8wDimW-.js.gz +0 -0
  73. package/public/assets/{badge-CSVsKOK_.js → badge-CWVbeuH3.js} +1 -1
  74. package/public/assets/badge-CWVbeuH3.js.br +0 -0
  75. package/public/assets/badge-CWVbeuH3.js.gz +0 -0
  76. package/public/assets/{build-format-l9BrlbZl.js → build-format-fzt-JrUX.js} +1 -1
  77. package/public/assets/build-format-fzt-JrUX.js.br +0 -0
  78. package/public/assets/build-format-fzt-JrUX.js.gz +0 -0
  79. package/public/assets/{dialog-B6jt7-2f.js → dialog-DQkbaw8h.js} +2 -2
  80. package/public/assets/dialog-DQkbaw8h.js.br +0 -0
  81. package/public/assets/dialog-DQkbaw8h.js.gz +0 -0
  82. package/public/assets/{index-Cm17I31a.js → index-C1Fo8g_c.js} +32 -32
  83. package/public/assets/index-C1Fo8g_c.js.br +0 -0
  84. package/public/assets/index-C1Fo8g_c.js.gz +0 -0
  85. package/public/assets/index-C2F1AgwB.css +1 -0
  86. package/public/assets/index-C2F1AgwB.css.br +0 -0
  87. package/public/assets/{index-D_P99bPX.css.gz → index-C2F1AgwB.css.gz} +0 -0
  88. package/public/assets/{list-view-Bp6pvsde.js → list-view-DxI397-H.js} +1 -1
  89. package/public/assets/list-view-DxI397-H.js.br +0 -0
  90. package/public/assets/list-view-DxI397-H.js.gz +0 -0
  91. package/public/assets/{pencil-g-WiK1F2.js → pencil-BJcOp3oN.js} +1 -1
  92. package/public/assets/pencil-BJcOp3oN.js.br +0 -0
  93. package/public/assets/pencil-BJcOp3oN.js.gz +0 -0
  94. package/public/assets/{resource-health-BbeyA8an.js → resource-health-Cq3gqqVd.js} +1 -1
  95. package/public/assets/resource-health-Cq3gqqVd.js.br +0 -0
  96. package/public/assets/resource-health-Cq3gqqVd.js.gz +0 -0
  97. package/public/assets/{trash-DHPg15P4.js → trash-DhBxh6H7.js} +1 -1
  98. package/public/assets/trash-DhBxh6H7.js.br +0 -0
  99. package/public/assets/trash-DhBxh6H7.js.gz +0 -0
  100. package/public/index.html +2 -2
  101. package/public/index.html.br +0 -0
  102. package/public/index.html.gz +0 -0
  103. package/public/assets/AppCenter-D_1rFI40.js.br +0 -0
  104. package/public/assets/AppCenter-D_1rFI40.js.gz +0 -0
  105. package/public/assets/Default-DF_X2_3I.js.br +0 -0
  106. package/public/assets/Default-DF_X2_3I.js.gz +0 -0
  107. package/public/assets/Invite-DETTWdYp.js +0 -1
  108. package/public/assets/Invite-DETTWdYp.js.br +0 -0
  109. package/public/assets/Invite-DETTWdYp.js.gz +0 -0
  110. package/public/assets/MacResources--1Enisxs.js.br +0 -0
  111. package/public/assets/MacResources--1Enisxs.js.gz +0 -0
  112. package/public/assets/QASession-BjoD9Toj.js +0 -150
  113. package/public/assets/QASession-BjoD9Toj.js.br +0 -0
  114. package/public/assets/QASession-BjoD9Toj.js.gz +0 -0
  115. package/public/assets/ResetPassword-B9miyXTP.js.br +0 -0
  116. package/public/assets/ResetPassword-B9miyXTP.js.gz +0 -0
  117. package/public/assets/Setup-CywZCqHU.js.br +0 -0
  118. package/public/assets/Setup-CywZCqHU.js.gz +0 -0
  119. package/public/assets/Team-CxdX_9mY.js +0 -6
  120. package/public/assets/Team-CxdX_9mY.js.br +0 -0
  121. package/public/assets/Team-CxdX_9mY.js.gz +0 -0
  122. package/public/assets/Tokens-BQnnawvG.js +0 -1
  123. package/public/assets/Tokens-BQnnawvG.js.br +0 -0
  124. package/public/assets/Tokens-BQnnawvG.js.gz +0 -0
  125. package/public/assets/alert-dialog-CuPdBkxF.js.br +0 -0
  126. package/public/assets/alert-dialog-CuPdBkxF.js.gz +0 -0
  127. package/public/assets/badge-CSVsKOK_.js.br +0 -0
  128. package/public/assets/badge-CSVsKOK_.js.gz +0 -0
  129. package/public/assets/build-format-l9BrlbZl.js.br +0 -0
  130. package/public/assets/build-format-l9BrlbZl.js.gz +0 -0
  131. package/public/assets/dialog-B6jt7-2f.js.br +0 -0
  132. package/public/assets/dialog-B6jt7-2f.js.gz +0 -0
  133. package/public/assets/index-Cm17I31a.js.br +0 -0
  134. package/public/assets/index-Cm17I31a.js.gz +0 -0
  135. package/public/assets/index-D_P99bPX.css +0 -1
  136. package/public/assets/index-D_P99bPX.css.br +0 -0
  137. package/public/assets/list-view-Bp6pvsde.js.br +0 -0
  138. package/public/assets/list-view-Bp6pvsde.js.gz +0 -0
  139. package/public/assets/pencil-g-WiK1F2.js.br +0 -0
  140. package/public/assets/pencil-g-WiK1F2.js.gz +0 -0
  141. package/public/assets/resource-health-BbeyA8an.js.br +0 -0
  142. package/public/assets/resource-health-BbeyA8an.js.gz +0 -0
  143. package/public/assets/trash-DHPg15P4.js.br +0 -0
  144. package/public/assets/trash-DHPg15P4.js.gz +0 -0
package/dist/api/auth.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { getDb } from '../db.js';
2
2
  import { makePasswordHash, verifyPassword, isInitialized, createAdminAccount } from '../lib/adminAccount.js';
3
- import { signJwt, requireAuth } from '../middleware/auth.js';
3
+ import { sessionCookie, requireAuth } from '../middleware/auth.js';
4
+ import { normalizeEmail, EMAIL_KEY_SQL } from '../lib/email.js';
4
5
  import { json, readJson } from '../router.js';
5
6
  import { config } from '../lib/config.js';
6
7
  import { resolveRequestClient } from '../lib/clientAddress.js';
@@ -15,25 +16,40 @@ export async function handleLogin(req, res, trustedProxies = config.local.truste
15
16
  const body = await readJson(req);
16
17
  if (!body.email || !body.password)
17
18
  return json(res, 400, { error: 'email and password required' });
18
- const key = `${resolveRequestClient(req, trustedProxies).addr}|${body.email.toLowerCase()}`;
19
+ const key = `${resolveRequestClient(req, trustedProxies).addr}|${normalizeEmail(body.email)}`;
19
20
  const gate = limiter.check(key);
20
21
  if (!gate.allowed) {
21
22
  res.writeHead(429, { 'Content-Type': 'application/json', 'Retry-After': String(Math.ceil(gate.retryAfterMs / 1000)) });
22
23
  res.end(JSON.stringify({ error: 'Too many attempts. Try again later.' }));
23
24
  return;
24
25
  }
25
- const db = getDb();
26
- const user = db.prepare('SELECT id, email, role, password_hash FROM users WHERE email = ?').get(body.email);
26
+ const user = findLoginUser(body.email);
27
27
  if (!user || !user.password_hash || !verifyPassword(body.password, user.password_hash)) {
28
28
  limiter.recordFailure(key);
29
29
  return json(res, 401, { error: 'Invalid credentials' });
30
30
  }
31
31
  limiter.reset(key);
32
- const token = signJwt({ userId: user.id, email: user.email, role: user.role });
33
- const cookie = `tapflow_token=${token}; HttpOnly; Path=/; SameSite=Lax; Max-Age=${7 * 24 * 3600}`;
32
+ const cookie = sessionCookie({ userId: user.id, email: user.email, role: user.role });
34
33
  res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': cookie });
35
34
  res.end(JSON.stringify({ ok: true, role: user.role }));
36
35
  }
36
+ /**
37
+ * The account an address signs in to. The address exactly as typed comes first, so each row of a pair
38
+ * migration 014 had to leave alone (two addresses that normalize to the same one) still signs in by its
39
+ * own address — tried second, `alice@x.com` would shadow `Alice@x.com` for good. Then the normalized
40
+ * form, which migration 014 and every write since store; then a normalized comparison that must be
41
+ * unambiguous, so an address matching both rows of a pair signs in to neither.
42
+ */
43
+ function findLoginUser(email) {
44
+ const db = getDb();
45
+ const byExact = db.prepare('SELECT id, email, role, password_hash FROM users WHERE email = ?');
46
+ const found = byExact.get(email) ?? byExact.get(normalizeEmail(email));
47
+ if (found)
48
+ return found;
49
+ const byKey = db.prepare(`SELECT id, email, role, password_hash FROM users WHERE ${EMAIL_KEY_SQL} = ?`)
50
+ .all(normalizeEmail(email));
51
+ return byKey.length === 1 ? byKey[0] : undefined;
52
+ }
37
53
  export function handleMe(req, res) {
38
54
  const auth = requireAuth(req, res);
39
55
  if (!auth)
@@ -45,7 +61,7 @@ export function handleMe(req, res) {
45
61
  const displayName = user.display_name ?? user.email.split('@')[0];
46
62
  json(res, 200, { id: user.id, email: user.email, displayName, avatarUrl: user.avatar_url, role: user.role });
47
63
  }
48
- export async function handleChangePassword(req, res) {
64
+ export async function handleChangePassword(req, res, onAuthChanged = () => { }) {
49
65
  const auth = requireAuth(req, res);
50
66
  if (!auth)
51
67
  return;
@@ -61,7 +77,11 @@ export async function handleChangePassword(req, res) {
61
77
  if (!verifyPassword(body.currentPassword, user.password_hash))
62
78
  return json(res, 401, { error: 'Incorrect current password' });
63
79
  db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(makePasswordHash(body.newPassword), auth.userId);
64
- json(res, 200, { ok: true });
80
+ // The new hash ends every session issued under the old one, this browser's included — so this browser
81
+ // gets a fresh cookie, and the others (a stolen one among them) are signed out. Open sockets follow.
82
+ onAuthChanged({ userId: auth.userId, scope: 'sessions' });
83
+ res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': sessionCookie(auth) });
84
+ res.end(JSON.stringify({ ok: true }));
65
85
  }
66
86
  export function handleLogout(_req, res) {
67
87
  res.writeHead(200, {
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/api/auth.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  import http from 'http';
2
2
  export declare function handleVerify(req: http.IncomingMessage, res: http.ServerResponse): void;
3
- export declare function handleAccept(req: http.IncomingMessage, res: http.ServerResponse, uploadsDir: string, onAuthChanged?: () => void): void;
3
+ export declare function handleAccept(req: http.IncomingMessage, res: http.ServerResponse, uploadsDir: string): void;
4
+ /** Whether an address already belongs to a member, however it was capitalized or spaced when stored. */
5
+ export declare function isMemberEmail(email: string): boolean;
4
6
  //# sourceMappingURL=invitations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAUvB,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAYtF;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,UAAU,EAAE,MAAM,EAClB,aAAa,GAAE,MAAM,IAAe,GACnC,IAAI,CA+EN"}
1
+ {"version":3,"file":"invitations.d.ts","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAWvB,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAYtF;AAED,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,UAAU,EAAE,MAAM,GACjB,IAAI,CAwEN;AAED,wGAAwG;AACxG,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD"}
@@ -3,7 +3,8 @@ import path from 'path';
3
3
  import crypto from 'crypto';
4
4
  import busboy from 'busboy';
5
5
  import { getDb } from '../db.js';
6
- import { signJwt } from '../middleware/auth.js';
6
+ import { sessionCookie } from '../middleware/auth.js';
7
+ import { normalizeEmail, EMAIL_KEY_SQL, SYNTHETIC_EMAIL_DOMAIN } from '../lib/email.js';
7
8
  import { makePasswordHash } from './auth.js';
8
9
  import { json } from '../router.js';
9
10
  export function handleVerify(req, res) {
@@ -19,7 +20,7 @@ export function handleVerify(req, res) {
19
20
  return json(res, 410, { error: 'Invitation expired or not found' });
20
21
  json(res, 200, { role: inv.role });
21
22
  }
22
- export function handleAccept(req, res, uploadsDir, onAuthChanged = () => { }) {
23
+ export function handleAccept(req, res, uploadsDir) {
23
24
  const bb = busboy({ headers: req.headers, limits: { fileSize: 2 * 1024 * 1024 } });
24
25
  const fields = {};
25
26
  let fileBuffer = null;
@@ -62,19 +63,15 @@ export function handleAccept(req, res, uploadsDir, onAuthChanged = () => { }) {
62
63
  return json(res, 410, { error: 'Invitation expired or not found' });
63
64
  const passwordHash = makePasswordHash(password);
64
65
  const displayName = fields.display_name?.trim() || (inv.email?.split('@')[0] ?? 'User');
65
- let userId;
66
- const existing = inv.email
67
- ? db.prepare('SELECT id FROM users WHERE email = ?').get(inv.email)
68
- : undefined;
69
- if (existing) {
70
- db.prepare('UPDATE users SET password_hash = ?, role = ?, display_name = ? WHERE id = ?')
71
- .run(passwordHash, inv.role, displayName, existing.id);
72
- userId = existing.id;
73
- }
74
- else {
75
- const result = db.prepare('INSERT INTO users (email, role, password_hash, display_name) VALUES (?, ?, ?, ?)').run(inv.email ?? `user_${crypto.randomBytes(4).toString('hex')}@tapflow.local`, inv.role, passwordHash, displayName);
76
- userId = result.lastInsertRowid;
66
+ // An invitation creates an account; it never changes one. Accepting for an address that already
67
+ // has an account used to overwrite that account's password, role and name — an invite link worked
68
+ // as a password reset nobody approved. The address may have joined since the invitation was made,
69
+ // so this is checked here as well as when inviting.
70
+ if (inv.email && isMemberEmail(inv.email)) {
71
+ return json(res, 409, { error: 'This email already has an account. Sign in instead.' });
77
72
  }
73
+ const result = db.prepare('INSERT INTO users (email, role, password_hash, display_name) VALUES (?, ?, ?, ?)').run(inv.email ? normalizeEmail(inv.email) : `user_${crypto.randomBytes(4).toString('hex')}${SYNTHETIC_EMAIL_DOMAIN}`, inv.role, passwordHash, displayName);
74
+ const userId = result.lastInsertRowid;
78
75
  if (fileBuffer && fileMime) {
79
76
  const ext = fileMime === 'image/png' ? '.png' : '.jpg';
80
77
  const avatarPath = path.join(uploadsDir, 'avatars', `user-${userId}${ext}`);
@@ -83,17 +80,16 @@ export function handleAccept(req, res, uploadsDir, onAuthChanged = () => { }) {
83
80
  db.prepare('UPDATE users SET avatar_url = ? WHERE id = ?').run(`/uploads/avatars/user-${userId}${ext}`, userId);
84
81
  }
85
82
  db.prepare("UPDATE invitations SET used_at = datetime('now') WHERE id = ?").run(inv.id);
86
- // Accepting for an email that already has an account overwrites its role, which can take an
87
- // Admin's agent authority away from sockets already open.
88
- if (existing)
89
- onAuthChanged();
90
83
  const user = db.prepare('SELECT email, role FROM users WHERE id = ?').get(userId);
91
- const jwtToken = signJwt({ userId, email: user.email, role: user.role });
92
- const cookie = `tapflow_token=${jwtToken}; HttpOnly; Path=/; SameSite=Lax; Max-Age=${7 * 24 * 3600}`;
84
+ const cookie = sessionCookie({ userId, email: user.email, role: user.role });
93
85
  res.writeHead(200, { 'Content-Type': 'application/json', 'Set-Cookie': cookie });
94
86
  res.end(JSON.stringify({ ok: true }));
95
87
  });
96
88
  bb.on('error', () => json(res, 500, { error: 'Failed to process request' }));
97
89
  req.pipe(bb);
98
90
  }
91
+ /** Whether an address already belongs to a member, however it was capitalized or spaced when stored. */
92
+ export function isMemberEmail(email) {
93
+ return getDb().prepare(`SELECT 1 FROM users WHERE ${EMAIL_KEY_SQL} = ?`).get(normalizeEmail(email)) !== undefined;
94
+ }
99
95
  //# sourceMappingURL=invitations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"invitations.js","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invitations.js","sourceRoot":"","sources":["../../src/api/invitations.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,22 @@
1
1
  import http from 'http';
2
+ import type { AuthChange } from '../lib/connectionAuth.js';
2
3
  import { type TapflowConfig } from '../lib/config.js';
3
4
  import { type TunnelRuntime } from '../lib/publicUrl.js';
4
5
  export declare function buildPasswordResetUrl(token: string, cfg: Pick<TapflowConfig, 'tunnel' | 'relay' | 'local'>, tunnel?: TunnelRuntime): string;
5
6
  export declare function passwordResetLinkWarning(url: string): string | null;
6
- export declare function sendPasswordResetEmail(userId: number, tunnel?: TunnelRuntime): Promise<boolean>;
7
+ /**
8
+ * Issues a reset link for a member and mails it when mail can reach them. Returns the token either way:
9
+ * the Admin who asked also gets the link, as with an invitation, so an install without SMTP can still
10
+ * reset a password.
11
+ *
12
+ * Only the newest link works. The earlier unused ones are spent here, so a link sent to the wrong place
13
+ * is dead as soon as the Admin makes another.
14
+ */
15
+ export declare function issuePasswordReset(userId: number, tunnel?: TunnelRuntime): Promise<{
16
+ token: string;
17
+ emailSent: boolean;
18
+ } | null>;
7
19
  export declare function handleVerifyReset(req: http.IncomingMessage, res: http.ServerResponse): void;
8
- export declare function handleDoReset(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
20
+ export declare function handleDoReset(req: http.IncomingMessage, res: http.ServerResponse, onAuthChanged?: (affected?: AuthChange) => void): Promise<void>;
9
21
  export declare function handleSendMemberReset(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, tunnel?: TunnelRuntime): Promise<void>;
10
22
  //# sourceMappingURL=passwordReset.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"passwordReset.d.ts","sourceRoot":"","sources":["../../src/api/passwordReset.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAQvB,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAO5E,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,EACtD,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAER;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEnE;AAED,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBrG;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAY3F;AAED,wBAAsB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBtG;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAWf"}
1
+ {"version":3,"file":"passwordReset.d.ts","sourceRoot":"","sources":["../../src/api/passwordReset.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAQvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAC1D,OAAO,EAAU,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAA0D,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAQhH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC,EACtD,MAAM,CAAC,EAAE,aAAa,GACrB,MAAM,CAER;AAED,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAAC,CA4BvD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAY3F;AAED,wBAAsB,aAAa,CACjC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,aAAa,GAAE,CAAC,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAe,GACxD,OAAO,CAAC,IAAI,CAAC,CA6Bf;AAED,wBAAsB,qBAAqB,CACzC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,IAAI,CAAC,CAiBf"}
@@ -6,7 +6,8 @@ import { makePasswordHash } from './auth.js';
6
6
  import { sendMail } from '../lib/mailer.js';
7
7
  import { json, readJson } from '../router.js';
8
8
  import { config } from '../lib/config.js';
9
- import { buildInviteBaseUrl } from '../lib/publicUrl.js';
9
+ import { buildInviteBaseUrl, forTeammates, resolvePublicBaseUrl } from '../lib/publicUrl.js';
10
+ import { SYNTHETIC_EMAIL_DOMAIN } from '../lib/email.js';
10
11
  const logger = createLogger('relay:password-reset');
11
12
  const INSECURE_RESET_LINK_WARNING = 'Password-reset email uses insecure HTTP. Reset tokens may be exposed in transit; configure HTTPS with tunnel.publicUrl or relay.url.';
12
13
  let warnedInsecureResetLink = false;
@@ -16,14 +17,29 @@ export function buildPasswordResetUrl(token, cfg, tunnel) {
16
17
  export function passwordResetLinkWarning(url) {
17
18
  return /^https:\/\//i.test(url) ? null : INSECURE_RESET_LINK_WARNING;
18
19
  }
19
- export async function sendPasswordResetEmail(userId, tunnel) {
20
+ /**
21
+ * Issues a reset link for a member and mails it when mail can reach them. Returns the token either way:
22
+ * the Admin who asked also gets the link, as with an invitation, so an install without SMTP can still
23
+ * reset a password.
24
+ *
25
+ * Only the newest link works. The earlier unused ones are spent here, so a link sent to the wrong place
26
+ * is dead as soon as the Admin makes another.
27
+ */
28
+ export async function issuePasswordReset(userId, tunnel) {
20
29
  const db = getDb();
21
30
  const user = db.prepare('SELECT id, email FROM users WHERE id = ?').get(userId);
22
31
  if (!user)
23
- return false;
32
+ return null;
24
33
  const token = crypto.randomBytes(32).toString('hex');
25
34
  const expiresAt = new Date(Date.now() + 2 * 60 * 60 * 1000).toISOString();
26
- db.prepare('INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)').run(userId, token, expiresAt);
35
+ db.transaction(() => {
36
+ db.prepare("UPDATE password_reset_tokens SET used_at = datetime('now') WHERE user_id = ? AND used_at IS NULL").run(userId);
37
+ db.prepare('INSERT INTO password_reset_tokens (user_id, token, expires_at) VALUES (?, ?, ?)').run(userId, token, expiresAt);
38
+ })();
39
+ // A member invited without an email has a made-up `@tapflow.local` address. An SMTP server may well
40
+ // accept mail for it, which would report "sent" for a message nobody can receive.
41
+ if (user.email.endsWith(SYNTHETIC_EMAIL_DOMAIN))
42
+ return { token, emailSent: false };
27
43
  const link = buildPasswordResetUrl(token, config, tunnel);
28
44
  const warning = passwordResetLinkWarning(link);
29
45
  if (warning !== null && !warnedInsecureResetLink) {
@@ -33,7 +49,7 @@ export async function sendPasswordResetEmail(userId, tunnel) {
33
49
  const html = `<p>A password reset was requested for your tapflow account.</p>
34
50
  <p><a href="${link}">Reset your password</a></p>
35
51
  <p>This link expires in 2 hours. If you did not request this, ignore this email.</p>`;
36
- return sendMail(user.email, 'Reset your tapflow password', html);
52
+ return { token, emailSent: await sendMail(user.email, 'Reset your tapflow password', html) };
37
53
  }
38
54
  export function handleVerifyReset(req, res) {
39
55
  const token = new URL(req.url ?? '/', 'http://x').searchParams.get('token');
@@ -48,7 +64,7 @@ export function handleVerifyReset(req, res) {
48
64
  return json(res, 410, { error: 'Token expired or not found' });
49
65
  json(res, 200, { ok: true });
50
66
  }
51
- export async function handleDoReset(req, res) {
67
+ export async function handleDoReset(req, res, onAuthChanged = () => { }) {
52
68
  const body = await readJson(req);
53
69
  if (!body.token || !body.password)
54
70
  return json(res, 400, { error: 'token and password required' });
@@ -61,8 +77,20 @@ export async function handleDoReset(req, res) {
61
77
  `).get(body.token);
62
78
  if (!row)
63
79
  return json(res, 410, { error: 'Token expired or not found' });
64
- db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(makePasswordHash(body.password), row.user_id);
65
- db.prepare("UPDATE password_reset_tokens SET used_at = datetime('now') WHERE id = ?").run(row.id);
80
+ db.transaction(() => {
81
+ db.prepare('UPDATE users SET password_hash = ? WHERE id = ?').run(makePasswordHash(body.password), row.user_id);
82
+ // Every outstanding link for this user, not only the one used: the password they unlock is gone.
83
+ db.prepare("UPDATE password_reset_tokens SET used_at = datetime('now') WHERE user_id = ? AND used_at IS NULL").run(row.user_id);
84
+ // A reset is how an account is recovered, and whoever held it may have minted tokens that a new
85
+ // password alone would leave working. So a reset revokes all of them, CI and agent tokens included;
86
+ // the owner issues new ones. A self-service change does not: it needs the current password, and
87
+ // revoking there would break the owner's own CI without warning.
88
+ db.prepare('DELETE FROM personal_access_tokens WHERE user_id = ?').run(row.user_id);
89
+ })();
90
+ // The new hash has already ended the user's sessions (`passwordVersion`) and the tokens are gone;
91
+ // this closes their open sockets, token-authenticated agents included, now rather than on the next
92
+ // heartbeat.
93
+ onAuthChanged({ userId: row.user_id });
66
94
  json(res, 200, { ok: true });
67
95
  }
68
96
  export async function handleSendMemberReset(req, res, params, tunnel) {
@@ -73,7 +101,14 @@ export async function handleSendMemberReset(req, res, params, tunnel) {
73
101
  const member = db.prepare('SELECT id FROM users WHERE id = ?').get(params.id);
74
102
  if (!member)
75
103
  return json(res, 404, { error: 'Member not found' });
76
- const emailSent = await sendPasswordResetEmail(member.id, tunnel);
77
- json(res, 200, { ok: true, emailSent });
104
+ const issued = await issuePasswordReset(member.id, tunnel);
105
+ if (!issued)
106
+ return json(res, 404, { error: 'Member not found' });
107
+ // The same rule as an invitation's `inviteUrl` (#788): null when the only address is a loopback one,
108
+ // and the dashboard builds the link from the browser's origin with `token` instead.
109
+ const resetUrl = forTeammates(resolvePublicBaseUrl(config, tunnel)) === null
110
+ ? null
111
+ : buildPasswordResetUrl(issued.token, config, tunnel);
112
+ json(res, 200, { ok: true, emailSent: issued.emailSent, token: issued.token, resetUrl });
78
113
  }
79
114
  //# sourceMappingURL=passwordReset.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"passwordReset.js","sourceRoot":"","sources":["../../src/api/passwordReset.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"passwordReset.js","sourceRoot":"","sources":["../../src/api/passwordReset.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import http from 'http';
2
+ import type { AuthChange } from '../lib/connectionAuth.js';
2
3
  import { type TunnelRuntime } from '../lib/publicUrl.js';
3
4
  export declare function handleListMembers(req: http.IncomingMessage, res: http.ServerResponse): void;
4
5
  export declare function handleInvite(req: http.IncomingMessage, res: http.ServerResponse, tunnel?: TunnelRuntime): Promise<void>;
5
- export declare function handleUpdateMember(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: () => void): Promise<void>;
6
- export declare function handleDeleteMember(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: () => void): void;
6
+ export declare function handleUpdateMember(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: (affected?: AuthChange) => void): Promise<void>;
7
+ export declare function handleDeleteMember(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: (affected?: AuthChange) => void): void;
7
8
  //# sourceMappingURL=team.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/api/team.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAOvB,OAAO,EAA0D,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEhH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAS3F;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA8B7H;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,MAAM,IAAe,GACnC,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,MAAM,IAAe,GACnC,IAAI,CAcN"}
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/api/team.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAKvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAK1D,OAAO,EAA0D,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAEhH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAS3F;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC7H;AAED,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,CAAC,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAe,GACxD,OAAO,CAAC,IAAI,CAAC,CAef;AAED,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,CAAC,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAe,GACxD,IAAI,CAcN"}
package/dist/api/team.js CHANGED
@@ -4,6 +4,8 @@ import { requireRole } from '../middleware/auth.js';
4
4
  import { json, readJson } from '../router.js';
5
5
  import { sendMail } from '../lib/mailer.js';
6
6
  import { config } from '../lib/config.js';
7
+ import { normalizeEmail } from '../lib/email.js';
8
+ import { isMemberEmail } from './invitations.js';
7
9
  import { buildInviteBaseUrl, forTeammates, resolvePublicBaseUrl } from '../lib/publicUrl.js';
8
10
  export function handleListMembers(req, res) {
9
11
  const auth = requireRole(req, res, ['Admin']);
@@ -18,6 +20,10 @@ export async function handleInvite(req, res, tunnel) {
18
20
  if (!auth)
19
21
  return;
20
22
  const body = await readJson(req);
23
+ const email = body.email ? normalizeEmail(body.email) : '';
24
+ // Inviting a member would only produce a link that is refused on accept.
25
+ if (email && isMemberEmail(email))
26
+ return json(res, 409, { error: 'Already a member' });
21
27
  const role = ['Admin', 'Developer', 'QA', 'Viewer'].includes(body.role ?? '')
22
28
  ? body.role
23
29
  : 'QA';
@@ -25,17 +31,17 @@ export async function handleInvite(req, res, tunnel) {
25
31
  const expiresAt = new Date(Date.now() + 7 * 24 * 3600 * 1000).toISOString();
26
32
  const db = getDb();
27
33
  db.prepare('INSERT INTO invitations (token, email, role, expires_at) VALUES (?, ?, ?, ?)')
28
- .run(token, body.email ?? null, role, expiresAt);
34
+ .run(token, email || null, role, expiresAt);
29
35
  // The dashboard copies the same link the mail carries (#788). It gets null when the only address is
30
36
  // the mail's localhost fallback or a loopback relay.url, and builds from the browser's origin instead.
31
37
  const inviteUrl = `${buildInviteBaseUrl(config, tunnel)}/invite?token=${token}`;
32
38
  const offered = forTeammates(resolvePublicBaseUrl(config, tunnel)) === null ? null : inviteUrl;
33
39
  let emailSent = false;
34
- if (body.email) {
40
+ if (email) {
35
41
  const html = `<p>You've been invited to join tapflow as <strong>${role}</strong>.</p>
36
42
  <p><a href="${inviteUrl}">Accept invitation</a></p>
37
43
  <p>This link expires in 7 days.</p>`;
38
- emailSent = await sendMail(body.email, 'You have been invited to tapflow', html);
44
+ emailSent = await sendMail(email, 'You have been invited to tapflow', html);
39
45
  }
40
46
  json(res, 201, { token, emailSent, inviteUrl: offered });
41
47
  }
@@ -52,7 +58,7 @@ export async function handleUpdateMember(req, res, params, onAuthChanged = () =>
52
58
  if (result.changes === 0)
53
59
  return json(res, 404, { error: 'Member not found' });
54
60
  // A demoted Admin's agent tokens stop working now, including on sockets already open.
55
- onAuthChanged();
61
+ onAuthChanged({ userId: Number(params.id) });
56
62
  json(res, 200, { ok: true });
57
63
  }
58
64
  export function handleDeleteMember(req, res, params, onAuthChanged = () => { }) {
@@ -67,7 +73,7 @@ export function handleDeleteMember(req, res, params, onAuthChanged = () => { })
67
73
  if (result.changes === 0)
68
74
  return json(res, 404, { error: 'Member not found' });
69
75
  // Their cookie and PATs (cascaded) stop working on HTTP at once; this closes their open sockets too.
70
- onAuthChanged();
76
+ onAuthChanged({ userId: Number(params.id) });
71
77
  json(res, 204, null);
72
78
  }
73
79
  //# sourceMappingURL=team.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/api/team.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/api/team.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import http from 'http';
2
+ import { type AuthChange } from '../lib/connectionAuth.js';
2
3
  export declare function handleListTokens(req: http.IncomingMessage, res: http.ServerResponse): void;
3
4
  export declare function handleCreateToken(req: http.IncomingMessage, res: http.ServerResponse): Promise<void>;
4
- export declare function handleRevokeToken(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: () => void): void;
5
+ export declare function handleRevokeToken(req: http.IncomingMessage, res: http.ServerResponse, params: Record<string, string>, onAuthChanged?: (affected?: AuthChange) => void): void;
5
6
  //# sourceMappingURL=tokens.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/api/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAUvB,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAS1F;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CA6C1G;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,MAAM,IAAe,GACnC,IAAI,CAaN"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../../src/api/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAA;AAKvB,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAKvE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,IAAI,CAS1F;AAED,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,GAAG,EAAE,IAAI,CAAC,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAkD1G;AAED,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,IAAI,CAAC,eAAe,EACzB,GAAG,EAAE,IAAI,CAAC,cAAc,EACxB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC9B,aAAa,GAAE,CAAC,QAAQ,CAAC,EAAE,UAAU,KAAK,IAAe,GACxD,IAAI,CAaN"}
@@ -14,10 +14,16 @@ export function handleListTokens(req, res) {
14
14
  json(res, 200, tokens);
15
15
  }
16
16
  export async function handleCreateToken(req, res) {
17
+ if (!requireAuth(req, res))
18
+ return;
19
+ const body = await readJson(req);
20
+ // Checked again once the body is in: a request can hold its body back for as long as it likes, and a
21
+ // password reset that commits meanwhile revokes every token of the account and ends its sessions. A
22
+ // token minted from the check made before the wait would outlive that reset. From here to the INSERT
23
+ // nothing awaits, so no reset can land between this check and the write.
17
24
  const auth = requireAuth(req, res);
18
25
  if (!auth)
19
26
  return;
20
- const body = await readJson(req);
21
27
  if (!body.name?.trim())
22
28
  return json(res, 400, { error: 'name required' });
23
29
  const scopes = (body.scope?.trim() || DEFAULT_SCOPE).split(',').map((s) => s.trim());
@@ -63,7 +69,7 @@ export function handleRevokeToken(req, res, params, onAuthChanged = () => { }) {
63
69
  if (result.changes === 0)
64
70
  return json(res, 404, { error: 'Token not found' });
65
71
  // Closes the sockets that were opened with it.
66
- onAuthChanged();
72
+ onAuthChanged({ userId: auth.userId, scope: 'token', patId: Number(params.id) });
67
73
  json(res, 204, null);
68
74
  }
69
75
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/api/tokens.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../../src/api/tokens.ts"],"names":[],"mappings":"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"}
@@ -5,11 +5,6 @@ export declare function verifyPassword(password: string, stored: string): boolea
5
5
  *
6
6
  * Shared so the two bootstrap paths cannot disagree about what "already initialized" means.
7
7
  *
8
- * They are not identical in every respect: the boot path trims `TAPFLOW_ADMIN_EMAIL` and the HTTP
9
- * one stores `body.email` as sent, so the same address with surrounding whitespace produces two
10
- * different rows and the HTTP one never matches `handleLogin`'s `WHERE email = ?`. Normalising
11
- * inside this function would change what a shipped endpoint stores, so it is a decision rather than
12
- * a fix here — tracked separately.
13
8
  */
14
9
  export declare function isInitialized(): boolean;
15
10
  /** Why `createAdminAccount` refused, or `ok`. The caller turns this into an HTTP status or a log line. */
@@ -1 +1 @@
1
- {"version":3,"file":"adminAccount.d.ts","sourceRoot":"","sources":["../../src/lib/adminAccount.ts"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQxE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAGvC;AAED,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,gBAAgB,GAAG,oBAAoB,CAAA;AAE9E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAOrF"}
1
+ {"version":3,"file":"adminAccount.d.ts","sourceRoot":"","sources":["../../src/lib/adminAccount.ts"],"names":[],"mappings":"AAiBA,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIzD;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAQxE;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAGvC;AAED,0GAA0G;AAC1G,MAAM,MAAM,iBAAiB,GAAG,IAAI,GAAG,gBAAgB,GAAG,oBAAoB,CAAA;AAE9E;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,iBAAiB,CAUrF"}
@@ -1,5 +1,6 @@
1
1
  import crypto from 'crypto';
2
2
  import { getDb } from '../db.js';
3
+ import { normalizeEmail } from './email.js';
3
4
  // The first-owner rules, and the password hashing they need.
4
5
  //
5
6
  // **Why this is in `lib/` and not beside the HTTP handlers.** Two callers need it: `api/auth.ts`
@@ -32,11 +33,6 @@ export function verifyPassword(password, stored) {
32
33
  *
33
34
  * Shared so the two bootstrap paths cannot disagree about what "already initialized" means.
34
35
  *
35
- * They are not identical in every respect: the boot path trims `TAPFLOW_ADMIN_EMAIL` and the HTTP
36
- * one stores `body.email` as sent, so the same address with surrounding whitespace produces two
37
- * different rows and the HTTP one never matches `handleLogin`'s `WHERE email = ?`. Normalising
38
- * inside this function would change what a shipped endpoint stores, so it is a decision rather than
39
- * a fix here — tracked separately.
40
36
  */
41
37
  export function isInitialized() {
42
38
  const { n } = getDb().prepare('SELECT COUNT(*) as n FROM users').get();
@@ -49,13 +45,16 @@ export function isInitialized() {
49
45
  * differently: 403 over HTTP, a silent no-op on every restart at boot.
50
46
  */
51
47
  export function createAdminAccount(email, password) {
52
- if (!email || !password)
48
+ // Normalized here rather than by each caller, so the boot path and `POST /auth/init` store the same
49
+ // value for the same address (#715).
50
+ const stored = email ? normalizeEmail(email) : '';
51
+ if (!stored || !password)
53
52
  return 'missing-fields';
54
53
  if (password.length < 8)
55
54
  return 'password-too-short';
56
55
  getDb()
57
56
  .prepare('INSERT INTO users (email, display_name, role, password_hash) VALUES (?, ?, ?, ?)')
58
- .run(email, 'Admin', 'Admin', makePasswordHash(password));
57
+ .run(stored, 'Admin', 'Admin', makePasswordHash(password));
59
58
  return 'ok';
60
59
  }
61
60
  //# sourceMappingURL=adminAccount.js.map