feat: add env variable API_KEY for restart / resync triggering

This commit is contained in:
2025-04-11 13:03:19 -07:00
parent 010a440e3e
commit f935b184d7
2 changed files with 90 additions and 22 deletions

View File

@@ -111,7 +111,6 @@ const USERAGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
http://127.0.0.1:4124/health http://127.0.0.1:4124/health
*/ */
const subdomainRestart = [ 'restart', 'sync', 'resync' ];
const subdomainGZP = [ 'gzip', 'gz' ]; const subdomainGZP = [ 'gzip', 'gz' ];
const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ]; const subdomainM3U = [ 'playlist', 'm3u', 'm3u8' ];
const subdomainEPG = [ 'guide', 'epg', 'xml' ]; const subdomainEPG = [ 'guide', 'epg', 'xml' ];
@@ -599,6 +598,7 @@ async function serveKey( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 400, code: 400,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -631,6 +631,7 @@ async function serveKey( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 500, code: 500,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -793,6 +794,7 @@ async function serveM3UPlaylist( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 404, code: 404,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -847,6 +849,7 @@ async function serveM3UPlaylist( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 500, code: 500,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -887,6 +890,7 @@ async function serveM3UPlaylist( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 500, code: 500,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -926,6 +930,7 @@ async function serveHealthCheck( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 200, code: 200,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -933,7 +938,7 @@ async function serveHealthCheck( req, res )
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}); });
Log.ok( `health`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `health check returned` ), chalk.greenBright( `${ statusCheck.status }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `<uptime>` ), chalk.gray( `${ process.uptime() }` ) ); Log.ok( `health`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `health check returned` ), chalk.greenBright( `${ statusCheck.status }` ), chalk.blueBright( `<client>` ), chalk.gray( `${ clientIp( req ) }` ), chalk.blueBright( `<statusCode>` ), chalk.gray( `${ statusCheck.code }` ), chalk.blueBright( `<uptime>` ), chalk.gray( Math.round( process.uptime() ) ) );
res.end( JSON.stringify( statusCheck ) ); res.end( JSON.stringify( statusCheck ) );
return; return;
@@ -953,6 +958,7 @@ async function serveHealthCheck( req, res )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 503, code: 503,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -1042,6 +1048,7 @@ async function serveM3U( res, req )
ref: req.url, ref: req.url,
method: req.method || 'GET', method: req.method || 'GET',
code: 500, code: 500,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -1187,7 +1194,7 @@ async function initialize()
FILE_GZP_MODIFIED = getFileModified( FILE_GZP ); FILE_GZP_MODIFIED = getFileModified( FILE_GZP );
const end = performance.now(); const end = performance.now();
Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `TVApp2 container is ready` ), chalk.blueBright( `took ${ end - start }ms` ), chalk.blueBright( `<message>` ), chalk.gray( `TVApp2 container is ready; took ${ end - start }ms` ), chalk.blueBright( `<ip>` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `<gateway>` ), chalk.gray( `${ envIpGateway }` ), chalk.blueBright( `<port>` ), chalk.gray( `${ envWebPort }` ) ); Log.info( `core`, chalk.yellow( `[init]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `TVApp2 container is ready` ), chalk.blueBright( `took ${ end - start }ms` ), chalk.blueBright( `<ip>` ), chalk.gray( `${ envIpContainer }` ), chalk.blueBright( `<gateway>` ), chalk.gray( `${ envIpGateway }` ), chalk.blueBright( `<port>` ), chalk.gray( `${ envWebPort }` ) );
} }
catch ( err ) catch ( err )
{ {
@@ -1245,6 +1252,66 @@ const server = http.createServer( ( request, response ) =>
if ( subdomainRestart.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) ) if ( subdomainRestart.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) )
{ {
/*
Not highly technical, but good enough for starting out until Express is integrated.
if restart command is triggered using website, allow it to pass through without an API key.
if restart command is triggered by using
referer = if activated from webpage via clicking icon
no referer = if activated using URL
referer is check just as an added aspect of the api key, but really this doesn't even need to be here
as the referer can be easily spoofed. remove once express and the new api system are added. right now
it does no harm for a user to even bypass this.
@todo integrate real api system after express replaces node http
*/
const apiKey = new URL( request.url, `http://${ request.headers.host }` ).searchParams.get( 'key' );
const referer = request.headers.referer || null;
if ( ( !referer && envApiKey && !apiKey ) || ( referer && !referer.includes( request.headers.host ) ) )
{
const statusCheck =
{
ip: envIpContainer, gateway: envIpGateway, client: clientIp( request ),
message: `must specify api key: http://${ request.headers.host }/api/restart?key=XXXXXXXX`,
status: `unauthorized`, ref: request.url, method: method || 'GET', code: 401,
uptime: Math.round( process.uptime() ), timestamp: Date.now()
};
response.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.error( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `unauthorized (401): restart attempt did not specify api key using ?key=XXX parameter` ), chalk.blueBright( `<type>` ), chalk.gray( `api/restart` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
response.end( JSON.stringify( statusCheck ) );
return;
}
/*
no referer, api key in url specified, api key set up with tvapp2 do not match
*/
if ( !referer && ( envApiKey !== apiKey ) )
{
const statusCheck =
{
ip: envIpContainer, gateway: envIpGateway, client: clientIp( request ),
message: `incorrect api key specified: http://${ request.headers.host }/api/restart?key=XXXXXXXX`,
status: `unauthorized`, ref: request.url, method: method || 'GET', code: 401,
uptime: Math.round( process.uptime() ), timestamp: Date.now()
};
response.writeHead( statusCheck.code, {
'Content-Type': 'application/json'
});
Log.error( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.redBright( `unauthorized (401): incorrect api key specified` ), chalk.blueBright( `<type>` ), chalk.gray( `api/restart` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
response.end( JSON.stringify( statusCheck ) );
}
await initialize(); await initialize();
const statusCheck = const statusCheck =
@@ -1257,6 +1324,7 @@ const server = http.createServer( ( request, response ) =>
ref: request.url, ref: request.url,
method: method || 'GET', method: method || 'GET',
code: 200, code: 200,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -1265,12 +1333,19 @@ const server = http.createServer( ( request, response ) =>
}); });
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api/restart` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api/restart` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
response.end( JSON.stringify( statusCheck ) ); response.end( JSON.stringify( statusCheck ) );
return; return;
} }
if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveHealthCheck( request, response );
return;
}
if ( subdomainM3U.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' ) if ( subdomainM3U.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{ {
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `m3u playlist` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) ); Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `m3u playlist` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
@@ -1311,14 +1386,6 @@ const server = http.createServer( ( request, response ) =>
return; return;
} }
if ( subdomainHealth.some( ( urlKeyword ) => loadFile.startsWith( urlKeyword ) ) && method === 'GET' )
{
Log.info( `www`, chalk.yellow( `[req]` ), chalk.white( `` ), chalk.blueBright( `<type>` ), chalk.gray( `api` ), chalk.blueBright( `<file>` ), chalk.gray( `${ loadFile }` ), chalk.blueBright( `<method>` ), chalk.gray( `${ method }` ) );
await serveHealthCheck( request, response );
return;
}
/* /*
General Template & .html / .css / .js General Template & .html / .css / .js
read the loaded asset file read the loaded asset file
@@ -1403,6 +1470,7 @@ const server = http.createServer( ( request, response ) =>
ref: request.url, ref: request.url,
method: method || 'GET', method: method || 'GET',
code: 404, code: 404,
uptime: Math.round( process.uptime() ),
timestamp: Date.now() timestamp: Date.now()
}; };
@@ -1435,9 +1503,9 @@ const server = http.createServer( ( request, response ) =>
( async() => ( async() =>
{ {
if ( !envApiKey ) if ( !envApiKey )
{
Log.warn( `core`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `API_KEY environment variable not defined for api, leaving blank` ) ); Log.warn( `core`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `API_KEY environment variable not defined for api, leaving blank` ) );
} else
Log.ok( `core`, chalk.yellow( `[api]` ), chalk.white( `` ), chalk.blueBright( `<message>` ), chalk.gray( `API_KEY environment variable successfully assigned` ) );
await initialize(); await initialize();

View File

@@ -29,7 +29,7 @@
</div> </div>
</nav> </nav>
</div> </div>
<!-- Header Notification: description --> <!-- Header Notification: description -->
<div class="container"> <div class="container">
<div class="container header-container"> <div class="container header-container">
@@ -147,7 +147,7 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Modal --> <!-- Modal -->
<div class="modal fade" id="modalTvapp2" tabindex="-1" data-bs-backdrop="static" aria-labelledby="modalTvapp2Label" aria-hidden="true"> <div class="modal fade" id="modalTvapp2" tabindex="-1" data-bs-backdrop="static" aria-labelledby="modalTvapp2Label" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
@@ -239,7 +239,7 @@
$('#tvapp2Toast').toast("show"); $('#tvapp2Toast').toast("show");
}); });
*/ */
/* /*
Notify > Localhost Notify > Localhost
*/ */
@@ -421,7 +421,7 @@
iconResync[0].classList.add('restart'); // normal spinner class iconResync[0].classList.add('restart'); // normal spinner class
document.location.reload() // reload page document.location.reload() // reload page
}, 5000 ); // how long until refresh page }, 5000 ); // how long until refresh page
}, 1000 ); // how long until dimmer is removed / reload page activated (also on delay) }, 1000 ); // how long until dimmer is removed / reload page activated (also on delay)
} }
}); });
} }
@@ -429,7 +429,7 @@
/* /*
Health check > Show time remaining as tooltip Health check > Show time remaining as tooltip
*/ */
function runTooltipCountdown( ) function runTooltipCountdown( )
{ {
let timerHours, timerMins, timerRemainsLS; let timerHours, timerMins, timerRemainsLS;
@@ -450,7 +450,7 @@
{ {
const timerElapsedMS = Date.now() - timerStartMS; // ( 2091 ) const timerElapsedMS = Date.now() - timerStartMS; // ( 2091 )
const timerRemainsMS = timerDelayMS - timerElapsedMS; // ( 7909 ) divide by 1000 for seconds const timerRemainsMS = timerDelayMS - timerElapsedMS; // ( 7909 ) divide by 1000 for seconds
timerRemainsLS = new Date( timerRemainsMS ); // (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time)) timerRemainsLS = new Date( timerRemainsMS ); // (Wed Dec 31 1969 10:01:42 (Coordinated Universal Time))
timerHours = timerRemainsLS.getUTCHours(); // ( 0 ) timerHours = timerRemainsLS.getUTCHours(); // ( 0 )
timerMins = timerRemainsLS.getUTCMinutes(); // ( 9 ) timerMins = timerRemainsLS.getUTCMinutes(); // ( 9 )
@@ -463,7 +463,7 @@
.attr('data-original-title', `Health check in ${ timeLeft }`) .attr('data-original-title', `Health check in ${ timeLeft }`)
.attr('aria-label', `Health check in ${ timeLeft }`) .attr('aria-label', `Health check in ${ timeLeft }`)
.attr('data-bs-original-title', `Health check in ${ timeLeft }`) .attr('data-bs-original-title', `Health check in ${ timeLeft }`)
}); });
const Heart = document.getElementsByClassName('fa-heart'); const Heart = document.getElementsByClassName('fa-heart');
Heart[0].style.color = '#FFF'; Heart[0].style.color = '#FFF';
@@ -486,7 +486,7 @@
updateTooltipCountdown(); updateTooltipCountdown();
}, timerRemainsLS.getUTCMilliseconds() + 500 ); }, timerRemainsLS.getUTCMilliseconds() + 500 );
} }
updateTooltipCountdown(); updateTooltipCountdown();
} }