Skip to content

Hhh#3978

Closed
MeowMeowMeow123412345 wants to merge 22 commits into
openfrontio:mainfrom
MeowMeowMeow123412345:main
Closed

Hhh#3978
MeowMeowMeow123412345 wants to merge 22 commits into
openfrontio:mainfrom
MeowMeowMeow123412345:main

Conversation

@MeowMeowMeow123412345
Copy link
Copy Markdown

@MeowMeowMeow123412345 MeowMeowMeow123412345 commented May 20, 2026

If this PR fixes an issue, link it below. If not, delete these two lines.
Resolves #(issue number)

Description:

Describe the PR.

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

DISCORD_USERNAME

Refactor getApiBase function to prioritize API_DOMAIN environment variable for API base URL.
Add magic credential handling for token login
Refactor JWT audience and issuer methods to improve handling of environment variables and API domain parsing.
Added registration of authentication routes to the server.
Add condition to handle root subdomain case
Add condition to handle root subdomain case
Added SUBDOMAIN variable for deployment configuration.
@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 20, 2026

Review Change Stack

Walkthrough

This PR adds Magic SDK-based email authentication to the application. It configures the client to send magic links directly via Magic SDK, implements a complete server-side auth system with JWT signing and refresh tokens, refactors configuration to support flexible API domains and JWT audience/issuer settings, and updates deployment scripts to handle root-domain and subdomain routing.

Changes

Deployment Subdomain Normalization

Layer / File(s) Summary
Subdomain normalization in build, deploy, and Docker routing
build-deploy.sh, deploy.sh, update.sh, example.env
Build wrapper and deployment script normalize SUBDOMAIN values of root or @ to empty string. Docker container names and Traefik routing rules conditionally use empty subdomain for root domain or ${SUBDOMAIN}.${DOMAIN} otherwise. Example env includes SUBDOMAIN guidance.

Environment & Configuration Setup

