2022-04-19 09:38:59 +02:00
/ *
* Uptime Kuma Server
* node "server/server.js"
* DO NOT require ( "./server" ) in other modules , it likely creates circular dependency !
* /
2021-08-08 15:03:10 +02:00
console . log ( "Welcome to Uptime Kuma" ) ;
2022-01-14 19:25:28 +02:00
2022-10-09 14:59:58 +02:00
// As the log function need to use dayjs, it should be very top
const dayjs = require ( "dayjs" ) ;
dayjs . extend ( require ( "dayjs/plugin/utc" ) ) ;
2022-12-15 07:39:48 +02:00
dayjs . extend ( require ( "./modules/dayjs/plugin/timezone" ) ) ;
2022-10-12 11:02:16 +02:00
dayjs . extend ( require ( "dayjs/plugin/customParseFormat" ) ) ;
2022-10-09 14:59:58 +02:00
2023-02-01 14:07:08 +02:00
// Load environment variables from `.env`
require ( "dotenv" ) . config ( ) ;
2022-01-14 19:25:28 +02:00
// Check Node.js Version
2023-07-17 07:17:00 +02:00
const nodeVersion = process . versions . node ;
// Get the required Node.js version from package.json
const requiredNodeVersions = require ( "../package.json" ) . engines . node ;
const bannedNodeVersions = " < 14 || 20.0.* || 20.1.* || 20.2.* || 20.3.* " ;
2022-01-14 19:25:28 +02:00
console . log ( ` Your Node.js version: ${ nodeVersion } ` ) ;
2023-07-17 07:17:00 +02:00
const semver = require ( "semver" ) ;
const requiredNodeVersionsComma = requiredNodeVersions . split ( "||" ) . map ( ( version ) => version . trim ( ) ) . join ( ", " ) ;
2023-05-26 12:08:52 +02:00
2023-07-17 07:17:00 +02:00
// Exit Uptime Kuma immediately if the Node.js version is banned
if ( semver . satisfies ( nodeVersion , bannedNodeVersions ) ) {
console . error ( "\x1b[31m%s\x1b[0m" , ` Error: Your Node.js version: ${ nodeVersion } is not supported, please upgrade your Node.js to ${ requiredNodeVersionsComma } . ` ) ;
2022-01-14 19:25:28 +02:00
process . exit ( - 1 ) ;
}
2023-07-17 07:17:00 +02:00
// Warning if the Node.js version is not in the support list, but it maybe still works
if ( ! semver . satisfies ( nodeVersion , requiredNodeVersions ) ) {
console . warn ( "\x1b[31m%s\x1b[0m" , ` Warning: Your Node.js version: ${ nodeVersion } is not officially supported, please upgrade your Node.js to ${ requiredNodeVersionsComma } . ` ) ;
}
2021-10-09 20:36:20 +02:00
const args = require ( "args-parser" ) ( process . argv ) ;
2022-05-31 17:06:43 +02:00
const { sleep , log , getRandomInt , genSecret , isDev } = require ( "../src/util" ) ;
2021-10-15 18:57:26 +02:00
const config = require ( "./config" ) ;
2021-10-09 20:36:20 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Arguments" ) ;
log . debug ( "server" , args ) ;
2021-09-14 17:28:38 +02:00
if ( ! process . env . NODE _ENV ) {
process . env . NODE _ENV = "production" ;
}
2023-12-10 14:39:43 +02:00
if ( ! process . env . UPTIME _KUMA _WS _ORIGIN _CHECK ) {
process . env . UPTIME _KUMA _WS _ORIGIN _CHECK = "cors-like" ;
}
2021-07-31 15:57:58 +02:00
2023-10-13 21:00:34 +02:00
log . info ( "server" , "Env: " + process . env . NODE _ENV ) ;
log . debug ( "server" , "Inside Container: " + ( process . env . UPTIME _KUMA _IS _CONTAINER === "1" ) ) ;
2021-07-31 15:57:58 +02:00
2023-12-12 10:23:41 +02:00
if ( process . env . UPTIME _KUMA _WS _ORIGIN _CHECK === "bypass" ) {
log . warn ( "server" , "WebSocket Origin Check: " + process . env . UPTIME _KUMA _WS _ORIGIN _CHECK ) ;
}
2021-07-31 15:57:58 +02:00
2023-10-13 21:00:34 +02:00
const checkVersion = require ( "./check-version" ) ;
log . info ( "server" , "Uptime Kuma Version: " + checkVersion . version ) ;
2021-07-31 15:57:58 +02:00
2023-10-13 21:00:34 +02:00
log . info ( "server" , "Loading modules" ) ;
2022-09-27 18:20:17 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing express" ) ;
2021-07-31 15:57:58 +02:00
const express = require ( "express" ) ;
2022-06-05 17:43:25 +02:00
const expressStaticGzip = require ( "express-static-gzip" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing redbean-node" ) ;
2021-07-27 19:47:13 +02:00
const { R } = require ( "redbean-node" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing jsonwebtoken" ) ;
2021-07-27 19:47:13 +02:00
const jwt = require ( "jsonwebtoken" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing http-graceful-shutdown" ) ;
2021-07-27 19:47:13 +02:00
const gracefulShutdown = require ( "http-graceful-shutdown" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing prometheus-api-metrics" ) ;
2021-07-27 19:47:13 +02:00
const prometheusAPIMetrics = require ( "prometheus-api-metrics" ) ;
2021-10-21 16:54:04 +02:00
const { passwordStrength } = require ( "check-password-strength" ) ;
2021-07-31 15:57:58 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing 2FA Modules" ) ;
2021-09-09 21:10:31 +02:00
const notp = require ( "notp" ) ;
const base32 = require ( "thirty-two" ) ;
2022-04-19 09:38:59 +02:00
const { UptimeKumaServer } = require ( "./uptime-kuma-server" ) ;
2023-12-10 14:40:40 +02:00
const server = UptimeKumaServer . getInstance ( ) ;
2022-04-19 09:38:59 +02:00
const io = module . exports . io = server . io ;
const app = server . app ;
2022-04-07 16:53:32 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing Monitor" ) ;
2021-07-31 15:57:58 +02:00
const Monitor = require ( "./model/monitor" ) ;
2023-10-09 01:01:54 +02:00
const User = require ( "./model/user" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing Settings" ) ;
2023-12-17 13:02:22 +02:00
const { getSettings , setSettings , setting , initJWTSecret , checkLogin , doubleCheckPassword , shake256 , SHAKE256 _LENGTH , allowDevAllOrigin ,
2023-10-09 01:01:54 +02:00
} = require ( "./util-server" ) ;
2021-09-07 16:42:46 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing Notification" ) ;
2021-07-31 15:57:58 +02:00
const { Notification } = require ( "./notification" ) ;
2021-09-07 16:42:46 +02:00
Notification . init ( ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing Database" ) ;
2021-07-31 15:57:58 +02:00
const Database = require ( "./database" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "Importing Background Jobs" ) ;
2022-04-05 13:41:29 +02:00
const { initBackgroundJobs , stopBackgroundJobs } = require ( "./jobs" ) ;
2022-03-29 11:38:48 +02:00
const { loginRateLimiter , twoFaRateLimiter } = require ( "./rate-limiter" ) ;
2021-09-27 17:40:38 +02:00
2023-02-15 02:39:29 +02:00
const { apiAuth } = require ( "./auth" ) ;
2021-07-27 19:47:13 +02:00
const { login } = require ( "./auth" ) ;
2021-07-28 14:35:55 +02:00
const passwordHash = require ( "./password-hash" ) ;
2021-07-31 15:57:58 +02:00
2023-12-10 14:40:40 +02:00
const hostname = config . hostname ;
2021-10-14 08:09:16 +02:00
if ( hostname ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Custom hostname: " + hostname ) ;
2021-10-14 08:09:16 +02:00
}
2023-12-10 14:40:40 +02:00
const port = config . port ;
2021-06-25 15:55:49 +02:00
2021-10-19 10:29:09 +02:00
const disableFrameSameOrigin = ! ! process . env . UPTIME _KUMA _DISABLE _FRAME _SAMEORIGIN || args [ "disable-frame-sameorigin" ] || false ;
2022-03-30 14:08:26 +02:00
const cloudflaredToken = args [ "cloudflared-token" ] || process . env . UPTIME _KUMA _CLOUDFLARED _TOKEN || undefined ;
2021-09-02 14:18:27 +02:00
2021-10-11 20:18:40 +02:00
// 2FA / notp verification defaults
2022-04-16 19:39:49 +02:00
const twoFAVerifyOptions = {
2021-10-11 20:18:40 +02:00
"window" : 1 ,
"time" : 30
2021-10-18 11:15:28 +02:00
} ;
2021-09-02 14:18:27 +02:00
2021-10-05 13:13:57 +02:00
/ * *
* Run unit test after the server is ready
* @ type { boolean }
* /
const testMode = ! ! args [ "test" ] || false ;
2021-09-02 14:18:27 +02:00
2021-09-04 20:03:40 +02:00
// Must be after io instantiation
2023-12-01 09:29:10 +02:00
const { sendNotificationList , sendHeartbeatList , sendInfo , sendProxyList , sendDockerHostList , sendAPIKeyList , sendRemoteBrowserList } = require ( "./client" ) ;
2021-09-16 16:48:28 +02:00
const { statusPageSocketHandler } = require ( "./socket-handlers/status-page-socket-handler" ) ;
2021-10-26 17:02:32 +02:00
const databaseSocketHandler = require ( "./socket-handlers/database-socket-handler" ) ;
2023-12-01 09:29:10 +02:00
const { remoteBrowserSocketHandler } = require ( "./socket-handlers/remote-browser-socket-handler" ) ;
2021-11-18 12:22:03 +02:00
const TwoFA = require ( "./2fa" ) ;
2022-03-10 15:34:30 +02:00
const StatusPage = require ( "./model/status_page" ) ;
2022-04-05 13:41:29 +02:00
const { cloudflaredSocketHandler , autoStart : cloudflaredAutoStart , stop : cloudflaredStop } = require ( "./socket-handlers/cloudflared-socket-handler" ) ;
2022-04-07 08:45:37 +02:00
const { proxySocketHandler } = require ( "./socket-handlers/proxy-socket-handler" ) ;
2022-07-22 17:47:04 +02:00
const { dockerSocketHandler } = require ( "./socket-handlers/docker-socket-handler" ) ;
2022-09-17 10:54:21 +02:00
const { maintenanceSocketHandler } = require ( "./socket-handlers/maintenance-socket-handler" ) ;
2023-02-14 21:49:04 +02:00
const { apiKeySocketHandler } = require ( "./socket-handlers/api-key-socket-handler" ) ;
2022-12-12 16:57:57 +02:00
const { generalSocketHandler } = require ( "./socket-handlers/general-socket-handler" ) ;
2022-10-08 17:56:58 +02:00
const { Settings } = require ( "./settings" ) ;
2023-03-05 09:59:43 +02:00
const apicache = require ( "./modules/apicache" ) ;
2023-06-27 09:54:33 +02:00
const { resetChrome } = require ( "./monitor-types/real-browser-monitor-type" ) ;
2023-02-05 12:01:54 +02:00
const { EmbeddedMariaDB } = require ( "./embedded-mariadb" ) ;
2023-02-11 08:41:02 +02:00
const { SetupDatabase } = require ( "./setup-database" ) ;
2024-05-19 22:03:32 +02:00
const { chartSocketHandler } = require ( "./socket-handlers/chart-socket-handler" ) ;
2021-09-04 20:03:40 +02:00
app . use ( express . json ( ) ) ;
2021-07-09 13:33:22 +02:00
2021-10-19 08:26:10 +02:00
// Global Middleware
app . use ( function ( req , res , next ) {
2021-10-19 10:29:09 +02:00
if ( ! disableFrameSameOrigin ) {
2021-10-19 08:41:05 +02:00
res . setHeader ( "X-Frame-Options" , "SAMEORIGIN" ) ;
}
2021-10-19 08:26:10 +02:00
res . removeHeader ( "X-Powered-By" ) ;
next ( ) ;
} ) ;
2021-07-21 20:02:35 +02:00
/ * *
* Show Setup Page
* @ type { boolean }
* /
2021-07-11 07:47:57 +02:00
let needSetup = false ;
2021-06-25 15:55:49 +02:00
( async ( ) => {
2023-02-11 08:41:02 +02:00
// Create a data directory
Database . initDataDir ( args ) ;
// Check if is chosen a database type
let setupDatabase = new SetupDatabase ( args , server ) ;
if ( setupDatabase . isNeedSetup ( ) ) {
// Hold here and start a special setup page until user choose a database type
await setupDatabase . start ( hostname , port ) ;
}
// Connect to database
2023-02-11 16:21:06 +02:00
try {
await initDatabase ( testMode ) ;
} catch ( e ) {
log . error ( "server" , "Failed to prepare your database: " + e . message ) ;
process . exit ( 1 ) ;
}
2023-02-11 08:41:02 +02:00
// Database should be ready now
2022-10-09 14:59:58 +02:00
await server . initAfterDatabaseReady ( ) ;
2022-10-08 17:56:58 +02:00
server . entryPage = await Settings . get ( "entryPage" ) ;
2022-04-06 16:43:22 +02:00
await StatusPage . loadDomainMappingList ( ) ;
2021-07-27 18:52:31 +02:00
2023-10-13 21:00:34 +02:00
log . debug ( "server" , "Adding route" ) ;
2021-07-27 18:52:31 +02:00
2021-09-11 13:40:03 +02:00
// ***************************
2021-07-27 18:52:31 +02:00
// Normal Router here
2021-09-11 13:40:03 +02:00
// ***************************
2021-07-27 18:52:31 +02:00
2021-11-02 15:48:46 +02:00
// Entry Page
2022-04-06 16:43:22 +02:00
app . get ( "/" , async ( request , response ) => {
2022-07-13 04:59:23 +02:00
let hostname = request . hostname ;
if ( await setting ( "trustProxy" ) ) {
const proxy = request . headers [ "x-forwarded-host" ] ;
if ( proxy ) {
hostname = proxy ;
}
}
log . debug ( "entry" , ` Request Domain: ${ hostname } ` ) ;
2022-04-06 16:43:22 +02:00
2022-10-08 17:56:58 +02:00
const uptimeKumaEntryPage = server . entryPage ;
2022-07-13 04:59:23 +02:00
if ( hostname in StatusPage . domainMappingList ) {
2022-05-30 09:45:44 +02:00
log . debug ( "entry" , "This is a status page domain" ) ;
2022-07-13 04:59:23 +02:00
let slug = StatusPage . domainMappingList [ hostname ] ;
2022-05-30 09:45:44 +02:00
await StatusPage . handleStatusPageResponse ( response , server . indexHTML , slug ) ;
2022-10-03 17:01:52 +02:00
} else if ( uptimeKumaEntryPage && uptimeKumaEntryPage . startsWith ( "statusPage-" ) ) {
2022-10-03 16:48:34 +02:00
response . redirect ( "/status/" + uptimeKumaEntryPage . replace ( "statusPage-" , "" ) ) ;
2022-05-30 09:45:44 +02:00
2021-11-02 15:48:46 +02:00
} else {
response . redirect ( "/dashboard" ) ;
}
} ) ;
2023-06-30 16:17:07 +02:00
app . get ( "/setup-database-info" , ( request , response ) => {
allowDevAllOrigin ( response ) ;
response . json ( {
runningSetup : false ,
needSetup : false ,
} ) ;
} ) ;
2022-04-17 13:30:58 +02:00
if ( isDev ) {
2023-07-09 12:53:57 +02:00
app . use ( express . urlencoded ( { extended : true } ) ) ;
2022-04-17 13:30:58 +02:00
app . post ( "/test-webhook" , async ( request , response ) => {
2022-12-05 12:18:19 +02:00
log . debug ( "test" , request . headers ) ;
2022-04-17 13:30:58 +02:00
log . debug ( "test" , request . body ) ;
response . send ( "OK" ) ;
} ) ;
2023-09-09 12:05:25 +02:00
app . post ( "/test-x-www-form-urlencoded" , async ( request , response ) => {
log . debug ( "test" , request . headers ) ;
log . debug ( "test" , request . body ) ;
response . send ( "OK" ) ;
} ) ;
2022-04-17 13:30:58 +02:00
}
2021-08-09 12:16:27 +02:00
// Robots.txt
app . get ( "/robots.txt" , async ( _request , response ) => {
let txt = "User-agent: *\nDisallow:" ;
2022-10-03 16:48:34 +02:00
if ( ! await setting ( "searchEngineIndex" ) ) {
2021-08-09 12:16:27 +02:00
txt += " /" ;
}
response . setHeader ( "Content-Type" , "text/plain" ) ;
response . send ( txt ) ;
} ) ;
2021-06-25 15:55:49 +02:00
2021-07-27 18:52:31 +02:00
// Basic Auth Router here
// Prometheus API metrics /metrics
// With Basic Auth using the first user's username/password
2023-02-15 02:39:29 +02:00
app . get ( "/metrics" , apiAuth , prometheusAPIMetrics ( ) ) ;
2021-08-09 12:16:27 +02:00
2022-06-05 17:43:25 +02:00
app . use ( "/" , expressStaticGzip ( "dist" , {
enableBrotli : true ,
} ) ) ;
2021-07-22 09:22:15 +02:00
2021-09-21 15:22:35 +02:00
// ./data/upload
app . use ( "/upload" , express . static ( Database . uploadDir ) ) ;
2021-09-14 06:10:25 +02:00
app . get ( "/.well-known/change-password" , async ( _ , response ) => {
response . redirect ( "https://github.com/louislam/uptime-kuma/wiki/Reset-Password-via-CLI" ) ;
} ) ;
2021-09-14 08:55:45 +02:00
// API Router
const apiRouter = require ( "./routers/api-router" ) ;
app . use ( apiRouter ) ;
2021-09-11 13:40:03 +02:00
2022-05-30 09:45:44 +02:00
// Status Page Router
const statusPageRouter = require ( "./routers/status-page-router" ) ;
app . use ( statusPageRouter ) ;
2021-10-19 08:26:10 +02:00
// Universal Route Handler, must be at the end of all express routes.
2021-08-09 12:16:27 +02:00
app . get ( "*" , async ( _request , response ) => {
2021-09-21 15:22:35 +02:00
if ( _request . originalUrl . startsWith ( "/upload/" ) ) {
response . status ( 404 ) . send ( "File not found." ) ;
} else {
2022-05-30 09:45:44 +02:00
response . send ( server . indexHTML ) ;
2021-09-21 15:22:35 +02:00
}
2021-07-09 08:14:03 +02:00
} ) ;
2023-10-13 21:00:34 +02:00
log . debug ( "server" , "Adding socket handler" ) ;
2021-07-27 19:47:13 +02:00
io . on ( "connection" , async ( socket ) => {
2021-07-13 12:08:12 +02:00
2024-03-15 16:02:55 +02:00
await sendInfo ( socket , true ) ;
2021-07-13 12:08:12 +02:00
2021-07-11 07:47:57 +02:00
if ( needSetup ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Redirect to setup page" ) ;
2021-09-21 15:22:35 +02:00
socket . emit ( "setup" ) ;
2021-07-11 07:47:57 +02:00
}
2021-07-30 05:33:44 +02:00
// ***************************
2021-09-11 13:40:03 +02:00
// Public Socket API
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
socket . on ( "loginByToken" , async ( token , callback ) => {
2022-07-31 17:36:33 +02:00
const clientIP = await server . getClientIP ( socket ) ;
log . info ( "auth" , ` Login by token. IP= ${ clientIP } ` ) ;
2021-06-25 15:55:49 +02:00
try {
2023-06-27 09:54:33 +02:00
let decoded = jwt . verify ( token , server . jwtSecret ) ;
2021-06-25 15:55:49 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "auth" , "Username from JWT: " + decoded . username ) ;
2021-06-25 15:55:49 +02:00
let user = await R . findOne ( "user" , " username = ? AND active = 1 " , [
2021-07-27 19:47:13 +02:00
decoded . username ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
if ( user ) {
2023-10-23 00:21:39 +02:00
// Check if the password changed
if ( decoded . h !== shake256 ( user . password , SHAKE256 _LENGTH ) ) {
throw new Error ( "The token is invalid due to password change or old token" ) ;
}
2022-04-13 17:33:37 +02:00
log . debug ( "auth" , "afterLogin" ) ;
2024-03-15 16:02:55 +02:00
await afterLogin ( socket , user ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "auth" , "afterLogin ok" ) ;
2021-06-25 15:55:49 +02:00
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Successfully logged in user ${ decoded . username } . IP= ${ clientIP } ` ) ;
2021-08-03 19:03:40 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} else {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Inactive or deleted user ${ decoded . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "authUserInactiveOrDeleted" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} catch ( error ) {
2022-07-31 17:36:33 +02:00
log . error ( "auth" , ` Invalid token. IP= ${ clientIP } ` ) ;
2023-10-09 01:01:54 +02:00
if ( error . message ) {
log . error ( "auth" , error . message , ` IP= ${ clientIP } ` ) ;
}
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "authInvalidToken" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} ) ;
socket . on ( "login" , async ( data , callback ) => {
2022-07-31 17:36:33 +02:00
const clientIP = await server . getClientIP ( socket ) ;
log . info ( "auth" , ` Login by username + password. IP= ${ clientIP } ` ) ;
2021-06-25 15:55:49 +02:00
2022-03-29 11:38:48 +02:00
// Checking
if ( typeof callback !== "function" ) {
return ;
}
if ( ! data ) {
return ;
}
2021-06-25 15:55:49 +02:00
2021-10-23 10:35:13 +02:00
// Login Rate Limit
2023-02-11 08:41:02 +02:00
if ( ! await loginRateLimiter . pass ( callback ) ) {
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Too many failed requests for user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-10-23 10:35:13 +02:00
return ;
}
2021-09-21 15:22:35 +02:00
let user = await login ( data . username , data . password ) ;
2021-07-13 16:22:46 +02:00
2021-07-27 18:52:31 +02:00
if ( user ) {
2022-04-26 00:26:26 +02:00
if ( user . twofa _status === 0 ) {
2024-03-15 16:02:55 +02:00
await afterLogin ( socket , user ) ;
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Successfully logged in user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
2023-10-09 01:01:54 +02:00
token : User . createJWT ( user , server . jwtSecret ) ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
2022-04-26 00:26:26 +02:00
if ( user . twofa _status === 1 && ! data . token ) {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` 2FA token required for user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
tokenRequired : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
if ( data . token ) {
2022-04-16 19:39:49 +02:00
let verify = notp . totp . verify ( data . token , user . twofa _secret , twoFAVerifyOptions ) ;
2021-09-09 21:10:31 +02:00
2021-10-19 00:42:33 +02:00
if ( user . twofa _last _token !== data . token && verify ) {
2024-03-15 16:02:55 +02:00
await afterLogin ( socket , user ) ;
2021-10-19 00:42:33 +02:00
await R . exec ( "UPDATE `user` SET twofa_last_token = ? WHERE id = ? " , [
data . token ,
socket . userID ,
] ) ;
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Successfully logged in user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
2023-10-09 01:01:54 +02:00
token : User . createJWT ( user , server . jwtSecret ) ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . warn ( "auth" , ` Invalid token provided for user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "authInvalidToken" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
}
2021-06-25 15:55:49 +02:00
} else {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . warn ( "auth" , ` Incorrect username or password for user ${ data . username } . IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "authIncorrectCreds" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
}
} ) ;
socket . on ( "logout" , async ( callback ) => {
2022-03-29 11:38:48 +02:00
// Rate Limit
2023-02-11 08:41:02 +02:00
if ( ! await loginRateLimiter . pass ( callback ) ) {
2022-03-29 11:38:48 +02:00
return ;
}
2021-09-21 15:22:35 +02:00
socket . leave ( socket . userID ) ;
2021-06-25 15:55:49 +02:00
socket . userID = null ;
2022-03-29 11:38:48 +02:00
if ( typeof callback === "function" ) {
callback ( ) ;
}
2021-07-11 07:47:57 +02:00
} ) ;
2022-03-29 11:38:48 +02:00
socket . on ( "prepare2FA" , async ( currentPassword , callback ) => {
2021-09-09 21:10:31 +02:00
try {
2023-02-11 08:41:02 +02:00
if ( ! await twoFaRateLimiter . pass ( callback ) ) {
2022-03-29 11:38:48 +02:00
return ;
}
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2022-03-29 11:38:48 +02:00
await doubleCheckPassword ( socket , currentPassword ) ;
2021-09-09 21:10:31 +02:00
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-09-09 21:10:31 +02:00
2022-04-26 00:26:26 +02:00
if ( user . twofa _status === 0 ) {
2021-10-18 11:37:11 +02:00
let newSecret = genSecret ( ) ;
2021-09-09 21:10:31 +02:00
let encodedSecret = base32 . encode ( newSecret ) ;
2021-09-30 18:23:18 +02:00
// Google authenticator doesn't like equal signs
// The fix is found at https://github.com/guyht/notp
// Related issue: https://github.com/louislam/uptime-kuma/issues/486
encodedSecret = encodedSecret . toString ( ) . replace ( /=/g , "" ) ;
2021-09-11 20:25:51 +02:00
let uri = ` otpauth://totp/Uptime%20Kuma: ${ user . username } ?secret= ${ encodedSecret } ` ;
2021-09-09 21:10:31 +02:00
await R . exec ( "UPDATE `user` SET twofa_secret = ? WHERE id = ? " , [
newSecret ,
socket . userID ,
] ) ;
callback ( {
ok : true ,
uri : uri ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "2faAlreadyEnabled" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} catch ( error ) {
callback ( {
ok : false ,
2022-03-29 11:38:48 +02:00
msg : error . message ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
2022-03-29 11:38:48 +02:00
socket . on ( "save2FA" , async ( currentPassword , callback ) => {
2022-07-31 17:36:33 +02:00
const clientIP = await server . getClientIP ( socket ) ;
2021-09-09 21:10:31 +02:00
try {
2023-02-11 08:41:02 +02:00
if ( ! await twoFaRateLimiter . pass ( callback ) ) {
2022-03-29 11:38:48 +02:00
return ;
}
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2022-03-29 11:38:48 +02:00
await doubleCheckPassword ( socket , currentPassword ) ;
2021-09-09 21:10:31 +02:00
await R . exec ( "UPDATE `user` SET twofa_status = 1 WHERE id = ? " , [
socket . userID ,
] ) ;
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Saved 2FA token. IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "2faEnabled" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} catch ( error ) {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . error ( "auth" , ` Error changing 2FA token. IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : false ,
2022-03-29 11:38:48 +02:00
msg : error . message ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
2022-03-29 11:38:48 +02:00
socket . on ( "disable2FA" , async ( currentPassword , callback ) => {
2022-07-31 17:36:33 +02:00
const clientIP = await server . getClientIP ( socket ) ;
2021-09-09 21:10:31 +02:00
try {
2023-02-11 08:41:02 +02:00
if ( ! await twoFaRateLimiter . pass ( callback ) ) {
2022-03-29 11:38:48 +02:00
return ;
}
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2022-03-29 11:38:48 +02:00
await doubleCheckPassword ( socket , currentPassword ) ;
2021-11-18 12:22:03 +02:00
await TwoFA . disable2FA ( socket . userID ) ;
2021-09-09 21:10:31 +02:00
2022-07-31 17:36:33 +02:00
log . info ( "auth" , ` Disabled 2FA token. IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "2faDisabled" ,
msgi18n : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} catch ( error ) {
2021-11-11 13:31:28 +02:00
2022-07-31 17:36:33 +02:00
log . error ( "auth" , ` Error disabling 2FA token. IP= ${ clientIP } ` ) ;
2021-11-11 13:31:28 +02:00
2021-09-09 21:10:31 +02:00
callback ( {
ok : false ,
2022-03-29 11:38:48 +02:00
msg : error . message ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
2022-03-29 11:38:48 +02:00
socket . on ( "verifyToken" , async ( token , currentPassword , callback ) => {
try {
checkLogin ( socket ) ;
await doubleCheckPassword ( socket , currentPassword ) ;
2021-09-09 21:10:31 +02:00
2022-03-29 11:38:48 +02:00
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
] ) ;
2021-09-09 21:10:31 +02:00
2022-04-16 19:39:49 +02:00
let verify = notp . totp . verify ( token , user . twofa _secret , twoFAVerifyOptions ) ;
2021-09-09 21:10:31 +02:00
2022-03-29 11:38:48 +02:00
if ( user . twofa _last _token !== token && verify ) {
callback ( {
ok : true ,
valid : true ,
} ) ;
} else {
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "authInvalidToken" ,
msgi18n : true ,
2022-03-29 11:38:48 +02:00
valid : false ,
} ) ;
}
} catch ( error ) {
2021-09-09 21:10:31 +02:00
callback ( {
ok : false ,
2022-03-29 11:38:48 +02:00
msg : error . message ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
socket . on ( "twoFAStatus" , async ( callback ) => {
try {
2022-03-29 11:38:48 +02:00
checkLogin ( socket ) ;
2021-09-09 21:10:31 +02:00
let user = await R . findOne ( "user" , " id = ? AND active = 1 " , [
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-09-09 21:10:31 +02:00
2022-04-26 00:26:26 +02:00
if ( user . twofa _status === 1 ) {
2021-09-09 21:10:31 +02:00
callback ( {
ok : true ,
status : true ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
} else {
callback ( {
ok : true ,
status : false ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} catch ( error ) {
callback ( {
ok : false ,
2022-03-29 11:38:48 +02:00
msg : error . message ,
2021-09-21 15:22:35 +02:00
} ) ;
2021-09-09 21:10:31 +02:00
}
} ) ;
2021-07-11 07:47:57 +02:00
socket . on ( "needSetup" , async ( callback ) => {
callback ( needSetup ) ;
} ) ;
socket . on ( "setup" , async ( username , password , callback ) => {
try {
2021-10-21 16:54:04 +02:00
if ( passwordStrength ( password ) . value === "Too weak" ) {
throw new Error ( "Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length." ) ;
}
2023-04-03 13:36:07 +02:00
if ( ( await R . knex ( "user" ) . count ( "id as count" ) . first ( ) ) . count !== 0 ) {
2021-10-18 22:35:47 +02:00
throw new Error ( "Uptime Kuma has been initialized. If you want to run setup again, please delete the database." ) ;
2021-07-11 07:47:57 +02:00
}
2021-09-21 15:22:35 +02:00
let user = R . dispense ( "user" ) ;
2021-07-11 07:47:57 +02:00
user . username = username ;
2021-09-21 15:22:35 +02:00
user . password = passwordHash . generate ( password ) ;
await R . store ( user ) ;
2021-07-11 07:47:57 +02:00
needSetup = false ;
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successAdded" ,
msgi18n : true ,
2021-07-11 07:47:57 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-11 07:47:57 +02:00
} ) ;
}
2021-06-25 15:55:49 +02:00
} ) ;
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
// Auth Only API
2021-07-30 05:33:44 +02:00
// ***************************
2021-06-25 15:55:49 +02:00
2021-07-30 13:18:26 +02:00
// Add a new monitor
2021-06-25 15:55:49 +02:00
socket . on ( "add" , async ( monitor , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
let bean = R . dispense ( "monitor" ) ;
2021-07-09 11:55:48 +02:00
let notificationIDList = monitor . notificationIDList ;
delete monitor . notificationIDList ;
2023-08-07 21:22:32 +02:00
// Ensure status code ranges are strings
if ( ! monitor . accepted _statuscodes . every ( ( code ) => typeof code === "string" ) ) {
throw new Error ( "Accepted status codes are not all strings" ) ;
}
2021-08-06 20:10:38 +02:00
monitor . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
delete monitor . accepted _statuscodes ;
2023-07-17 10:15:44 +02:00
monitor . kafkaProducerBrokers = JSON . stringify ( monitor . kafkaProducerBrokers ) ;
monitor . kafkaProducerSaslOptions = JSON . stringify ( monitor . kafkaProducerSaslOptions ) ;
2021-09-21 15:22:35 +02:00
bean . import ( monitor ) ;
bean . user _id = socket . userID ;
2022-12-08 17:21:55 +02:00
bean . validate ( ) ;
2021-09-21 15:22:35 +02:00
await R . store ( bean ) ;
2021-06-25 15:55:49 +02:00
2021-09-21 15:22:35 +02:00
await updateMonitorNotification ( bean . id , notificationIDList ) ;
2021-07-09 11:55:48 +02:00
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2023-08-04 08:48:21 +02:00
if ( monitor . active !== false ) {
await startMonitor ( socket . userID , bean . id ) ;
}
2021-06-27 10:10:55 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "monitor" , ` Added Monitor: ${ monitor . id } User ID: ${ socket . userID } ` ) ;
2021-11-11 13:31:28 +02:00
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successAdded" ,
msgi18n : true ,
2021-07-27 19:47:13 +02:00
monitorID : bean . id ,
2021-06-25 15:55:49 +02:00
} ) ;
2021-06-27 10:10:55 +02:00
} catch ( e ) {
2021-11-11 13:31:28 +02:00
2022-04-13 17:33:37 +02:00
log . error ( "monitor" , ` Error adding Monitor: ${ monitor . id } User ID: ${ socket . userID } ` ) ;
2021-11-11 13:31:28 +02:00
2021-06-27 10:10:55 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-27 10:10:55 +02:00
} ) ;
}
} ) ;
2021-07-30 13:18:26 +02:00
// Edit a monitor
2021-06-27 10:10:55 +02:00
socket . on ( "editMonitor" , async ( monitor , callback ) => {
try {
2023-06-26 04:44:15 +02:00
let removeGroupChildren = false ;
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-27 10:10:55 +02:00
2021-09-21 15:22:35 +02:00
let bean = await R . findOne ( "monitor" , " id = ? " , [ monitor . id ] ) ;
2021-06-27 10:10:55 +02:00
if ( bean . user _id !== socket . userID ) {
2021-09-21 15:22:35 +02:00
throw new Error ( "Permission denied." ) ;
2021-06-27 10:10:55 +02:00
}
2023-06-26 04:44:15 +02:00
// Check if Parent is Descendant (would cause endless loop)
2023-02-01 21:16:56 +02:00
if ( monitor . parent !== null ) {
const childIDs = await Monitor . getAllChildrenIDs ( monitor . id ) ;
if ( childIDs . includes ( monitor . parent ) ) {
throw new Error ( "Invalid Monitor Group" ) ;
}
}
2023-06-26 04:44:15 +02:00
// Remove children if monitor type has changed (from group to non-group)
if ( bean . type === "group" && monitor . type !== bean . type ) {
removeGroupChildren = true ;
}
2023-08-07 21:22:32 +02:00
// Ensure status code ranges are strings
if ( ! monitor . accepted _statuscodes . every ( ( code ) => typeof code === "string" ) ) {
throw new Error ( "Accepted status codes are not all strings" ) ;
}
2021-09-21 15:22:35 +02:00
bean . name = monitor . name ;
2021-11-12 01:06:32 +02:00
bean . description = monitor . description ;
2023-01-28 03:58:03 +02:00
bean . parent = monitor . parent ;
2021-09-21 15:22:35 +02:00
bean . type = monitor . type ;
bean . url = monitor . url ;
2021-10-02 16:48:27 +02:00
bean . method = monitor . method ;
bean . body = monitor . body ;
bean . headers = monitor . headers ;
2021-11-04 11:12:06 +02:00
bean . basic _auth _user = monitor . basic _auth _user ;
bean . basic _auth _pass = monitor . basic _auth _pass ;
2023-08-06 18:14:56 +02:00
bean . timeout = monitor . timeout ;
2023-09-15 23:13:20 +02:00
bean . oauth _client _id = monitor . oauth _client _id ;
bean . oauth _client _secret = monitor . oauth _client _secret ;
bean . oauth _auth _method = monitor . oauth _auth _method ;
bean . oauth _token _url = monitor . oauth _token _url ;
bean . oauth _scopes = monitor . oauth _scopes ;
2023-01-04 09:37:30 +02:00
bean . tlsCa = monitor . tlsCa ;
bean . tlsCert = monitor . tlsCert ;
bean . tlsKey = monitor . tlsKey ;
2021-09-21 15:22:35 +02:00
bean . interval = monitor . interval ;
2021-09-11 18:54:55 +02:00
bean . retryInterval = monitor . retryInterval ;
2022-01-23 16:22:57 +02:00
bean . resendInterval = monitor . resendInterval ;
2021-07-01 08:03:06 +02:00
bean . hostname = monitor . hostname ;
2023-01-08 10:22:36 +02:00
bean . game = monitor . game ;
2021-07-19 18:23:06 +02:00
bean . maxretries = monitor . maxretries ;
2022-06-21 16:33:09 +02:00
bean . port = parseInt ( monitor . port ) ;
2023-02-12 09:14:41 +02:00
if ( isNaN ( bean . port ) ) {
bean . port = null ;
}
2021-07-01 11:19:28 +02:00
bean . keyword = monitor . keyword ;
2023-04-06 02:10:21 +02:00
bean . invertKeyword = monitor . invertKeyword ;
2021-07-30 13:18:26 +02:00
bean . ignoreTls = monitor . ignoreTls ;
2022-04-05 15:27:50 +02:00
bean . expiryNotification = monitor . expiryNotification ;
2021-07-30 13:18:26 +02:00
bean . upsideDown = monitor . upsideDown ;
2022-07-14 09:32:51 +02:00
bean . packetSize = monitor . packetSize ;
2021-08-08 18:23:51 +02:00
bean . maxredirects = monitor . maxredirects ;
2021-08-05 13:04:38 +02:00
bean . accepted _statuscodes _json = JSON . stringify ( monitor . accepted _statuscodes ) ;
2021-08-23 00:05:48 +02:00
bean . dns _resolve _type = monitor . dns _resolve _type ;
bean . dns _resolve _server = monitor . dns _resolve _server ;
2021-09-30 18:09:43 +02:00
bean . pushToken = monitor . pushToken ;
2022-01-13 20:28:45 +02:00
bean . docker _container = monitor . docker _container ;
2022-07-22 17:47:04 +02:00
bean . docker _host = monitor . docker _host ;
2021-10-30 19:37:15 +02:00
bean . proxyId = Number . isInteger ( monitor . proxyId ) ? monitor . proxyId : null ;
2021-12-18 23:35:18 +02:00
bean . mqttUsername = monitor . mqttUsername ;
2022-04-18 13:05:14 +02:00
bean . mqttPassword = monitor . mqttPassword ;
2021-12-18 23:35:18 +02:00
bean . mqttTopic = monitor . mqttTopic ;
bean . mqttSuccessMessage = monitor . mqttSuccessMessage ;
2023-12-02 19:36:19 +02:00
bean . mqttCheckType = monitor . mqttCheckType ;
2022-05-12 19:48:03 +02:00
bean . databaseConnectionString = monitor . databaseConnectionString ;
bean . databaseQuery = monitor . databaseQuery ;
2022-05-13 19:58:23 +02:00
bean . authMethod = monitor . authMethod ;
bean . authWorkstation = monitor . authWorkstation ;
bean . authDomain = monitor . authDomain ;
2022-08-20 17:45:11 +02:00
bean . grpcUrl = monitor . grpcUrl ;
2022-08-03 07:00:39 +02:00
bean . grpcProtobuf = monitor . grpcProtobuf ;
2022-12-28 17:31:33 +02:00
bean . grpcServiceName = monitor . grpcServiceName ;
2022-08-03 07:00:39 +02:00
bean . grpcMethod = monitor . grpcMethod ;
bean . grpcBody = monitor . grpcBody ;
bean . grpcMetadata = monitor . grpcMetadata ;
bean . grpcEnableTls = monitor . grpcEnableTls ;
2022-05-12 11:48:38 +02:00
bean . radiusUsername = monitor . radiusUsername ;
bean . radiusPassword = monitor . radiusPassword ;
bean . radiusCalledStationId = monitor . radiusCalledStationId ;
bean . radiusCallingStationId = monitor . radiusCallingStationId ;
bean . radiusSecret = monitor . radiusSecret ;
2022-08-12 02:57:03 +02:00
bean . httpBodyEncoding = monitor . httpBodyEncoding ;
2023-07-13 17:37:26 +02:00
bean . expectedValue = monitor . expectedValue ;
bean . jsonPath = monitor . jsonPath ;
2023-07-17 10:15:44 +02:00
bean . kafkaProducerTopic = monitor . kafkaProducerTopic ;
bean . kafkaProducerBrokers = JSON . stringify ( monitor . kafkaProducerBrokers ) ;
bean . kafkaProducerAllowAutoTopicCreation = monitor . kafkaProducerAllowAutoTopicCreation ;
bean . kafkaProducerSaslOptions = JSON . stringify ( monitor . kafkaProducerSaslOptions ) ;
bean . kafkaProducerMessage = monitor . kafkaProducerMessage ;
2023-10-28 08:42:55 +02:00
bean . kafkaProducerSsl = monitor . kafkaProducerSsl ;
bean . kafkaProducerAllowAutoTopicCreation =
monitor . kafkaProducerAllowAutoTopicCreation ;
2023-08-07 21:14:21 +02:00
bean . gamedigGivenPortOnly = monitor . gamedigGivenPortOnly ;
2023-12-01 09:29:10 +02:00
bean . remote _browser = monitor . remote _browser ;
2021-06-27 10:10:55 +02:00
2022-12-08 17:21:55 +02:00
bean . validate ( ) ;
2021-09-21 15:22:35 +02:00
await R . store ( bean ) ;
2021-06-27 10:10:55 +02:00
2023-06-26 04:44:15 +02:00
if ( removeGroupChildren ) {
await Monitor . unlinkAllChildren ( monitor . id ) ;
}
2021-09-21 15:22:35 +02:00
await updateMonitorNotification ( bean . id , monitor . notificationIDList ) ;
2021-07-09 11:55:48 +02:00
2023-09-09 19:54:03 +02:00
if ( await bean . isActive ( ) ) {
2021-09-21 15:22:35 +02:00
await restartMonitor ( socket . userID , bean . id ) ;
2021-06-27 10:10:55 +02:00
}
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2021-06-25 15:55:49 +02:00
2021-06-27 10:10:55 +02:00
callback ( {
ok : true ,
msg : "Saved." ,
2023-09-01 14:51:28 +02:00
msgi18n : true ,
2021-07-27 19:47:13 +02:00
monitorID : bean . id ,
2021-06-27 10:10:55 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} catch ( e ) {
2022-04-13 17:33:37 +02:00
log . error ( "monitor" , e ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-08-26 12:55:19 +02:00
socket . on ( "getMonitorList" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
} ) ;
} catch ( e ) {
2022-04-13 17:33:37 +02:00
log . error ( "monitor" , e ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-06-25 15:55:49 +02:00
socket . on ( "getMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "monitor" , ` Get Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-06-25 15:55:49 +02:00
let bean = await R . findOne ( "monitor" , " id = ? AND user_id = ? " , [
monitorID ,
socket . userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2021-07-09 11:55:48 +02:00
monitor : await bean . toJSON ( ) ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-10-18 13:00:39 +02:00
socket . on ( "getMonitorBeats" , async ( monitorID , period , callback ) => {
try {
checkLogin ( socket ) ;
2022-04-13 17:33:37 +02:00
log . info ( "monitor" , ` Get Monitor Beats: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-10-18 13:00:39 +02:00
2021-10-22 12:38:41 +02:00
if ( period == null ) {
throw new Error ( "Invalid period." ) ;
}
2023-02-12 10:59:07 +02:00
const sqlHourOffset = Database . sqlHourOffset ( ) ;
2021-10-22 12:38:41 +02:00
let list = await R . getAll ( `
2023-02-11 08:41:02 +02:00
SELECT *
FROM heartbeat
WHERE monitor _id = ?
2023-02-12 10:59:07 +02:00
AND time > $ { sqlHourOffset }
2021-10-22 12:38:41 +02:00
ORDER BY time ASC
` , [
monitorID ,
2023-02-12 10:59:07 +02:00
- period ,
2021-10-22 12:38:41 +02:00
] ) ;
2021-10-18 13:00:39 +02:00
callback ( {
2021-10-22 12:38:41 +02:00
ok : true ,
data : list ,
2021-10-18 13:00:39 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-06-25 15:55:49 +02:00
// Start or Resume the monitor
socket . on ( "resumeMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
await startMonitor ( socket . userID , monitorID ) ;
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successResumed" ,
msgi18n : true ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "pauseMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
await pauseMonitor ( socket . userID , monitorID ) ;
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successPaused" ,
msgi18n : true ,
2021-06-25 15:55:49 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "deleteMonitor" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Delete Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-06-25 15:55:49 +02:00
2022-04-07 16:53:32 +02:00
if ( monitorID in server . monitorList ) {
2023-02-23 18:16:49 +02:00
await server . monitorList [ monitorID ] . stop ( ) ;
2022-04-07 16:53:32 +02:00
delete server . monitorList [ monitorID ] ;
2021-06-25 15:55:49 +02:00
}
2023-06-29 16:41:01 +02:00
const startTime = Date . now ( ) ;
2021-06-25 15:55:49 +02:00
await R . exec ( "DELETE FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
socket . userID ,
2021-06-25 15:55:49 +02:00
] ) ;
2023-03-05 09:59:43 +02:00
// Fix #2880
apicache . clear ( ) ;
2023-06-29 16:41:01 +02:00
const endTime = Date . now ( ) ;
log . info ( "DB" , ` Delete Monitor completed in : ${ endTime - startTime } ms ` ) ;
2021-06-25 15:55:49 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successDeleted" ,
msgi18n : true ,
2021-06-25 15:55:49 +02:00
} ) ;
2022-04-07 17:02:57 +02:00
await server . sendMonitorList ( socket ) ;
2021-06-25 15:55:49 +02:00
} catch ( e ) {
2021-08-26 12:55:19 +02:00
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "getTags" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
const list = await R . findAll ( "tag" ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
tags : list . map ( bean => bean . toJSON ( ) ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "addTag" , async ( tag , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
let bean = R . dispense ( "tag" ) ;
bean . name = tag . name ;
bean . color = tag . color ;
await R . store ( bean ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
tag : await bean . toJSON ( ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "editTag" , async ( tag , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2022-05-26 10:52:18 +02:00
let bean = await R . findOne ( "tag" , " id = ? " , [ tag . id ] ) ;
if ( bean == null ) {
callback ( {
ok : false ,
2023-09-26 22:53:14 +02:00
msg : "tagNotFound" ,
msgi18n : true ,
2022-05-26 10:52:18 +02:00
} ) ;
return ;
}
2021-09-21 15:22:35 +02:00
bean . name = tag . name ;
bean . color = tag . color ;
await R . store ( bean ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-01 14:51:28 +02:00
msg : "Saved." ,
msgi18n : true ,
2021-08-26 12:55:19 +02:00
tag : await bean . toJSON ( ) ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "deleteTag" , async ( tagID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-21 15:22:35 +02:00
await R . exec ( "DELETE FROM tag WHERE id = ? " , [ tagID ] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successDeleted" ,
msgi18n : true ,
2021-08-26 12:55:19 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "addMonitorTag" , async ( tagID , monitorID , value , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
await R . exec ( "INSERT INTO monitor_tag (tag_id, monitor_id, value) VALUES (?, ?, ?)" , [
tagID ,
monitorID ,
value ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successAdded" ,
msgi18n : true ,
2021-08-26 12:55:19 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "editMonitorTag" , async ( tagID , monitorID , value , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
await R . exec ( "UPDATE monitor_tag SET value = ? WHERE tag_id = ? AND monitor_id = ?" , [
value ,
tagID ,
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successEdited" ,
msgi18n : true ,
2021-08-26 12:55:19 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-09-10 08:22:34 +02:00
socket . on ( "deleteMonitorTag" , async ( tagID , monitorID , value , callback ) => {
2021-08-26 12:55:19 +02:00
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-26 12:55:19 +02:00
2021-09-10 08:22:34 +02:00
await R . exec ( "DELETE FROM monitor_tag WHERE tag_id = ? AND monitor_id = ? AND value = ?" , [
2021-08-26 12:55:19 +02:00
tagID ,
monitorID ,
2021-09-10 08:22:34 +02:00
value ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successDeleted" ,
msgi18n : true ,
2021-08-26 12:55:19 +02:00
} ) ;
} catch ( e ) {
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2023-09-23 13:03:45 +02:00
socket . on ( "monitorImportantHeartbeatListCount" , async ( monitorID , callback ) => {
try {
checkLogin ( socket ) ;
let count ;
if ( monitorID == null ) {
count = await R . count ( "heartbeat" , "important = 1" ) ;
} else {
count = await R . count ( "heartbeat" , "monitor_id = ? AND important = 1" , [
monitorID ,
] ) ;
}
2021-08-26 12:55:19 +02:00
callback ( {
ok : true ,
2023-09-23 13:03:45 +02:00
count : count ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
2021-08-26 12:55:19 +02:00
} ) ;
2023-09-23 13:03:45 +02:00
}
} ) ;
2021-08-26 12:55:19 +02:00
2023-09-23 13:03:45 +02:00
socket . on ( "monitorImportantHeartbeatListPaged" , async ( monitorID , offset , count , callback ) => {
try {
checkLogin ( socket ) ;
let list ;
if ( monitorID == null ) {
list = await R . find ( "heartbeat" , `
important = 1
ORDER BY time DESC
LIMIT ?
OFFSET ?
` , [
count ,
offset ,
] ) ;
} else {
list = await R . find ( "heartbeat" , `
monitor _id = ?
AND important = 1
ORDER BY time DESC
LIMIT ?
OFFSET ?
` , [
monitorID ,
count ,
offset ,
] ) ;
}
callback ( {
ok : true ,
data : list ,
} ) ;
2021-08-26 12:55:19 +02:00
} catch ( e ) {
2021-06-25 15:55:49 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
socket . on ( "changePassword" , async ( password , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-06-25 15:55:49 +02:00
2023-02-11 08:41:02 +02:00
if ( ! password . newPassword ) {
2021-09-21 15:22:35 +02:00
throw new Error ( "Invalid new password" ) ;
2021-06-25 15:55:49 +02:00
}
2021-10-21 16:54:04 +02:00
if ( passwordStrength ( password . newPassword ) . value === "Too weak" ) {
throw new Error ( "Password is too weak. It should contain alphabetic and numeric characters. It must be at least 6 characters in length." ) ;
}
2022-03-29 11:38:48 +02:00
let user = await doubleCheckPassword ( socket , password . currentPassword ) ;
await user . resetPassword ( password . newPassword ) ;
2021-06-25 15:55:49 +02:00
2023-12-11 13:26:20 +02:00
server . disconnectAllSocketClients ( user . id , socket . id ) ;
2023-12-10 14:40:40 +02:00
2022-03-29 11:38:48 +02:00
callback ( {
ok : true ,
2023-12-18 13:52:49 +02:00
token : User . createJWT ( user , server . jwtSecret ) ,
2023-09-26 22:53:14 +02:00
msg : "successAuthChangePassword" ,
msgi18n : true ,
2022-03-29 11:38:48 +02:00
} ) ;
2021-06-25 15:55:49 +02:00
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-06-25 15:55:49 +02:00
} ) ;
}
} ) ;
2021-07-06 08:30:10 +02:00
2021-07-31 15:57:58 +02:00
socket . on ( "getSettings" , async ( callback ) => {
2021-07-06 08:30:10 +02:00
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2022-10-09 14:59:58 +02:00
const data = await getSettings ( "general" ) ;
if ( ! data . serverTimezone ) {
data . serverTimezone = await server . getTimezone ( ) ;
}
2021-07-06 08:30:10 +02:00
callback ( {
ok : true ,
2022-10-09 14:59:58 +02:00
data : data ,
2021-07-31 15:57:58 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2022-03-29 11:38:48 +02:00
socket . on ( "setSettings" , async ( data , currentPassword , callback ) => {
2021-07-31 15:57:58 +02:00
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-31 15:57:58 +02:00
2022-04-28 17:12:16 +02:00
// If currently is disabled auth, don't need to check
// Disabled Auth + Want to Disable Auth => No Check
// Disabled Auth + Want to Enable Auth => No Check
// Enabled Auth + Want to Disable Auth => Check!!
// Enabled Auth + Want to Enable Auth => No Check
const currentDisabledAuth = await setting ( "disableAuth" ) ;
if ( ! currentDisabledAuth && data . disableAuth ) {
2022-03-29 11:38:48 +02:00
await doubleCheckPassword ( socket , currentPassword ) ;
}
2024-04-18 19:08:31 +02:00
// Log out all clients if enabling auth
// GHSA-23q2-5gf8-gjpp
if ( currentDisabledAuth && ! data . disableAuth ) {
server . disconnectAllSocketClients ( socket . userID , socket . id ) ;
}
2023-06-27 09:54:33 +02:00
const previousChromeExecutable = await Settings . get ( "chromeExecutable" ) ;
2023-08-28 10:15:48 +02:00
const previousNSCDStatus = await Settings . get ( "nscd" ) ;
2023-06-27 09:54:33 +02:00
2021-09-15 14:40:26 +02:00
await setSettings ( "general" , data ) ;
2022-10-08 17:56:58 +02:00
server . entryPage = data . entryPage ;
2021-07-31 15:57:58 +02:00
2022-10-09 14:59:58 +02:00
// Also need to apply timezone globally
if ( data . serverTimezone ) {
await server . setTimezone ( data . serverTimezone ) ;
}
2021-07-31 15:57:58 +02:00
2023-06-27 09:54:33 +02:00
// If Chrome Executable is changed, need to reset the browser
if ( previousChromeExecutable !== data . chromeExecutable ) {
log . info ( "settings" , "Chrome executable is changed. Resetting Chrome..." ) ;
await resetChrome ( ) ;
}
2023-08-28 10:15:48 +02:00
// Update nscd status
if ( previousNSCDStatus !== data . nscd ) {
if ( data . nscd ) {
2023-11-30 10:12:04 +02:00
await server . startNSCDServices ( ) ;
2023-08-28 10:15:48 +02:00
} else {
2023-11-30 10:12:04 +02:00
await server . stopNSCDServices ( ) ;
2023-08-28 10:15:48 +02:00
}
}
2021-07-31 15:57:58 +02:00
callback ( {
ok : true ,
2023-09-01 14:51:28 +02:00
msg : "Saved." ,
msgi18n : true ,
2021-07-06 08:30:10 +02:00
} ) ;
2024-03-15 16:02:55 +02:00
await sendInfo ( socket ) ;
await server . sendMaintenanceList ( socket ) ;
2021-10-08 14:03:52 +02:00
2021-07-06 08:30:10 +02:00
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-06 08:30:10 +02:00
} ) ;
}
} ) ;
2021-07-09 08:14:03 +02:00
// Add or Edit
socket . on ( "addNotification" , async ( notification , notificationID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
let notificationBean = await Notification . save ( notification , notificationID , socket . userID ) ;
await sendNotificationList ( socket ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
2023-09-01 14:51:28 +02:00
msg : "Saved." ,
msgi18n : true ,
2021-09-09 15:24:29 +02:00
id : notificationBean . id ,
2021-07-09 08:14:03 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
socket . on ( "deleteNotification" , async ( notificationID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
await Notification . delete ( notificationID , socket . userID ) ;
await sendNotificationList ( socket ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
2023-09-26 22:53:14 +02:00
msg : "successDeleted" ,
msgi18n : true ,
2021-07-09 08:14:03 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
socket . on ( "testNotification" , async ( notification , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-09 08:14:03 +02:00
2021-09-21 15:22:35 +02:00
let msg = await Notification . send ( notification , notification . name + " Testing" ) ;
2021-07-09 08:14:03 +02:00
callback ( {
ok : true ,
2021-07-27 19:47:13 +02:00
msg ,
2021-07-09 08:14:03 +02:00
} ) ;
} catch ( e ) {
2021-09-21 15:22:35 +02:00
console . error ( e ) ;
2021-07-18 14:49:46 +02:00
2021-07-09 08:14:03 +02:00
callback ( {
ok : false ,
2021-07-27 19:47:13 +02:00
msg : e . message ,
2021-07-09 08:14:03 +02:00
} ) ;
}
} ) ;
2021-07-18 12:51:58 +02:00
socket . on ( "checkApprise" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-07-18 12:51:58 +02:00
callback ( Notification . checkApprise ( ) ) ;
} catch ( e ) {
callback ( false ) ;
}
} ) ;
2021-08-03 19:03:40 +02:00
2021-08-29 18:47:01 +02:00
socket . on ( "clearEvents" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-29 18:47:01 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Clear Events Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-08-29 18:47:01 +02:00
await R . exec ( "UPDATE heartbeat SET msg = ?, important = ? WHERE monitor_id = ? " , [
"" ,
"0" ,
monitorID ,
] ) ;
callback ( {
ok : true ,
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearHeartbeats" , async ( monitorID , callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-08-29 18:47:01 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Clear Heartbeats Monitor: ${ monitorID } User ID: ${ socket . userID } ` ) ;
2021-08-29 18:47:01 +02:00
await R . exec ( "DELETE FROM heartbeat WHERE monitor_id = ?" , [
monitorID
] ) ;
2021-09-04 20:03:40 +02:00
await sendHeartbeatList ( socket , monitorID , true , true ) ;
2021-08-29 18:47:01 +02:00
callback ( {
ok : true ,
2021-09-01 00:36:24 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
socket . on ( "clearStatistics" , async ( callback ) => {
try {
2021-09-21 15:22:35 +02:00
checkLogin ( socket ) ;
2021-09-01 00:36:24 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Clear Statistics User ID: ${ socket . userID } ` ) ;
2021-09-01 00:36:24 +02:00
await R . exec ( "DELETE FROM heartbeat" ) ;
2024-01-05 14:51:05 +02:00
await R . exec ( "DELETE FROM stat_daily" ) ;
await R . exec ( "DELETE FROM stat_hourly" ) ;
await R . exec ( "DELETE FROM stat_minutely" ) ;
// Restart all monitors to reset the stats
for ( let monitorID in server . monitorList ) {
await restartMonitor ( socket . userID , monitorID ) ;
}
2021-09-01 00:36:24 +02:00
callback ( {
ok : true ,
2021-08-29 18:47:01 +02:00
} ) ;
} catch ( e ) {
callback ( {
ok : false ,
msg : e . message ,
} ) ;
}
} ) ;
2021-09-16 16:48:28 +02:00
// Status Page Socket Handler for admin only
statusPageSocketHandler ( socket ) ;
2022-03-29 08:48:02 +02:00
cloudflaredSocketHandler ( socket ) ;
2021-10-26 17:02:32 +02:00
databaseSocketHandler ( socket ) ;
2022-04-07 08:45:37 +02:00
proxySocketHandler ( socket ) ;
2022-07-22 17:47:04 +02:00
dockerSocketHandler ( socket ) ;
2022-09-17 10:54:21 +02:00
maintenanceSocketHandler ( socket ) ;
2023-02-14 21:49:04 +02:00
apiKeySocketHandler ( socket ) ;
2023-12-01 09:29:10 +02:00
remoteBrowserSocketHandler ( socket ) ;
2022-12-12 16:57:57 +02:00
generalSocketHandler ( socket , server ) ;
2024-05-19 22:03:32 +02:00
chartSocketHandler ( socket ) ;
2021-09-16 16:48:28 +02:00
2022-04-13 17:33:37 +02:00
log . debug ( "server" , "added all socket handlers" ) ;
2021-08-03 19:03:40 +02:00
2021-08-04 07:31:17 +02:00
// ***************************
// Better do anything after added all socket handlers here
// ***************************
2022-04-13 17:33:37 +02:00
log . debug ( "auth" , "check auto login" ) ;
2021-08-03 19:03:40 +02:00
if ( await setting ( "disableAuth" ) ) {
2022-04-13 17:33:37 +02:00
log . info ( "auth" , "Disabled Auth: auto login to admin" ) ;
2024-03-15 16:02:55 +02:00
await afterLogin ( socket , await R . findOne ( "user" ) ) ;
2021-09-21 15:22:35 +02:00
socket . emit ( "autoLogin" ) ;
2021-08-03 19:03:40 +02:00
} else {
2024-04-21 20:39:44 +02:00
socket . emit ( "loginRequired" ) ;
2022-04-13 17:33:37 +02:00
log . debug ( "auth" , "need auth" ) ;
2021-08-03 19:03:40 +02:00
}
2021-06-25 15:55:49 +02:00
} ) ;
2023-10-13 21:00:34 +02:00
log . debug ( "server" , "Init the server" ) ;
2021-08-10 15:28:54 +02:00
2022-04-19 09:38:59 +02:00
server . httpServer . once ( "error" , async ( err ) => {
2023-10-13 21:00:34 +02:00
log . error ( "server" , "Cannot listen: " + err . message ) ;
2022-04-05 13:41:29 +02:00
await shutdownFunction ( ) ;
2023-10-13 21:00:34 +02:00
process . exit ( 1 ) ;
2021-08-10 15:28:54 +02:00
} ) ;
2021-08-10 10:36:21 +02:00
2024-03-15 16:02:55 +02:00
await server . start ( ) ;
2023-07-24 11:04:50 +02:00
2022-04-19 09:38:59 +02:00
server . httpServer . listen ( port , hostname , ( ) => {
2021-08-10 10:36:21 +02:00
if ( hostname ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , ` Listening on ${ hostname } : ${ port } ` ) ;
2021-08-10 10:36:21 +02:00
} else {
2022-04-13 17:33:37 +02:00
log . info ( "server" , ` Listening on ${ port } ` ) ;
2021-08-10 10:36:21 +02:00
}
2021-06-25 15:55:49 +02:00
startMonitors ( ) ;
2021-08-21 13:50:22 +02:00
checkVersion . startInterval ( ) ;
2021-06-25 15:55:49 +02:00
} ) ;
2023-05-12 18:55:48 +02:00
await initBackgroundJobs ( ) ;
2021-09-27 17:40:38 +02:00
2022-03-30 05:59:49 +02:00
// Start cloudflared at the end if configured
2022-03-30 14:08:26 +02:00
await cloudflaredAutoStart ( cloudflaredToken ) ;
2022-03-30 05:59:49 +02:00
2021-06-25 15:55:49 +02:00
} ) ( ) ;
2022-04-20 20:56:40 +02:00
/ * *
* Update notifications for a given monitor
* @ param { number } monitorID ID of monitor to update
2022-04-21 21:02:18 +02:00
* @ param { number [ ] } notificationIDList List of new notification
2022-04-20 20:56:40 +02:00
* providers to add
* @ returns { Promise < void > }
* /
2021-07-09 11:55:48 +02:00
async function updateMonitorNotification ( monitorID , notificationIDList ) {
2021-08-10 15:37:51 +02:00
await R . exec ( "DELETE FROM monitor_notification WHERE monitor_id = ? " , [
2021-07-27 19:47:13 +02:00
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-07-09 11:55:48 +02:00
for ( let notificationID in notificationIDList ) {
if ( notificationIDList [ notificationID ] ) {
let relation = R . dispense ( "monitor_notification" ) ;
relation . monitor _id = monitorID ;
relation . notification _id = notificationID ;
2021-09-21 15:22:35 +02:00
await R . store ( relation ) ;
2021-07-09 11:55:48 +02:00
}
}
}
2022-04-20 20:56:40 +02:00
/ * *
* Check if a given user owns a specific monitor
2023-08-11 09:46:41 +02:00
* @ param { number } userID ID of user to check
* @ param { number } monitorID ID of monitor to check
2022-04-20 20:56:40 +02:00
* @ returns { Promise < void > }
* @ throws { Error } The specified user does not own the monitor
* /
2021-06-25 15:55:49 +02:00
async function checkOwner ( userID , monitorID ) {
let row = await R . getRow ( "SELECT id FROM monitor WHERE id = ? AND user_id = ? " , [
monitorID ,
userID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
if ( ! row ) {
throw new Error ( "You do not own this monitor." ) ;
}
}
2022-04-20 20:56:40 +02:00
/ * *
* Function called after user login
2021-11-10 07:24:31 +02:00
* This function is used to send the heartbeat list of a monitor .
2022-04-20 20:56:40 +02:00
* @ param { Socket } socket Socket . io instance
2023-08-11 09:46:41 +02:00
* @ param { object } user User object
2022-04-20 20:56:40 +02:00
* @ returns { Promise < void > }
* /
2021-06-25 15:55:49 +02:00
async function afterLogin ( socket , user ) {
socket . userID = user . id ;
2021-09-21 15:22:35 +02:00
socket . join ( user . id ) ;
2021-06-29 10:06:20 +02:00
2022-04-07 17:02:57 +02:00
let monitorList = await server . sendMonitorList ( socket ) ;
2024-03-18 18:16:04 +02:00
await Promise . allSettled ( [
sendInfo ( socket ) ,
server . sendMaintenanceList ( socket ) ,
sendNotificationList ( socket ) ,
sendProxyList ( socket ) ,
sendDockerHostList ( socket ) ,
sendAPIKeyList ( socket ) ,
sendRemoteBrowserList ( socket ) ,
] ) ;
2021-08-23 12:52:55 +02:00
2022-03-21 09:28:59 +02:00
await StatusPage . sendStatusPageList ( io , socket ) ;
2021-08-23 12:52:55 +02:00
for ( let monitorID in monitorList ) {
await sendHeartbeatList ( socket , monitorID ) ;
}
for ( let monitorID in monitorList ) {
2021-09-21 15:22:35 +02:00
await Monitor . sendStats ( io , monitorID , user . id ) ;
2021-08-23 12:52:55 +02:00
}
2022-12-12 16:57:57 +02:00
// Set server timezone from client browser if not set
// It should be run once only
if ( ! await Settings . get ( "initServerTimezone" ) ) {
log . debug ( "server" , "emit initServerTimezone" ) ;
socket . emit ( "initServerTimezone" ) ;
}
2021-06-25 15:55:49 +02:00
}
2022-04-20 20:56:40 +02:00
/ * *
* Initialize the database
2023-08-11 09:46:41 +02:00
* @ param { boolean } testMode Should the connection be
2022-04-20 20:56:40 +02:00
* started in test mode ?
* @ returns { Promise < void > }
* /
2021-11-04 17:19:31 +02:00
async function initDatabase ( testMode = false ) {
2023-10-13 21:00:34 +02:00
log . debug ( "server" , "Connecting to the database" ) ;
2021-11-04 17:19:31 +02:00
await Database . connect ( testMode ) ;
2023-10-13 21:00:34 +02:00
log . info ( "server" , "Connected to the database" ) ;
2021-07-18 12:51:58 +02:00
2021-07-21 20:02:35 +02:00
// Patch the database
2021-09-21 15:22:35 +02:00
await Database . patch ( ) ;
2021-07-21 20:02:35 +02:00
2021-06-25 15:55:49 +02:00
let jwtSecretBean = await R . findOne ( "setting" , " `key` = ? " , [
2021-07-27 19:47:13 +02:00
"jwtSecret" ,
2021-06-25 15:55:49 +02:00
] ) ;
if ( ! jwtSecretBean ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "JWT secret is not found, generate one." ) ;
2021-08-09 14:09:01 +02:00
jwtSecretBean = await initJWTSecret ( ) ;
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Stored JWT secret into database" ) ;
2021-06-25 15:55:49 +02:00
} else {
2023-10-13 21:00:34 +02:00
log . debug ( "server" , "Load JWT secret from database." ) ;
2021-06-25 15:55:49 +02:00
}
2021-07-21 20:02:35 +02:00
// If there is no record in user table, it is a new Uptime Kuma instance, need to setup
2023-04-03 13:36:07 +02:00
if ( ( await R . knex ( "user" ) . count ( "id as count" ) . first ( ) ) . count === 0 ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "No user, need setup" ) ;
2021-07-11 07:47:57 +02:00
needSetup = true ;
}
2023-06-27 09:54:33 +02:00
server . jwtSecret = jwtSecretBean . value ;
2021-06-25 15:55:49 +02:00
}
2022-04-20 20:56:40 +02:00
/ * *
* Start the specified monitor
* @ param { number } userID ID of user who owns monitor
* @ param { number } monitorID ID of monitor to start
* @ returns { Promise < void > }
* /
2021-06-25 15:55:49 +02:00
async function startMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
await checkOwner ( userID , monitorID ) ;
2021-06-25 15:55:49 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Resume Monitor: ${ monitorID } User ID: ${ userID } ` ) ;
2021-06-25 15:55:49 +02:00
await R . exec ( "UPDATE monitor SET active = 1 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
userID ,
2021-06-25 15:55:49 +02:00
] ) ;
let monitor = await R . findOne ( "monitor" , " id = ? " , [
2021-07-27 19:47:13 +02:00
monitorID ,
2021-09-21 15:22:35 +02:00
] ) ;
2021-06-25 15:55:49 +02:00
2022-04-07 16:53:32 +02:00
if ( monitor . id in server . monitorList ) {
2023-02-23 18:16:49 +02:00
await server . monitorList [ monitor . id ] . stop ( ) ;
2021-06-27 10:10:55 +02:00
}
2022-04-07 16:53:32 +02:00
server . monitorList [ monitor . id ] = monitor ;
2023-02-23 18:16:49 +02:00
await monitor . start ( io ) ;
2021-06-25 15:55:49 +02:00
}
2022-04-20 20:56:40 +02:00
/ * *
* Restart a given monitor
* @ param { number } userID ID of user who owns monitor
* @ param { number } monitorID ID of monitor to start
* @ returns { Promise < void > }
* /
2021-06-27 10:10:55 +02:00
async function restartMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
return await startMonitor ( userID , monitorID ) ;
2021-06-27 10:10:55 +02:00
}
2022-04-20 20:56:40 +02:00
/ * *
* Pause a given monitor
2022-04-21 14:01:22 +02:00
* @ param { number } userID ID of user who owns monitor
2022-04-20 20:56:40 +02:00
* @ param { number } monitorID ID of monitor to start
* @ returns { Promise < void > }
* /
2021-06-25 15:55:49 +02:00
async function pauseMonitor ( userID , monitorID ) {
2021-09-21 15:22:35 +02:00
await checkOwner ( userID , monitorID ) ;
2021-06-25 15:55:49 +02:00
2022-04-13 17:33:37 +02:00
log . info ( "manage" , ` Pause Monitor: ${ monitorID } User ID: ${ userID } ` ) ;
2021-06-25 15:55:49 +02:00
await R . exec ( "UPDATE monitor SET active = 0 WHERE id = ? AND user_id = ? " , [
monitorID ,
2021-07-27 19:47:13 +02:00
userID ,
2021-06-25 15:55:49 +02:00
] ) ;
2022-04-07 16:53:32 +02:00
if ( monitorID in server . monitorList ) {
2023-02-23 18:16:49 +02:00
await server . monitorList [ monitorID ] . stop ( ) ;
2023-11-16 14:41:35 +02:00
server . monitorList [ monitorID ] . active = 0 ;
2021-06-25 15:55:49 +02:00
}
}
2023-08-11 09:46:41 +02:00
/ * *
* Resume active monitors
* @ returns { Promise < void > }
* /
2021-06-25 15:55:49 +02:00
async function startMonitors ( ) {
2021-09-21 15:22:35 +02:00
let list = await R . find ( "monitor" , " active = 1 " ) ;
2021-06-25 15:55:49 +02:00
for ( let monitor of list ) {
2022-04-07 16:53:32 +02:00
server . monitorList [ monitor . id ] = monitor ;
2021-08-19 12:41:31 +02:00
}
2021-08-19 12:33:52 +02:00
2021-08-19 12:41:31 +02:00
for ( let monitor of list ) {
2023-02-23 18:16:49 +02:00
await monitor . start ( io ) ;
2021-08-19 12:33:52 +02:00
// Give some delays, so all monitors won't make request at the same moment when just start the server.
await sleep ( getRandomInt ( 300 , 1000 ) ) ;
2021-06-25 15:55:49 +02:00
}
}
2022-04-20 20:56:40 +02:00
/ * *
* Shutdown the application
2021-11-10 07:24:31 +02:00
* Stops all monitors and closes the database connection .
* @ param { string } signal The signal that triggered this function to be called .
2022-04-20 20:56:40 +02:00
* @ returns { Promise < void > }
* /
2021-07-15 19:44:51 +02:00
async function shutdownFunction ( signal ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Shutdown requested" ) ;
log . info ( "server" , "Called signal: " + signal ) ;
2021-07-15 19:44:51 +02:00
2022-10-15 11:17:26 +02:00
await server . stop ( ) ;
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Stopping all monitors" ) ;
2022-04-07 16:53:32 +02:00
for ( let id in server . monitorList ) {
let monitor = server . monitorList [ id ] ;
2023-02-23 18:16:49 +02:00
await monitor . stop ( ) ;
2021-07-15 19:44:51 +02:00
}
2021-07-21 20:02:35 +02:00
await sleep ( 2000 ) ;
await Database . close ( ) ;
2022-04-05 13:41:29 +02:00
2023-02-05 12:01:54 +02:00
if ( EmbeddedMariaDB . hasInstance ( ) ) {
EmbeddedMariaDB . getInstance ( ) . stop ( ) ;
}
2022-04-05 13:41:29 +02:00
stopBackgroundJobs ( ) ;
await cloudflaredStop ( ) ;
2022-12-08 13:13:47 +02:00
Settings . stopCacheCleaner ( ) ;
2021-07-15 19:44:51 +02:00
}
2023-08-11 09:46:41 +02:00
/ * *
* Final function called before application exits
* @ returns { void }
* /
2021-07-15 19:44:51 +02:00
function finalFunction ( ) {
2022-04-13 17:33:37 +02:00
log . info ( "server" , "Graceful shutdown successful!" ) ;
2021-07-15 19:44:51 +02:00
}
2022-04-19 09:38:59 +02:00
gracefulShutdown ( server . httpServer , {
2021-07-27 19:47:13 +02:00
signals : "SIGINT SIGTERM" ,
2021-07-15 19:44:51 +02:00
timeout : 30000 , // timeout: 30 secs
development : false , // not in dev mode
forceExit : true , // triggers process.exit() at the end of shutdown process
onShutdown : shutdownFunction , // shutdown function (async) - e.g. for cleanup DB, ...
2021-07-27 19:47:13 +02:00
finally : finalFunction , // finally function (sync) - e.g. for logging
2021-07-15 19:44:51 +02:00
} ) ;
2021-08-17 09:32:34 +02:00
// Catch unexpected errors here
2023-10-12 20:50:10 +02:00
let unexpectedErrorHandler = ( error , promise ) => {
2021-08-17 09:32:34 +02:00
console . trace ( error ) ;
2022-05-06 08:41:34 +02:00
UptimeKumaServer . errorLog ( error , false ) ;
2021-08-17 09:32:34 +02:00
console . error ( "If you keep encountering errors, please report to https://github.com/louislam/uptime-kuma/issues" ) ;
2023-10-12 20:50:10 +02:00
} ;
process . addListener ( "unhandledRejection" , unexpectedErrorHandler ) ;
process . addListener ( "uncaughtException" , unexpectedErrorHandler ) ;