@rsktash/beads-ui 0.1.25 → 0.1.28

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/index.html CHANGED
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Beads UI</title>
7
- <script type="module" crossorigin src="/assets/index-Cw2Wd_3N.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-DfzvbsTp.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/assets/index-Cgg9X2NI.css">
9
9
  </head>
10
10
  <body style="background: #FDFBF7; color: #1A1A1A;">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rsktash/beads-ui",
3
- "version": "0.1.25",
3
+ "version": "0.1.28",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/rsktash/beads-ui.git"
package/server/app.js CHANGED
@@ -5,7 +5,7 @@ import express from 'express';
5
5
  import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import { createRequire } from 'node:module';
8
- import { authMiddleware, isAuthEnabled, loadUsers, login } from './auth.js';
8
+ import { authMiddleware, isAuthEnabled, loadUsers, login, verifyToken } from './auth.js';
9
9
 
10
10
  const require = createRequire(import.meta.url);
11
11
  const pkg = require('../package.json');
@@ -63,7 +63,9 @@ export function createApp(config) {
63
63
  res.json({ ok: true, authEnabled: false });
64
64
  return;
65
65
  }
66
- const user = /** @type {any} */ (req).user;
66
+ const authHeader = req.headers.authorization;
67
+ const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
68
+ const user = token ? verifyToken(token) : null;
67
69
  if (!user) {
68
70
  res.status(401).json({ ok: false, error: 'Unauthorized' });
69
71
  return;
package/server/auth.js CHANGED
@@ -2,8 +2,9 @@
2
2
  * Optional authentication module.
3
3
  * When a users config file exists and contains users, auth is enforced.
4
4
  * When no users are configured, auth is bypassed entirely.
5
+ * Simple session tokens stored in memory — no JWT complexity.
5
6
  */
6
- import { createHmac, randomBytes } from 'node:crypto';
7
+ import { randomBytes } from 'node:crypto';
7
8
  import fs from 'node:fs';
8
9
  import { debug } from './logging.js';
9
10
 
@@ -12,13 +13,12 @@ const log = debug('auth');
12
13
  /** @type {{ username: string, password: string, role?: string }[]} */
13
14
  let users = [];
14
15
 
15
- /** JWT secret generated per server start when no env override */
16
- const JWT_SECRET = process.env.BEADS_UI_JWT_SECRET || randomBytes(32).toString('hex');
17
- const TOKEN_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
16
+ /** @type {Map<string, { username: string, role: string }>} */
17
+ const sessions = new Map();
18
18
 
19
19
  /**
20
20
  * Load users from config file.
21
- * File path: BEADS_UI_AUTH_FILE env var, or ./beads-ui-users.json in cwd.
21
+ * @param {string} [filePath] - BEADS_UI_AUTH_FILE env var
22
22
  */
23
23
  export function loadUsers() {
24
24
  const filePath = process.env.BEADS_UI_AUTH_FILE || '';
@@ -31,7 +31,7 @@ export function loadUsers() {
31
31
  const parsed = JSON.parse(raw);
32
32
  users = Array.isArray(parsed.users) ? parsed.users : [];
33
33
  log('loaded %d users from %s', users.length, filePath);
34
- } catch (err) {
34
+ } catch {
35
35
  log('no users file at %s — auth disabled', filePath);
36
36
  users = [];
37
37
  }
@@ -42,41 +42,13 @@ export function isAuthEnabled() {
42
42
  return users.length > 0;
43
43
  }
44
44
 
45
-
46
45
  /**
47
- * Create a JWT-like token (HMAC-SHA256 signed).
48
- * @param {{ username: string, role: string }} payload
49
- * @returns {string}
50
- */
51
- function createToken(payload) {
52
- const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
53
- const body = Buffer.from(JSON.stringify({
54
- ...payload,
55
- iat: Date.now(),
56
- exp: Date.now() + TOKEN_EXPIRY_MS
57
- })).toString('base64url');
58
- const sig = createHmac('sha256', JWT_SECRET).update(`${header}.${body}`).digest('base64url');
59
- return `${header}.${body}.${sig}`;
60
- }
61
-
62
- /**
63
- * Verify and decode a token.
46
+ * Verify a session token.
64
47
  * @param {string} token
65
48
  * @returns {{ username: string, role: string } | null}
66
49
  */
67
50
  export function verifyToken(token) {
68
- try {
69
- const parts = token.split('.');
70
- if (parts.length !== 3) return null;
71
- const [header, body, sig] = parts;
72
- const expected = createHmac('sha256', JWT_SECRET).update(`${header}.${body}`).digest('base64url');
73
- if (sig !== expected) return null;
74
- const payload = JSON.parse(Buffer.from(body, 'base64url').toString());
75
- if (payload.exp && payload.exp < Date.now()) return null;
76
- return { username: payload.username, role: payload.role };
77
- } catch {
78
- return null;
79
- }
51
+ return sessions.get(token) || null;
80
52
  }
81
53
 
82
54
  /**
@@ -90,13 +62,14 @@ export function login(username, password) {
90
62
  if (!user) return { ok: false, error: 'Invalid credentials' };
91
63
  if (password !== user.password) return { ok: false, error: 'Invalid credentials' };
92
64
  const role = user.role || '';
93
- const token = createToken({ username, role });
65
+ const token = randomBytes(32).toString('hex');
66
+ sessions.set(token, { username, role });
94
67
  return { ok: true, token, user: { username, role } };
95
68
  }
96
69
 
97
70
  /**
98
71
  * Express middleware — skips auth if no users configured.
99
- * Protects all routes except /api/auth/* and static assets.
72
+ * Protects API routes only. Static assets and SPA routes pass through.
100
73
  * @param {import('express').Request} req
101
74
  * @param {import('express').Response} res
102
75
  * @param {import('express').NextFunction} next
@@ -104,12 +77,10 @@ export function login(username, password) {
104
77
  export function authMiddleware(req, res, next) {
105
78
  if (!isAuthEnabled()) return next();
106
79
 
107
- // Allow auth endpoints and health check
80
+ // Allow auth endpoints, health check, and config
108
81
  if (req.path.startsWith('/api/auth') || req.path === '/healthz' || req.path === '/api/config') return next();
109
82
 
110
83
  // Allow static assets and SPA routes (auth enforced client-side)
111
- const ext = req.path.split('.').pop();
112
- if (ext && ['js', 'css', 'html', 'svg', 'png', 'ico', 'woff', 'woff2', 'ttf'].includes(ext)) return next();
113
84
  if (!req.path.startsWith('/api/')) return next();
114
85
 
115
86
  // Check Authorization header
@@ -119,11 +90,11 @@ export function authMiddleware(req, res, next) {
119
90
  res.status(401).json({ ok: false, error: 'Unauthorized' });
120
91
  return;
121
92
  }
122
- const decoded = verifyToken(token);
123
- if (!decoded) {
93
+ const user = verifyToken(token);
94
+ if (!user) {
124
95
  res.status(401).json({ ok: false, error: 'Invalid token' });
125
96
  return;
126
97
  }
127
- /** @type {any} */ (req).user = decoded;
98
+ /** @type {any} */ (req).user = user;
128
99
  next();
129
100
  }