Layer / File(s) Summary
Environment variable exposure and TypeScript/Vite setup
src/core/configuration/Env.ts, tsconfig.json, vite.config.ts, package.json
Env object now exposes API_DOMAIN, JWT_ISSUER, JWT_AUDIENCE, MAGIC_PUBLISHABLE_KEY, and CORS_ORIGINS getters. TypeScript config adds src/* path alias. Vite injects MAGIC_PUBLISHABLE_KEY into builds. npm scripts pass --tsconfig to tsx.
JWT audience and issuer derivation from environment
src/core/configuration/DefaultConfig.ts
jwtAudience() now computes from Env.JWT_AUDIENCE or normalized Env.API_DOMAIN, falling back to openfront.io. jwtIssuer() derives from audience with special handling for localhost, IPs, and api.* domains. Helper parseApiDomain() normalizes domain input.

Client-Side Magic Authentication

Layer / File(s) Summary
API domain configuration and endpoint resolution
src/client/Api.ts
getApiBase() now reads process.env.API_DOMAIN, trims whitespace, detects existing schemes (http://, https://), normalizes localhost/loopback to http://, and defaults other domains to https://. Preserves fallback to audience-based logic when env is absent.
Magic SDK initialization and email-link authentication
src/client/Auth.ts
Imports Magic SDK. New getMagicClient() helper reads MAGIC_PUBLISHABLE_KEY and returns Magic client or undefined. sendMagicLink() calls magic.auth.loginWithEmailLink() instead of backend endpoint; returns false when publishable key is missing.
Magic credential token handling in page routing
src/client/Main.ts
handleUrl() reads magic_credential from query parameters, removes query string from URL history, opens token-login modal with the credential, and exits early. Preserves existing hash-based token-login flow.

Server-Side Auth Routes & JWT System

Layer / File(s) Summary
Auth module initialization and utilities
src/server/AuthRoutes.ts
Initializes Magic admin SDK from MAGIC_SECRET_KEY. Generates and caches Ed25519 JWK for public JWKS endpoint. Maintains in-memory maps for users (by email) and refresh tokens (by token string, with expiry). Parses and validates CORS origins against CORS_ORIGINS env.
User ID and token generation
src/server/AuthRoutes.ts
Generates persistent user IDs (via SHA-256), public user IDs (UUIDs), and refresh tokens (random). User-creation helper reuses in-memory user by email or creates new record.
JWT signing and refresh-cookie management
src/server/AuthRoutes.ts
Signs JWTs with EdDSA private key, setting iss, aud, sub, and exp claims. Refresh-cookie setter marks cookies as HttpOnly, Secure, and SameSite=Lax.
Magic token verification and user response building
src/server/AuthRoutes.ts
Verifies Magic login tokens via admin SDK, extracts email from metadata, validates issuer/audience claims, and builds UserMeResponse from in-memory user data or returns error.
Auth route handlers and middleware
src/server/AuthRoutes.ts
Registers routes: GET /users/@me (validates Bearer token, returns user), POST /auth/login/token (verifies Magic token, sets refresh cookie), POST /auth/refresh (rotates token, sets new cookie), POST /auth/logout (clears cookie). Handles OPTIONS preflight and serves JWKS at /.well-known/jwks.json. CORS headers middleware validates request origin.
Express app registration and cookie middleware
src/server/Master.ts, src/server/Worker.ts
Master module imports and asynchronously registers registerAuthRoutes(app) at startup (top-level await). Worker module imports and registers cookie-parser middleware for HTTP cookie parsing.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Client
  participant MagicSDK
  participant Server as Server<br/>(AuthRoutes)
  participant MagicAdmin as Magic<br/>(Admin)

  User->>Client: Enter email in login form
  Client->>MagicSDK: sendMagicLink(email)
  MagicSDK->>User: Send email with magic link
  
  User->>User: Click magic link<br/>(magic_credential in URL)
  User->>Client: Navigate to app with<br/>magic_credential param
  Client->>Client: handleUrl() extracts credential
  Client->>Client: openWithToken(credential)
  Client->>Server: POST /auth/login/token<br/>(login-token querystring)
  
  Server->>MagicAdmin: Verify login token
  MagicAdmin-->>Server: email + metadata
  Server->>Server: Create/find user
  Server->>Server: Generate JWT + refresh token
  Server->>Server: Set refresh cookie
  Server-->>Client: { email, id, ... }
  
  Client->>Client: Store JWT in state
  Client->>Server: GET /users/@me<br/>(Authorization: Bearer JWT)
  Server->>Server: Verify token
  Server-->>Client: { email, id, ... }
  Client->>User: Render authenticated UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Poem

🔐 Magic links now light the way,

EdDSA keys sign claims to stay,

Refresh tokens spin and rotate with grace,

While cookies track each user's place.

Root domains heed the deploy,

Authentication's newfound joy! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is completely empty of meaningful content—only template placeholders remain unfilled. Replace template text with a clear description of the changes: Magic SDK integration for authentication, JWT/refresh token handling, environment variable refactoring, and subdomain deployment fixes.
Title check ❓ Inconclusive The title 'Hhh' is vague and does not convey any meaningful information about the changeset. Replace with a clear, descriptive title that summarizes the main change, such as 'Add Magic SDK authentication and JWT token handling' or 'Integrate Magic authentication with JWT and environment-based configuration'.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (4)
src/client/Auth.ts (1)

182-189: 💤 Low value

Consider caching the Magic client instance.

Creating a new Magic instance on every call to getMagicClient() is wasteful. The Magic SDK recommends reusing the client. A simple singleton pattern would avoid repeated initialization overhead.

♻️ Proposed singleton pattern
+let __magicClient: Magic | null = null;
+
 function getMagicClient(): Magic | null {
+  if (__magicClient) {
+    return __magicClient;
+  }
   const publishableKey = process.env.MAGIC_PUBLISHABLE_KEY;
   if (!publishableKey) {
     console.error("MAGIC_PUBLISHABLE_KEY is not configured");
     return null;
   }
-  return new Magic(publishableKey);
+  __magicClient = new Magic(publishableKey);
+  return __magicClient;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/Auth.ts` around lines 182 - 189, The getMagicClient function
currently constructs a new Magic(...) on every call; change it to implement a
simple singleton by storing the created Magic instance in a module-level
variable (e.g., let magicClient: Magic | null = null) and returning that cached
instance on subsequent calls; keep the same publishableKey check (initialize
magicClient only if publishableKey exists) and ensure getMagicClient returns the
cached magicClient (or null) instead of creating a new Magic each time.
src/server/AuthRoutes.ts (3)

198-200: ⚡ Quick win

Linear scan to find user by persistentId is inefficient.

Array.from(usersByEmail.values()).find(...) iterates all users. Consider adding a second map keyed by persistentId for O(1) lookups.

Proposed fix - add a second map
+const usersByPersistentId = new Map<
+  string,
+  { email: string; persistentId: string; publicId: string }
+>();
+
 function createUser(email: string) {
   const existing = usersByEmail.get(email);
   if (existing) return existing;

   const user = {
     email,
     persistentId: createPersistentId(),
     publicId: createPublicId(),
   };
   usersByEmail.set(email, user);
+  usersByPersistentId.set(user.persistentId, user);
   return user;
 }

Then replace Array.from(usersByEmail.values()).find(...) with usersByPersistentId.get(persistentId).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` around lines 198 - 200, The current lookup uses
Array.from(usersByEmail.values()).find(...) which does a linear scan to match
entry.persistentId === result.persistentId; introduce a second Map (e.g.,
usersByPersistentId) keyed by persistentId, populate/update it wherever users
are created/updated/removed, and replace the linear search with a constant-time
lookup like usersByPersistentId.get(result.persistentId) (ensure consistency
between usersByEmail and usersByPersistentId on all user lifecycle operations).

57-68: ⚡ Quick win

getAllowedOrigins() is called on every request, re-parsing the env var each time.

For better performance, parse and cache allowed origins once at startup.

Proposed fix
+const allowedOriginsCache = new Set<string>();
+
+function initAllowedOrigins(): void {
+  const env = process.env.CORS_ORIGINS;
+  if (env) {
+    env.split(",").forEach((value) => {
+      const trimmed = value.trim();
+      if (trimmed.length > 0) {
+        allowedOriginsCache.add(trimmed);
+      }
+    });
+  }
+}
+
 function getAllowedOrigins(): Set<string> {
-  const origins = new Set<string>();
-  const env = process.env.CORS_ORIGINS;
-  if (env) {
-    env.split(",").forEach((value) => {
-      const trimmed = value.trim();
-      if (trimmed.length > 0) {
-        origins.add(trimmed);
-      }
-    });
-  }
-  return origins;
+  return allowedOriginsCache;
 }
+
+// Call at module load
+initAllowedOrigins();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` around lines 57 - 68, The getAllowedOrigins()
function re-parses process.env.CORS_ORIGINS on every request; change it to parse
once and return a cached Set: create a top-level cachedOrigins (or
lazy-initialized via an initAllowedOrigins function) that splits, trims and
populates the Set at module load (or on first call) and have getAllowedOrigins()
simply return that cached Set; update any call sites to use the new cached value
(keep function name getAllowedOrigins if used widely) so runtime requests no
longer re-parse the environment variable.

104-112: 💤 Low value

Cookie with sameSite: "none" requires careful consideration.

Using sameSite: "none" allows cross-site cookie sending, which is needed for cross-origin auth flows but increases CSRF exposure. This is fine if intentional for your architecture (client on different domain than API). Just verify this matches your deployment model.

Also, note that clearCookie on Line 271-275 should use matching options (including path if you set one) for reliable cookie removal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` around lines 104 - 112, The current
setRefreshCookie function sets sameSite: "none" which permits cross-site
cookies—confirm this is intentional for your cross-origin auth flow or make it
configurable (e.g., derive sameSite from an env/config flag) in setRefreshCookie
(and document it); also ensure cookie clearing uses identical options so
clearCookie reliably removes the cookie—update the clearCookie call to include
the same secure, sameSite, httpOnly, maxAge/path options (and include path if
you set one) so the refreshCookieName is cleared consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@example.env`:
- Line 10: The inline comment after the SUBDOMAIN assignment can break dotenv
parsing (your export $(grep ... | xargs) loader) — move the comment off the
assignment so the line contains only the key/value (SUBDOMAIN=www) and put the
explanatory text on its own line prefixed with #; update the SUBDOMAIN line in
the file (referencing the SUBDOMAIN variable) so it contains no inline comment.

In `@package.json`:
- Around line 74-76: The packages "cookie-parser" and "`@magic-sdk/admin`" are
used at runtime (imported in server code) but currently listed under
devDependencies; move both "cookie-parser" and "`@magic-sdk/admin`" entries from
devDependencies into dependencies in package.json so production installs include
them and server files that import those packages (e.g., the server Worker and
AuthRoutes modules) won't fail at runtime.

In `@src/core/configuration/DefaultConfig.ts`:
- Around line 109-154: Add unit tests covering all jwtAudience()/jwtIssuer()
derivation branches: test explicit overrides using Env.JWT_AUDIENCE and
Env.JWT_ISSUER, scheme stripping when parseApiDomain() returns a URL (use
Env.API_DOMAIN), audience values that already include http/https, localhost and
IP forms (e.g., "localhost", "localhost:3000", "127.0.0.1", "[::1]"), audiences
starting with "api.", and the default fallback; exercise
DefaultConfig.jwtAudience, DefaultConfig.jwtIssuer and parseApiDomain behavior
by stubbing Env variables and asserting expected outputs for each branch.

In `@src/server/AuthRoutes.ts`:
- Around line 14-29: The in-memory Maps refreshTokens and usersByEmail in
AuthRoutes.ts are process-local and must be replaced with a persistent/shared
store to survive restarts and scale horizontally; implement a small storage
abstraction (e.g., TokenStore/UserStore interface) and replace direct Map
accesses in functions that read/write refreshTokens and usersByEmail with async
calls to a Redis-backed or DB-backed implementation that supports TTL for tokens
and atomic operations for lookups/invalidations, migrate existing code paths to
await the new store methods, and provide a default in-memory fallback for local
dev only.
- Line 6: Remove the unused import TokenPayloadSchema from the import list in
AuthRoutes.ts so only used symbols (e.g., UserMeResponse) are imported; update
the import line that currently reads import { TokenPayloadSchema, UserMeResponse
} from "../core/ApiSchemas" to omit TokenPayloadSchema to satisfy ESLint and
avoid confusion.
- Line 31: The code instantiates the Magic SDK unconditionally (const magic =
new Magic(magicSecretKey)) which will cause confusing runtime failures when
magicSecretKey (MAGIC_SECRET_KEY) is empty; update the startup logic to validate
magicSecretKey before creating the Magic instance and fail fast with a clear
error (throw or process.exit) if it's missing, or guard all uses of magic (e.g.,
magic.users.getMetadataByToken) behind a conditional that checks the key so
initialization only occurs when a valid key is present.

In `@src/server/Worker.ts`:
- Line 2: Remove the unused cookie-parser middleware from Worker.ts: delete the
import cookieParser and remove the app.use(cookieParser()) call so Worker no
longer loads cookie parsing on every request; search for the symbols
cookieParser and the app.use(cookieParser()) invocation in Worker.ts (and ensure
no other modules rely on req.cookies) before committing the removal.

---

Nitpick comments:
In `@src/client/Auth.ts`:
- Around line 182-189: The getMagicClient function currently constructs a new
Magic(...) on every call; change it to implement a simple singleton by storing
the created Magic instance in a module-level variable (e.g., let magicClient:
Magic | null = null) and returning that cached instance on subsequent calls;
keep the same publishableKey check (initialize magicClient only if
publishableKey exists) and ensure getMagicClient returns the cached magicClient
(or null) instead of creating a new Magic each time.

In `@src/server/AuthRoutes.ts`:
- Around line 198-200: The current lookup uses
Array.from(usersByEmail.values()).find(...) which does a linear scan to match
entry.persistentId === result.persistentId; introduce a second Map (e.g.,
usersByPersistentId) keyed by persistentId, populate/update it wherever users
are created/updated/removed, and replace the linear search with a constant-time
lookup like usersByPersistentId.get(result.persistentId) (ensure consistency
between usersByEmail and usersByPersistentId on all user lifecycle operations).
- Around line 57-68: The getAllowedOrigins() function re-parses
process.env.CORS_ORIGINS on every request; change it to parse once and return a
cached Set: create a top-level cachedOrigins (or lazy-initialized via an
initAllowedOrigins function) that splits, trims and populates the Set at module
load (or on first call) and have getAllowedOrigins() simply return that cached
Set; update any call sites to use the new cached value (keep function name
getAllowedOrigins if used widely) so runtime requests no longer re-parse the
environment variable.
- Around line 104-112: The current setRefreshCookie function sets sameSite:
"none" which permits cross-site cookies—confirm this is intentional for your
cross-origin auth flow or make it configurable (e.g., derive sameSite from an
env/config flag) in setRefreshCookie (and document it); also ensure cookie
clearing uses identical options so clearCookie reliably removes the
cookie—update the clearCookie call to include the same secure, sameSite,
httpOnly, maxAge/path options (and include path if you set one) so the
refreshCookieName is cleared consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2a3acf04-0e7a-4f0d-89a1-dc6dbbb8a7cc

📥 Commits

Reviewing files that changed from the base of the PR and between 513057a and 607fe90.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • resources/flags/Quebec.svg is excluded by !**/*.svg
📒 Files selected for processing (15)
  • build-deploy.sh
  • deploy.sh
  • example.env
  • package.json
  • src/client/Api.ts
  • src/client/Auth.ts
  • src/client/Main.ts
  • src/core/configuration/DefaultConfig.ts
  • src/core/configuration/Env.ts
  • src/server/AuthRoutes.ts
  • src/server/Master.ts
  • src/server/Worker.ts
  • tsconfig.json
  • update.sh
  • vite.config.ts

