Skip to content

feat: add JWT expiry validation and refactor magic numbers#612

Merged
MickLesk merged 2 commits into
community-scripts:mainfrom
Vivelis:fix/ws-auth-#611
Jun 13, 2026
Merged

feat: add JWT expiry validation and refactor magic numbers#612
MickLesk merged 2 commits into
community-scripts:mainfrom
Vivelis:fix/ws-auth-#611

Conversation

@Vivelis

@Vivelis Vivelis commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Pull Request Summary

✍️ Description

Fix: WebSocket 401 Unauthorized — JWT auth mismatch due to .env parsing

Problem: When installing an LXC container, the WebSocket connection to ws://host:3000/ws/script-execution was rejected with HTTP 401 even though the user was authenticated with a valid auth-token cookie.

Root cause: getAuthConfig() and getJwtSecret() in src/lib/auth.ts parsed the .env file directly with regex instead of reading from process.env (already populated by dotenv.config()). On startup, if the regex failed to match (encoding, whitespace, quotes), getJwtSecret() auto-generated a new secret and overwrote the .env file. On the next restart, dotenv loaded the new secret, but the session cookie was signed with the old one → verification failed → 401.

Changes

src/env.js — Fixed zod schema for auth variables:

  • AUTH_ENABLED, AUTH_SETUP_COMPLETED: changed from z.string() to z.coerce.boolean() (handles "true" / "false" strings properly)
  • Added AUTH_SESSION_DURATION_DAYS: z.coerce.number().int().positive()

src/lib/auth.ts — Simplified config reading, removed regex fallbacks:

  • getJwtSecret(): reads exclusively from process.env.JWT_SECRET, auto-generates only when absent. No .env file regex parsing.
  • getAuthConfig(): reads all 5 auth variables from process.env directly with inline conversions. Removed readString/readBool/readInt helpers and the .env file fallback regex.
  • Write functions (updateAuthCredentials, setSetupCompleted, etc.) unchanged — their regex usage is necessary for in-place .env editing.

server.js — Logging & magic number cleanup:

  • isWebSocketUpgradeAuthorized(): added 3 security-safe warning logs (missing auth-token cookie, token expired, token verification failed) without exposing token contents
  • Fixed dynamic require() of decodeToken → static ES import
  • Replaced 9 magic numbers (1000) with 3 named constants: JWT_EXP_MS_FACTOR, OUTPUT_BUFFER_MAX_LENGTH, BACKUP_UPDATE_DELAY_MS

🔗 Related PR / Issue

Fixes: #611

✅ Prerequisites (X in brackets)

  • Self-review completed – Code follows project standards.
  • Tested thoroughly – Changes work as expected.
  • No security risks – No hardcoded secrets, unnecessary privilege escalations, or permission issues.

🛠️ Type of Change (X in brackets)

  • 🐞 Bug fix – Resolves an issue without breaking functionality.
  • New feature – Adds new, non-breaking functionality.
  • 💥 Breaking change – Alters existing functionality in a way that may require updates.

- Decode JWT in WebSocket auth to reject expired tokens and log auth failures.
- Introduce constants `OUTPUT_BUFFER_MAX_LENGTH`, `BACKUP_UPDATE_DELAY_MS`, and `JWT_EXP_MS_FACTOR`.
- Replace hardcoded buffer length and delay values with the new constants.
- Coerce `AUTH_ENABLED` and `AUTH_SETUP_COMPLETED` to boolean and add `AUTH_SESSION_DURATION_DAYS` env.
@MickLesk
MickLesk merged commit 62bcfe0 into community-scripts:main Jun 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Launching an install script shows a disconnected shell

2 participants