@@ -14,6 +14,8 @@ const __dirname = path.dirname(__filename)
1414export class APIHandler {
1515 constructor ( ) {
1616 this . endpoint = Config . getSparqlEndpoint ( 'newsmonitor' )
17+ this . adminUsername = Config . getEnv ( 'NEWSMONITOR_ADMIN_USERNAME' , 'admin' )
18+ this . adminPassword = Config . getEnv ( 'NEWSMONITOR_ADMIN_PASSWORD' )
1719 }
1820
1921 /**
@@ -362,8 +364,25 @@ export class APIHandler {
362364 const path = url . pathname
363365
364366 try {
367+ if ( path === '/api/admin-check' ) {
368+ if ( ! this . requireAdminAuth ( req , res ) ) {
369+ return true
370+ }
371+
372+ res . writeHead ( 200 , {
373+ 'Content-Type' : 'application/json' ,
374+ 'Access-Control-Allow-Origin' : '*'
375+ } )
376+ res . end ( JSON . stringify ( { status : 'ok' , user : this . adminUsername } ) )
377+ return true
378+ }
379+
365380 // Subscribe to feeds (POST)
366381 if ( path === '/api/subscribe' && req . method === 'POST' ) {
382+ if ( ! this . requireAdminAuth ( req , res ) ) {
383+ return true
384+ }
385+
367386 const body = await this . parseBody ( req )
368387 const urls = body . urls || [ ]
369388
@@ -388,6 +407,10 @@ export class APIHandler {
388407
389408 // Unsubscribe from feed (POST)
390409 if ( path === '/api/unsubscribe' && req . method === 'POST' ) {
410+ if ( ! this . requireAdminAuth ( req , res ) ) {
411+ return true
412+ }
413+
391414 const body = await this . parseBody ( req )
392415 const feedUri = body . feedUri
393416
@@ -409,6 +432,10 @@ export class APIHandler {
409432
410433 // Update all feeds (POST)
411434 if ( path === '/api/update-feeds' && req . method === 'POST' ) {
435+ if ( ! this . requireAdminAuth ( req , res ) ) {
436+ return true
437+ }
438+
412439 try {
413440 console . log ( 'Manual feed update triggered via API' )
414441 const result = await this . runTransCommand ( 'src/apps/newsmonitor/update-all' )
@@ -544,4 +571,53 @@ export class APIHandler {
544571 return true
545572 }
546573 }
574+
575+ requireAdminAuth ( req , res ) {
576+ if ( ! this . adminPassword ) {
577+ console . error ( 'Admin password missing: set NEWSMONITOR_ADMIN_PASSWORD in .env' )
578+ res . writeHead ( 503 , {
579+ 'Content-Type' : 'application/json' ,
580+ 'Access-Control-Allow-Origin' : '*'
581+ } )
582+ res . end ( JSON . stringify ( { error : 'Admin password not configured' } ) )
583+ return false
584+ }
585+
586+ if ( ! this . isAdminAuthorized ( req ) ) {
587+ res . writeHead ( 401 , {
588+ 'Content-Type' : 'application/json' ,
589+ 'Access-Control-Allow-Origin' : '*' ,
590+ 'WWW-Authenticate' : 'Basic realm="NewsMonitor Admin"'
591+ } )
592+ res . end ( JSON . stringify ( { error : 'Admin authentication required' } ) )
593+ return false
594+ }
595+
596+ return true
597+ }
598+
599+ isAdminAuthorized ( req ) {
600+ const authHeader = req . headers . authorization || ''
601+ if ( ! authHeader . startsWith ( 'Basic ' ) ) {
602+ return false
603+ }
604+
605+ const encoded = authHeader . slice ( 'Basic ' . length )
606+ let decoded
607+ try {
608+ decoded = Buffer . from ( encoded , 'base64' ) . toString ( 'utf8' )
609+ } catch ( error ) {
610+ return false
611+ }
612+
613+ const separatorIndex = decoded . indexOf ( ':' )
614+ if ( separatorIndex === - 1 ) {
615+ return false
616+ }
617+
618+ const username = decoded . slice ( 0 , separatorIndex )
619+ const password = decoded . slice ( separatorIndex + 1 )
620+
621+ return username === this . adminUsername && password === this . adminPassword
622+ }
547623}
0 commit comments