Comment thread example.env
GHCR_TOKEN=your_docker_token_here

DOMAIN=your-domain.com
SUBDOMAIN=www # Set to blank for root domain deployment, or use 'root' when calling deploy scripts.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move the comment to its own line to avoid .env parse breakage.

On Line 10, inline comments can break your current loader (export $(grep ... | xargs)) by turning comment words into invalid export tokens.

Suggested fix
-SUBDOMAIN=www  # Set to blank for root domain deployment, or use 'root' when calling deploy scripts.
+# Set to blank for root domain deployment, or use 'root' when calling deploy scripts.
+SUBDOMAIN=www
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SUBDOMAIN=www # Set to blank for root domain deployment, or use 'root' when calling deploy scripts.
# Set to blank for root domain deployment, or use 'root' when calling deploy scripts.
SUBDOMAIN=www
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example.env` at line 10, The inline comment after the SUBDOMAIN assignment
can break dotenv parsing (your export $(grep ... | xargs) loader) — move the
comment off the assignment so the line contains only the key/value
(SUBDOMAIN=www) and put the explanatory text on its own line prefixed with #;
update the SUBDOMAIN line in the file (referencing the SUBDOMAIN variable) so it
contains no inline comment.

Comment thread package.json
Comment on lines +74 to +76
"cookie-parser": "^1.4.7",
"@magic-sdk/admin": "^2.0.0",
"magic-sdk": "^6.0.0",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether these packages are imported by runtime server code
rg -n -C2 '`@magic-sdk/admin`|cookie-parser|magic-sdk' src/server src/client

# Show where they are currently declared
python - <<'PY'
import json
from pathlib import Path
pkg = json.loads(Path("package.json").read_text())
for name in ["cookie-parser", "`@magic-sdk/admin`", "magic-sdk"]:
    loc = "dependencies" if name in pkg.get("dependencies", {}) else "devDependencies" if name in pkg.get("devDependencies", {}) else "missing"
    print(f"{name}: {loc}")
PY

Repository: openfrontio/OpenFrontIO

Length of output: 1000


Move server runtime packages to dependencies.

cookie-parser and @magic-sdk/admin are imported in server code (src/server/Worker.ts and src/server/AuthRoutes.ts). When production deploys with npm install --production, packages in devDependencies are skipped, causing runtime errors. Move them to dependencies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 74 - 76, The packages "cookie-parser" and
"`@magic-sdk/admin`" are used at runtime (imported in server code) but currently
listed under devDependencies; move both "cookie-parser" and "`@magic-sdk/admin`"
entries from devDependencies into dependencies in package.json so production
installs include them and server files that import those packages (e.g., the
server Worker and AuthRoutes modules) won't fail at runtime.

Comment on lines +109 to +154

private parseApiDomain(): string | undefined {
const apiDomain = Env.API_DOMAIN?.trim();
if (!apiDomain) return undefined;
return apiDomain.replace(/\/+$/, "");
}

jwtAudience(): string {
if (Env.JWT_AUDIENCE?.trim()) {
return Env.JWT_AUDIENCE.trim();
}

const apiDomain = this.parseApiDomain();
if (apiDomain) {
return apiDomain.replace(/^https?:\/\//, "");
}

return "openfront.io";
}

jwtIssuer(): string {
if (Env.JWT_ISSUER?.trim()) {
return Env.JWT_ISSUER.trim().replace(/\/+$/, "");
}

const audience = this.jwtAudience().trim();
if (audience.startsWith("http://") || audience.startsWith("https://")) {
return audience.replace(/\/+$/, "");
}

if (
audience === "localhost" ||
audience.startsWith("localhost:") ||
audience.startsWith("127.") ||
audience.startsWith("0.") ||
audience.startsWith("[::1]")
) {
return `http://${audience}`;
}

