mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-11-30 18:24:03 +00:00
Add Basic Auth for /metrics
This commit is contained in:
parent
cc6f1d7487
commit
209fa83cff
6 changed files with 141 additions and 47 deletions
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -625,6 +625,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="
|
||||||
},
|
},
|
||||||
|
"basic-auth": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||||
|
"requires": {
|
||||||
|
"safe-buffer": "5.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"bcrypt": {
|
"bcrypt": {
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz",
|
||||||
|
@ -1294,6 +1302,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"express-basic-auth": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-iJ0h1Gk6fZRrFmO7tP9nIbxwNgCUJASfNj5fb0Hy15lGtbqqsxpt7609+wq+0XlByZjXmC/rslWQtnuSTVRIcg==",
|
||||||
|
"requires": {
|
||||||
|
"basic-auth": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"extend": {
|
"extend": {
|
||||||
"version": "3.0.2",
|
"version": "3.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
"command-exists": "^1.2.9",
|
"command-exists": "^1.2.9",
|
||||||
"dayjs": "^1.10.6",
|
"dayjs": "^1.10.6",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
|
"express-basic-auth": "^1.2.0",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"http-graceful-shutdown": "^3.1.2",
|
"http-graceful-shutdown": "^3.1.2",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
|
40
server/auth.js
Normal file
40
server/auth.js
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
const basicAuth = require('express-basic-auth')
|
||||||
|
const passwordHash = require('./password-hash');
|
||||||
|
const {R} = require("redbean-node");
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username : string
|
||||||
|
* @param password : string
|
||||||
|
* @returns {Promise<Bean|null>}
|
||||||
|
*/
|
||||||
|
exports.login = async function (username, password) {
|
||||||
|
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
||||||
|
username
|
||||||
|
])
|
||||||
|
|
||||||
|
if (user && passwordHash.verify(password, user.password)) {
|
||||||
|
// Upgrade the hash to bcrypt
|
||||||
|
if (passwordHash.needRehash(user.password)) {
|
||||||
|
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
||||||
|
passwordHash.generate(password),
|
||||||
|
user.id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function myAuthorizer(username, password, callback) {
|
||||||
|
exports.login(username, password).then((user) => {
|
||||||
|
callback(null, user != null)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.basicAuth = basicAuth({
|
||||||
|
authorizer: myAuthorizer,
|
||||||
|
authorizeAsync: true,
|
||||||
|
challenge: true
|
||||||
|
});
|
|
@ -1,4 +1,3 @@
|
||||||
const Prometheus = require('prom-client');
|
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const utc = require('dayjs/plugin/utc')
|
const utc = require('dayjs/plugin/utc')
|
||||||
|
@ -6,6 +5,7 @@ var timezone = require('dayjs/plugin/timezone')
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
dayjs.extend(timezone)
|
dayjs.extend(timezone)
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
|
const {Prometheus} = require("../prometheus");
|
||||||
const {debug, UP, DOWN, PENDING} = require("../util");
|
const {debug, UP, DOWN, PENDING} = require("../util");
|
||||||
const {tcping, ping, checkCertificate} = require("../util-server");
|
const {tcping, ping, checkCertificate} = require("../util-server");
|
||||||
const {R} = require("redbean-node");
|
const {R} = require("redbean-node");
|
||||||
|
@ -18,26 +18,6 @@ const customAgent = new https.Agent({
|
||||||
maxCachedSessions: 0
|
maxCachedSessions: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonLabels = [
|
|
||||||
'monitor_name',
|
|
||||||
'monitor_type',
|
|
||||||
'monitor_url',
|
|
||||||
'monitor_hostname',
|
|
||||||
'monitor_port',
|
|
||||||
]
|
|
||||||
|
|
||||||
const monitor_response_time = new Prometheus.Gauge({
|
|
||||||
name: 'monitor_response_time',
|
|
||||||
help: 'Monitor Response Time (ms)',
|
|
||||||
labelNames: commonLabels
|
|
||||||
});
|
|
||||||
|
|
||||||
const monitor_status = new Prometheus.Gauge({
|
|
||||||
name: 'monitor_status',
|
|
||||||
help: 'Monitor Status (1 = UP, 0= DOWN)',
|
|
||||||
labelNames: commonLabels
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* status:
|
* status:
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
|
@ -76,13 +56,7 @@ class Monitor extends BeanModel {
|
||||||
let previousBeat = null;
|
let previousBeat = null;
|
||||||
let retries = 0;
|
let retries = 0;
|
||||||
|
|
||||||
const monitorLabelValues = {
|
let prometheus = new Prometheus(this);
|
||||||
monitor_name: this.name,
|
|
||||||
monitor_type: this.type,
|
|
||||||
monitor_url: this.url,
|
|
||||||
monitor_hostname: this.hostname,
|
|
||||||
monitor_port: this.port
|
|
||||||
}
|
|
||||||
|
|
||||||
const beat = async () => {
|
const beat = async () => {
|
||||||
|
|
||||||
|
@ -219,7 +193,6 @@ class Monitor extends BeanModel {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_status.set(monitorLabelValues, bean.status)
|
|
||||||
|
|
||||||
if (bean.status === UP) {
|
if (bean.status === UP) {
|
||||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${this.interval} seconds | Type: ${this.type}`)
|
||||||
|
@ -229,7 +202,7 @@ class Monitor extends BeanModel {
|
||||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Type: ${this.type}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
monitor_response_time.set(monitorLabelValues, bean.ping)
|
prometheus.update(bean)
|
||||||
|
|
||||||
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
io.to(this.user_id).emit("heartbeat", bean.toJSON());
|
||||||
|
|
||||||
|
|
59
server/prometheus.js
Normal file
59
server/prometheus.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
const PrometheusClient = require('prom-client');
|
||||||
|
|
||||||
|
const commonLabels = [
|
||||||
|
'monitor_name',
|
||||||
|
'monitor_type',
|
||||||
|
'monitor_url',
|
||||||
|
'monitor_hostname',
|
||||||
|
'monitor_port',
|
||||||
|
]
|
||||||
|
|
||||||
|
const monitor_response_time = new PrometheusClient.Gauge({
|
||||||
|
name: 'monitor_response_time',
|
||||||
|
help: 'Monitor Response Time (ms)',
|
||||||
|
labelNames: commonLabels
|
||||||
|
});
|
||||||
|
|
||||||
|
const monitor_status = new PrometheusClient.Gauge({
|
||||||
|
name: 'monitor_status',
|
||||||
|
help: 'Monitor Status (1 = UP, 0= DOWN)',
|
||||||
|
labelNames: commonLabels
|
||||||
|
});
|
||||||
|
|
||||||
|
class Prometheus {
|
||||||
|
monitorLabelValues = {}
|
||||||
|
|
||||||
|
constructor(monitor) {
|
||||||
|
this.monitorLabelValues = {
|
||||||
|
monitor_name: monitor.name,
|
||||||
|
monitor_type: monitor.type,
|
||||||
|
monitor_url: monitor.url,
|
||||||
|
monitor_hostname: monitor.hostname,
|
||||||
|
monitor_port: monitor.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
update(heartbeat) {
|
||||||
|
try {
|
||||||
|
monitor_status.set(this.monitorLabelValues, heartbeat.status)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (typeof heartbeat.ping === 'number') {
|
||||||
|
monitor_response_time.set(this.monitorLabelValues, heartbeat.ping)
|
||||||
|
} else {
|
||||||
|
// Is it good?
|
||||||
|
monitor_response_time.set(this.monitorLabelValues, -1)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Prometheus
|
||||||
|
}
|
|
@ -5,7 +5,6 @@ const http = require('http');
|
||||||
const { Server } = require("socket.io");
|
const { Server } = require("socket.io");
|
||||||
const dayjs = require("dayjs");
|
const dayjs = require("dayjs");
|
||||||
const {R} = require("redbean-node");
|
const {R} = require("redbean-node");
|
||||||
const passwordHash = require('./password-hash');
|
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
@ -15,7 +14,9 @@ const gracefulShutdown = require('http-graceful-shutdown');
|
||||||
const Database = require("./database");
|
const Database = require("./database");
|
||||||
const {sleep} = require("./util");
|
const {sleep} = require("./util");
|
||||||
const args = require('args-parser')(process.argv);
|
const args = require('args-parser')(process.argv);
|
||||||
const apiMetrics = require('prometheus-api-metrics');
|
const prometheusAPIMetrics = require('prometheus-api-metrics');
|
||||||
|
const { basicAuth } = require("./auth");
|
||||||
|
const {login} = require("./auth");
|
||||||
const version = require('../package.json').version;
|
const version = require('../package.json').version;
|
||||||
const hostname = args.host || "0.0.0.0"
|
const hostname = args.host || "0.0.0.0"
|
||||||
const port = args.port || 3001
|
const port = args.port || 3001
|
||||||
|
@ -27,6 +28,9 @@ const app = express();
|
||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
const io = new Server(server);
|
const io = new Server(server);
|
||||||
app.use(express.json())
|
app.use(express.json())
|
||||||
|
const basicAuthRouter = express.Router();
|
||||||
|
basicAuthRouter.use(basicAuth)
|
||||||
|
app.use(basicAuthRouter)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Total WebSocket client connected to server currently, no actual use
|
* Total WebSocket client connected to server currently, no actual use
|
||||||
|
@ -56,15 +60,27 @@ let needSetup = false;
|
||||||
await initDatabase();
|
await initDatabase();
|
||||||
|
|
||||||
console.log("Adding route")
|
console.log("Adding route")
|
||||||
|
|
||||||
|
// Normal Router here
|
||||||
|
|
||||||
app.use('/', express.static("dist"));
|
app.use('/', express.static("dist"));
|
||||||
app.use(apiMetrics())
|
|
||||||
|
|
||||||
|
// Basic Auth Router here
|
||||||
|
|
||||||
|
// For testing
|
||||||
|
basicAuthRouter.get('/test-auth', (req, res) => {
|
||||||
|
res.end("OK")
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prometheus API metrics /metrics
|
||||||
|
// With Basic Auth using the first user's username/password
|
||||||
|
basicAuthRouter.use(prometheusAPIMetrics())
|
||||||
|
|
||||||
|
// Universal Route Handler, must be at the end
|
||||||
app.get('*', function(request, response, next) {
|
app.get('*', function(request, response, next) {
|
||||||
response.sendFile(process.cwd() + '/dist/index.html');
|
response.sendFile(process.cwd() + '/dist/index.html');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
console.log("Adding socket handler")
|
console.log("Adding socket handler")
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
|
|
||||||
|
@ -120,20 +136,9 @@ let needSetup = false;
|
||||||
socket.on("login", async (data, callback) => {
|
socket.on("login", async (data, callback) => {
|
||||||
console.log("Login")
|
console.log("Login")
|
||||||
|
|
||||||
let user = await R.findOne("user", " username = ? AND active = 1 ", [
|
let user = await login(data.username, data.password)
|
||||||
data.username
|
|
||||||
])
|
|
||||||
|
|
||||||
if (user && passwordHash.verify(data.password, user.password)) {
|
|
||||||
|
|
||||||
// Upgrade the hash to bcrypt
|
|
||||||
if (passwordHash.needRehash(user.password)) {
|
|
||||||
await R.exec("UPDATE `user` SET password = ? WHERE id = ? ", [
|
|
||||||
passwordHash.generate(data.password),
|
|
||||||
user.id
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (user) {
|
||||||
await afterLogin(socket, user)
|
await afterLogin(socket, user)
|
||||||
|
|
||||||
callback({
|
callback({
|
||||||
|
|
Loading…
Reference in a new issue