99 * - Accepts text or URL in JSON body
1010 * - Supports multiple intelligence features: summarization, topics, sentiment, intents
1111 * - CORS-enabled for frontend communication
12- * - JWT session auth with page nonce (production only)
12+ * - JWT session auth with rate limiting (production only)
1313 */
1414
1515require ( "dotenv" ) . config ( ) ;
@@ -33,70 +33,18 @@ const CONFIG = {
3333} ;
3434
3535// ============================================================================
36- // SESSION AUTH - JWT tokens with page nonce for production security
36+ // SESSION AUTH - JWT tokens for production security
3737// ============================================================================
3838
3939/**
40- * Session secret for signing JWTs. When set (production/Fly.io), nonce
41- * validation is enforced. When unset (local dev), tokens are issued freely.
40+ * Session secret for signing JWTs.
4241 */
4342const SESSION_SECRET =
4443 process . env . SESSION_SECRET || crypto . randomBytes ( 32 ) . toString ( "hex" ) ;
45- const REQUIRE_NONCE = ! ! process . env . SESSION_SECRET ;
46-
47- /** In-memory nonce store: nonce -> expiry timestamp */
48- const sessionNonces = new Map ( ) ;
49-
50- /** Nonce expiry time (5 minutes) */
51- const NONCE_TTL_MS = 5 * 60 * 1000 ;
5244
5345/** JWT expiry time (1 hour) */
5446const JWT_EXPIRY = "1h" ;
5547
56- /**
57- * Generates a single-use nonce and stores it with an expiry
58- * @returns {string } The generated nonce
59- */
60- function generateNonce ( ) {
61- const nonce = crypto . randomBytes ( 16 ) . toString ( "hex" ) ;
62- sessionNonces . set ( nonce , Date . now ( ) + NONCE_TTL_MS ) ;
63- return nonce ;
64- }
65-
66- /**
67- * Validates and consumes a nonce (single-use)
68- * @param {string } nonce - The nonce to validate
69- * @returns {boolean } True if the nonce was valid and consumed
70- */
71- function consumeNonce ( nonce ) {
72- const expiry = sessionNonces . get ( nonce ) ;
73- if ( ! expiry ) return false ;
74- sessionNonces . delete ( nonce ) ;
75- return Date . now ( ) < expiry ;
76- }
77-
78- /** Periodically clean up expired nonces (every 60 seconds) */
79- setInterval ( ( ) => {
80- const now = Date . now ( ) ;
81- for ( const [ nonce , expiry ] of sessionNonces ) {
82- if ( now >= expiry ) sessionNonces . delete ( nonce ) ;
83- }
84- } , 60_000 ) ;
85-
86- /**
87- * Reads frontend/dist/index.html and injects a session nonce meta tag.
88- * Returns null in dev mode (no built frontend).
89- */
90- let indexHtmlTemplate = null ;
91- try {
92- indexHtmlTemplate = fs . readFileSync (
93- path . join ( __dirname , "frontend" , "dist" , "index.html" ) ,
94- "utf-8"
95- ) ;
96- } catch {
97- // No built frontend (dev mode) — index.html served by Vite
98- }
99-
10048/**
10149 * Express middleware that validates JWT from Authorization header.
10250 * Returns 401 with JSON error if token is missing or invalid.
@@ -168,42 +116,14 @@ app.use(cors());
168116// ============================================================================
169117
170118/**
171- * GET / — Serve index.html with injected session nonce (production only).
172- * In dev mode, Vite serves the frontend directly.
173- */
174- app . get ( "/" , ( req , res ) => {
175- if ( ! indexHtmlTemplate ) {
176- return res . status ( 404 ) . send ( "Frontend not built. Run make build first." ) ;
177- }
178- const nonce = generateNonce ( ) ;
179- const html = indexHtmlTemplate . replace (
180- "</head>" ,
181- `<meta name="session-nonce" content="${ nonce } ">\n</head>`
182- ) ;
183- res . type ( "html" ) . send ( html ) ;
184- } ) ;
185-
186- /**
187- * GET /api/session — Issues a JWT. In production (SESSION_SECRET set),
188- * requires a valid single-use nonce via X-Session-Nonce header.
119+ * GET /api/session — Issues a signed JWT for session authentication.
189120 */
190121app . get ( "/api/session" , ( req , res ) => {
191- if ( REQUIRE_NONCE ) {
192- const nonce = req . headers [ "x-session-nonce" ] ;
193- if ( ! nonce || ! consumeNonce ( nonce ) ) {
194- return res . status ( 403 ) . json ( {
195- error : {
196- type : "AuthenticationError" ,
197- code : "INVALID_NONCE" ,
198- message : "Valid session nonce required. Please refresh the page." ,
199- } ,
200- } ) ;
201- }
202- }
203-
204- const token = jwt . sign ( { iat : Math . floor ( Date . now ( ) / 1000 ) } , SESSION_SECRET , {
205- expiresIn : JWT_EXPIRY ,
206- } ) ;
122+ const token = jwt . sign (
123+ { iat : Math . floor ( Date . now ( ) / 1000 ) } ,
124+ SESSION_SECRET ,
125+ { expiresIn : JWT_EXPIRY }
126+ ) ;
207127 res . json ( { token } ) ;
208128} ) ;
209129
@@ -432,7 +352,7 @@ app.get('/api/metadata', (req, res) => {
432352app . listen ( CONFIG . port , CONFIG . host , ( ) => {
433353 console . log ( "\n" + "=" . repeat ( 70 ) ) ;
434354 console . log ( `🚀 Backend API running at http://localhost:${ CONFIG . port } ` ) ;
435- console . log ( `📡 GET /api/session${ REQUIRE_NONCE ? " (nonce required)" : "" } ` ) ;
355+ console . log ( `📡 GET /api/session` ) ;
436356 console . log ( `📡 POST /api/text-intelligence (auth required)` ) ;
437357 console . log ( `📡 GET /api/metadata` ) ;
438358 console . log ( "=" . repeat ( 70 ) + "\n" ) ;
0 commit comments