if (audience.startsWith("api.")) {
return `https://${audience}`;
}

return `https://api.${audience}`;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add focused tests for JWT audience/issuer derivation paths.

This core config change rewires auth-critical jwtAudience() / jwtIssuer() behavior, but no tests are included for these new branches (explicit env override, scheme stripping, localhost/IP handling, api.*, fallback).

As per coding guidelines: “All changes to src/core/ must include tests”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/configuration/DefaultConfig.ts` around lines 109 - 154, Add unit
tests covering all jwtAudience()/jwtIssuer() derivation branches: test explicit
overrides using Env.JWT_AUDIENCE and Env.JWT_ISSUER, scheme stripping when
parseApiDomain() returns a URL (use Env.API_DOMAIN), audience values that
already include http/https, localhost and IP forms (e.g., "localhost",
"localhost:3000", "127.0.0.1", "[::1]"), audiences starting with "api.", and the
default fallback; exercise DefaultConfig.jwtAudience, DefaultConfig.jwtIssuer
and parseApiDomain behavior by stubbing Env variables and asserting expected
outputs for each branch.

Comment thread src/server/AuthRoutes.ts
import { randomBytes, randomUUID } from "crypto";
import { exportJWK, generateKeyPair, SignJWT } from "jose";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
import { TokenPayloadSchema, UserMeResponse } from "../core/ApiSchemas";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Remove unused import TokenPayloadSchema.

ESLint flags this as unused. Clean it up to avoid confusion.

Proposed fix
-import { TokenPayloadSchema, UserMeResponse } from "../core/ApiSchemas";
+import { UserMeResponse } from "../core/ApiSchemas";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { TokenPayloadSchema, UserMeResponse } from "../core/ApiSchemas";
import { UserMeResponse } from "../core/ApiSchemas";
🧰 Tools
🪛 ESLint

[error] 6-6: 'TokenPayloadSchema' is defined but never used.

(@typescript-eslint/no-unused-vars)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` at line 6, Remove the unused import
TokenPayloadSchema from the import list in AuthRoutes.ts so only used symbols
(e.g., UserMeResponse) are imported; update the import line that currently reads
import { TokenPayloadSchema, UserMeResponse } from "../core/ApiSchemas" to omit
TokenPayloadSchema to satisfy ESLint and avoid confusion.

