Skip to content

Commit 3e32626

Browse files
committed
refactor(auth): remove nonce, rely on rate limiting + JWT
1 parent 2d4cabc commit 3e32626

File tree

3 files changed

+11
-70
lines changed

3 files changed

+11
-70
lines changed

deploy/Caddyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
}
44

55
:8080 {
6-
# HTML page: Caddy templates inject base path + session nonce
6+
# HTML page: Caddy templates inject base path
77
@htmlpage {
88
path /
99
path /index.html

deploy/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ COPY frontend/package.json frontend/pnpm-lock.yaml ./frontend/
1313
RUN cd frontend && pnpm install --frozen-lockfile
1414
COPY frontend/ ./frontend/
1515
RUN cd frontend && pnpm build
16-
# Inject Caddy template directives for subpath base path + session nonce
17-
RUN sed -i 's|<head>|<head>\n <base href="{{ .Req.Header.Get "X-Base-Path" }}">\n <meta name="session-nonce" content="{{ placeholder "http.request.uuid" }}">|' ./frontend/dist/index.html
16+
# Inject Caddy template directive for subpath base path
17+
RUN sed -i 's|<head>|<head>\n <base href="{{ .Req.Header.Get "X-Base-Path" }}">|' ./frontend/dist/index.html
1818

1919
# Stage 3: Runtime
2020
FROM node:24-slim

server.js

Lines changed: 8 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -37,47 +37,14 @@ const CONFIG = {
3737
};
3838

3939
// ============================================================================
40-
// SESSION AUTH - JWT tokens with page nonce for production security
40+
// SESSION AUTH - JWT tokens for production security
4141
// ============================================================================
4242

4343
const SESSION_SECRET =
4444
process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex');
45-
const REQUIRE_NONCE = !!process.env.SESSION_SECRET;
4645

47-
const sessionNonces = new Map();
48-
const NONCE_TTL_MS = 5 * 60 * 1000;
4946
const JWT_EXPIRY = '1h';
5047

51-
function generateNonce() {
52-
const nonce = crypto.randomBytes(16).toString('hex');
53-
sessionNonces.set(nonce, Date.now() + NONCE_TTL_MS);
54-
return nonce;
55-
}
56-
57-
function consumeNonce(nonce) {
58-
const expiry = sessionNonces.get(nonce);
59-
if (!expiry) return false;
60-
sessionNonces.delete(nonce);
61-
return Date.now() < expiry;
62-
}
63-
64-
setInterval(() => {
65-
const now = Date.now();
66-
for (const [nonce, expiry] of sessionNonces) {
67-
if (now >= expiry) sessionNonces.delete(nonce);
68-
}
69-
}, 60_000);
70-
71-
let indexHtmlTemplate = null;
72-
try {
73-
indexHtmlTemplate = fs.readFileSync(
74-
path.join(__dirname, 'frontend', 'dist', 'index.html'),
75-
'utf-8'
76-
);
77-
} catch {
78-
// No built frontend (dev mode)
79-
}
80-
8148
/**
8249
* Validates JWT from WebSocket subprotocol: access_token.<jwt>
8350
* Returns the token string if valid, null if invalid.
@@ -120,40 +87,14 @@ app.use(cors());
12087
// ============================================================================
12188

12289
/**
123-
* GET / — Serve index.html with injected session nonce (production only)
124-
*/
125-
app.get('/', (req, res) => {
126-
if (!indexHtmlTemplate) {
127-
return res.status(404).send('Frontend not built. Run make build first.');
128-
}
129-
const nonce = generateNonce();
130-
const html = indexHtmlTemplate.replace(
131-
'</head>',
132-
`<meta name="session-nonce" content="${nonce}">\n</head>`
133-
);
134-
res.type('html').send(html);
135-
});
136-
137-
/**
138-
* GET /api/session — Issues a JWT. In production, requires valid nonce.
90+
* GET /api/session — Issues a signed JWT for session authentication.
13991
*/
14092
app.get('/api/session', (req, res) => {
141-
if (REQUIRE_NONCE) {
142-
const nonce = req.headers['x-session-nonce'];
143-
if (!nonce || !consumeNonce(nonce)) {
144-
return res.status(403).json({
145-
error: {
146-
type: 'AuthenticationError',
147-
code: 'INVALID_NONCE',
148-
message: 'Valid session nonce required. Please refresh the page.',
149-
},
150-
});
151-
}
152-
}
153-
154-
const token = jwt.sign({ iat: Math.floor(Date.now() / 1000) }, SESSION_SECRET, {
155-
expiresIn: JWT_EXPIRY,
156-
});
93+
const token = jwt.sign(
94+
{ iat: Math.floor(Date.now() / 1000) },
95+
SESSION_SECRET,
96+
{ expiresIn: JWT_EXPIRY }
97+
);
15798
res.json({ token });
15899
});
159100

@@ -358,7 +299,7 @@ server.listen(CONFIG.port, CONFIG.host, () => {
358299
console.log("\n" + "=".repeat(70));
359300
console.log(`🚀 Backend API Server running at http://localhost:${CONFIG.port}`);
360301
console.log("");
361-
console.log(`📡 GET /api/session${REQUIRE_NONCE ? ' (nonce required)' : ''}`);
302+
console.log(`📡 GET /api/session`);
362303
console.log(`📡 WS /api/voice-agent (auth required)`);
363304
console.log(`📡 GET /api/metadata`);
364305
console.log("=".repeat(70) + "\n");

0 commit comments

Comments
 (0)