Comment thread src/server/AuthRoutes.ts
Comment on lines +14 to +29
const refreshTokens = new Map<
string,
{
persistentId: string;
email: string;
expiresAt: number;
}
>();
const usersByEmail = new Map<
string,
{
email: string;
persistentId: string;
publicId: string;
}
>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

In-memory token/user storage will not survive restarts and breaks horizontal scaling.

Both refreshTokens and usersByEmail maps are process-local. When the server restarts, all sessions are lost. If you run multiple master instances behind a load balancer, users will get "invalid token" errors when requests hit a different instance.

Consider using Redis or a database for production deployments.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` around lines 14 - 29, The in-memory Maps
refreshTokens and usersByEmail in AuthRoutes.ts are process-local and must be
replaced with a persistent/shared store to survive restarts and scale
horizontally; implement a small storage abstraction (e.g., TokenStore/UserStore
interface) and replace direct Map accesses in functions that read/write
refreshTokens and usersByEmail with async calls to a Redis-backed or DB-backed
implementation that supports TTL for tokens and atomic operations for
lookups/invalidations, migrate existing code paths to await the new store
methods, and provide a default in-memory fallback for local dev only.

Comment thread src/server/AuthRoutes.ts
}
>();

const magic = new Magic(magicSecretKey);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Magic SDK is initialized even when MAGIC_SECRET_KEY is empty.

If the key is missing, magic.users.getMetadataByToken() will fail at runtime with a confusing error. Consider failing fast at startup if this is a required dependency.

Proposed fix
 const magicSecretKey = process.env.MAGIC_SECRET_KEY ?? "";
+if (!magicSecretKey) {
+  console.warn("MAGIC_SECRET_KEY not set; auth routes will reject logins");
+}
 const magic = new Magic(magicSecretKey);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const magic = new Magic(magicSecretKey);
const magicSecretKey = process.env.MAGIC_SECRET_KEY ?? "";
if (!magicSecretKey) {
console.warn("MAGIC_SECRET_KEY not set; auth routes will reject logins");
}
const magic = new Magic(magicSecretKey);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/AuthRoutes.ts` at line 31, The code instantiates the Magic SDK
unconditionally (const magic = new Magic(magicSecretKey)) which will cause
confusing runtime failures when magicSecretKey (MAGIC_SECRET_KEY) is empty;
update the startup logic to validate magicSecretKey before creating the Magic
instance and fail fast with a clear error (throw or process.exit) if it's
missing, or guard all uses of magic (e.g., magic.users.getMetadataByToken)
behind a conditional that checks the key so initialization only occurs when a
valid key is present.

Comment thread src/server/Worker.ts
@@ -1,4 +1,5 @@
import compression from "compression";
import cookieParser from "cookie-parser";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 💤 Low value

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if Worker.ts or its imported modules access req.cookies
rg -n 'req\.cookies' src/server/Worker.ts
rg -n 'req\.cookies' src/server/GameManager.ts src/server/Client.ts src/server/WorkerLobbyService.ts 2>/dev/null || echo "Files not found or no matches"

Repository: openfrontio/OpenFrontIO

Length of output: 97


🏁 Script executed:

# 1. Verify line 54 and the middleware setup
sed -n '50,60p' src/server/Worker.ts

# 2. Check if cookie-parser is used elsewhere or just added
rg -n 'cookie-parser' src/server/

# 3. Verify Master.ts has auth routes that Worker.ts lacks
echo "=== Checking Master.ts for auth routes ===" 
rg -n 'router.post.*auth|router.get.*auth|router.post.*login|router.get.*login' src/server/Master.ts | head -5

echo "=== Checking Worker.ts for auth routes ===" 
rg -n 'router.post.*auth|router.get.*auth|router.post.*login|router.get.*login' src/server/Worker.ts

# 4. Search for any TODO or comments about future cookie usage
rg -n -i 'TODO|FIXME|future.*cookie|cookie.*future' src/server/Worker.ts src/server/GameManager.ts

Repository: openfrontio/OpenFrontIO

Length of output: 587


🏁 Script executed:

# Check if auth is handled differently in Master.ts
rg -n "app.use|router\." src/server/Master.ts | head -20

# Check for any cookie usage in Worker WebSocket handling
rg -n 'wss\.|socket\.handshake|cookie' src/server/Worker.ts | head -10

# Search for any usage of express app in Worker that might need cookies
rg -n 'authenticate|auth|login|session' src/server/Worker.ts

Repository: openfrontio/OpenFrontIO

Length of output: 1138


🏁 Script executed:

# Check Master.ts for cookie-parser usage and auth routes
rg -n 'cookie-parser|cookieParser' src/server/Master.ts

# Check Master.ts middleware setup more completely
head -100 src/server/Master.ts | grep -n "app.use"

# Confirm Worker.ts authentication method
rg -n 'authorization|Bearer' src/server/Worker.ts | head -5

Repository: openfrontio/OpenFrontIO

Length of output: 420


Remove unused cookie-parser middleware from Worker.

Worker.ts uses Bearer token authentication (reading from req.headers.authorization), not cookies. The cookie-parser middleware is added at line 54 but req.cookies is never accessed in Worker or its imported modules. This adds unnecessary overhead to every request in the Worker process. Master.ts doesn't even use this middleware.

Remove the import on line 2 and the app.use(cookieParser()) call on line 54.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/Worker.ts` at line 2, Remove the unused cookie-parser middleware
from Worker.ts: delete the import cookieParser and remove the
app.use(cookieParser()) call so Worker no longer loads cookie parsing on every
request; search for the symbols cookieParser and the app.use(cookieParser())
invocation in Worker.ts (and ensure no other modules rely on req.cookies) before
committing the removal.

@github-project-automation github-project-automation Bot moved this from Triage to Development in OpenFront Release Management May 20, 2026
@evanpelle evanpelle closed this May 21, 2026
@github-project-automation github-project-automation Bot moved this from Development to Complete in OpenFront Release Management May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Complete

Development

Successfully merging this pull request may close these issues.

3 participants