mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-08-07 09:14:58 +00:00
WIP
This commit is contained in:
+1
-22
@@ -115,27 +115,6 @@ async function sendProxyList(socket) {
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit API key list to client
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function sendAPIKeyList(socket) {
|
||||
const timeLogger = new TimeLogger();
|
||||
|
||||
let result = [];
|
||||
const list = await R.find("api_key");
|
||||
|
||||
for (let bean of list) {
|
||||
result.push(bean.toPublicJSON());
|
||||
}
|
||||
|
||||
io.to(socket.userID).emit("apiKeyList", result);
|
||||
timeLogger.print("Sent API Key List");
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits the version information to the client.
|
||||
* @param {Socket} socket Socket.io socket instance
|
||||
@@ -240,7 +219,7 @@ module.exports = {
|
||||
sendImportantHeartbeatList,
|
||||
sendHeartbeatList,
|
||||
sendProxyList,
|
||||
sendAPIKeyList,
|
||||
|
||||
sendInfo,
|
||||
sendDockerHostList,
|
||||
sendRemoteBrowserList,
|
||||
|
||||
+2
-3
@@ -4,7 +4,7 @@
|
||||
* DO NOT require("./server") in other modules, it likely creates circular dependency!
|
||||
*/
|
||||
import { getRandomInt, isDev, log, sleep } from "../src/util";
|
||||
import { auth, doubleCheckPassword, getDisableAuthSession, getSession } from "./better-auth";
|
||||
import { auth, createHeaders, doubleCheckPassword, getDisableAuthSession, getSession } from "./better-auth";
|
||||
import { createBetterAuthRouter, needSetup } from "./routers/better-auth-router";
|
||||
import { betterAuthSocketHandler } from "./socket-handlers/better-auth-socket-handler";
|
||||
import { loadEnvFile } from "node:process";
|
||||
@@ -153,7 +153,6 @@ const {
|
||||
sendInfo,
|
||||
sendProxyList,
|
||||
sendDockerHostList,
|
||||
sendAPIKeyList,
|
||||
sendRemoteBrowserList,
|
||||
sendMonitorTypeList,
|
||||
} = require("./client");
|
||||
@@ -377,6 +376,7 @@ app.use(function (req, res, next) {
|
||||
if (session) {
|
||||
socket.userID = session.user.id;
|
||||
socket.session = session;
|
||||
socket.headers = createHeaders(socket.request.headers.cookie);
|
||||
socket.emit("session", session.user.username);
|
||||
log.debug("auth", `Session active:`, session.session.ipAddress, session.user.username);
|
||||
}
|
||||
@@ -1445,7 +1445,6 @@ async function afterLogin(socket, user) {
|
||||
sendNotificationList(socket),
|
||||
sendProxyList(socket),
|
||||
sendDockerHostList(socket),
|
||||
sendAPIKeyList(socket),
|
||||
sendRemoteBrowserList(socket),
|
||||
sendMonitorTypeList(socket),
|
||||
]);
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
const { checkLogin } = require("../util-server");
|
||||
const { log } = require("../../src/util");
|
||||
const { R } = require("redbean-node");
|
||||
const { nanoid } = require("nanoid");
|
||||
const passwordHash = require("../password-hash");
|
||||
const dayjs = require("dayjs");
|
||||
const apicache = require("../modules/apicache");
|
||||
const APIKey = require("../model/api_key");
|
||||
const { Settings } = require("../settings");
|
||||
const { sendAPIKeyList } = require("../client");
|
||||
const { auth, createHeaders, checkLogin } = require("../better-auth");
|
||||
|
||||
/**
|
||||
* Handlers for API keys
|
||||
@@ -14,34 +10,38 @@ const { sendAPIKeyList } = require("../client");
|
||||
* @returns {void}
|
||||
*/
|
||||
module.exports.apiKeySocketHandler = (socket) => {
|
||||
// Add a new api key
|
||||
// Add a new better-auth api key
|
||||
socket.on("addAPIKey", async (key, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
let clearKey = nanoid(40);
|
||||
let hashedKey = await passwordHash.generate(clearKey);
|
||||
key["key"] = hashedKey;
|
||||
let bean = await APIKey.save(key, socket.userID);
|
||||
let expiresIn;
|
||||
|
||||
if (key.expires) {
|
||||
expiresIn = dayjs(key.expires).diff(dayjs(), "second");
|
||||
if (expiresIn <= 0) {
|
||||
expiresIn = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await auth().api.createApiKey({
|
||||
body: {
|
||||
name: key.name,
|
||||
expiresIn,
|
||||
userId: socket.userID,
|
||||
},
|
||||
headers: socket.headers,
|
||||
});
|
||||
|
||||
log.debug("apikeys", "Added API Key");
|
||||
log.debug("apikeys", key);
|
||||
|
||||
// Append key ID and prefix to start of key separated by _, used to get
|
||||
// correct hash when validating key.
|
||||
let formattedKey = "uk" + bean.id + "_" + clearKey;
|
||||
await sendAPIKeyList(socket);
|
||||
|
||||
// Enable API auth if the user creates a key, otherwise only basic
|
||||
// auth will be used for API.
|
||||
await Settings.set("apiKeysEnabled", true);
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "successAdded",
|
||||
msgi18n: true,
|
||||
key: formattedKey,
|
||||
keyID: bean.id,
|
||||
key: result.key,
|
||||
keyID: result.id,
|
||||
});
|
||||
} catch (e) {
|
||||
callback({
|
||||
@@ -54,9 +54,19 @@ module.exports.apiKeySocketHandler = (socket) => {
|
||||
socket.on("getAPIKeyList", async (callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
await sendAPIKeyList(socket);
|
||||
|
||||
const baKeys = await auth().api.listApiKeys({
|
||||
query: {},
|
||||
headers: socket.headers,
|
||||
});
|
||||
|
||||
const legacyBeans = await R.find("api_key");
|
||||
const legacyAPIKeyList = legacyBeans.map((bean) => bean.toPublicJSON());
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
apiKeyList: baKeys.apiKeys,
|
||||
legacyAPIKeyList: legacyAPIKeyList,
|
||||
});
|
||||
} catch (e) {
|
||||
log.error("apikeys", e);
|
||||
@@ -71,19 +81,18 @@ module.exports.apiKeySocketHandler = (socket) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("apikeys", `Deleted API Key: ${keyID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("DELETE FROM api_key WHERE id = ? AND user_id = ? ", [keyID, socket.userID]);
|
||||
|
||||
apicache.clear();
|
||||
await auth().api.deleteApiKey({
|
||||
body: {
|
||||
keyId: keyID,
|
||||
},
|
||||
headers: createHeaders(socket.request.headers.cookie),
|
||||
});
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
await sendAPIKeyList(socket);
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
@@ -92,48 +101,25 @@ module.exports.apiKeySocketHandler = (socket) => {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("disableAPIKey", async (keyID, callback) => {
|
||||
socket.on("deleteLegacyAPIKey", async (keyID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("apikeys", `Disabled Key: ${keyID} User ID: ${socket.userID}`);
|
||||
log.debug("apikeys", `Deleted Legacy API Key: ${keyID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE api_key SET active = 0 WHERE id = ? ", [keyID]);
|
||||
await R.exec("DELETE FROM api_key WHERE id = ? AND user_id = ? ", [keyID, socket.userID]);
|
||||
|
||||
apicache.clear();
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "successDisabled",
|
||||
msgi18n: true,
|
||||
});
|
||||
|
||||
await sendAPIKeyList(socket);
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
msg: e.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("enableAPIKey", async (keyID, callback) => {
|
||||
try {
|
||||
checkLogin(socket);
|
||||
|
||||
log.debug("apikeys", `Enabled Key: ${keyID} User ID: ${socket.userID}`);
|
||||
|
||||
await R.exec("UPDATE api_key SET active = 1 WHERE id = ? ", [keyID]);
|
||||
|
||||
apicache.clear();
|
||||
const legacyBeans = await R.find("api_key");
|
||||
const legacyKeys = legacyBeans.map((bean) => bean.toPublicJSON());
|
||||
|
||||
callback({
|
||||
ok: true,
|
||||
msg: "successEnabled",
|
||||
msg: "successDeleted",
|
||||
msgi18n: true,
|
||||
legacyKeys: legacyKeys,
|
||||
});
|
||||
|
||||
await sendAPIKeyList(socket);
|
||||
} catch (e) {
|
||||
callback({
|
||||
ok: false,
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
import { twoFactorClient, usernameClient } from "better-auth/client/plugins";
|
||||
import { apiKeyClient } from "@better-auth/api-key/client";
|
||||
import { reconnectSocket } from "./mixins/socket";
|
||||
|
||||
export const baseURL =
|
||||
@@ -10,7 +9,7 @@ export const baseURL =
|
||||
|
||||
export const authClient = createAuthClient({
|
||||
baseURL,
|
||||
plugins: [usernameClient(), twoFactorClient(), apiKeyClient()],
|
||||
plugins: [usernameClient(), twoFactorClient()],
|
||||
});
|
||||
|
||||
authClient.signIn;
|
||||
|
||||
@@ -149,6 +149,7 @@ export default {
|
||||
this.clearKey = res.key;
|
||||
this.keymodal.show();
|
||||
this.clearForm();
|
||||
this.$emit("keyAdded");
|
||||
} else {
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
|
||||
@@ -11,15 +11,47 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span
|
||||
v-if="Object.keys(keyList).length === 0"
|
||||
class="d-flex align-items-center justify-content-center my-3"
|
||||
>
|
||||
<div v-if="apiKeyList.length === 0 && legacyKeyList.length === 0">
|
||||
<span class="d-flex align-items-center justify-content-center my-3">
|
||||
{{ $t("No API Keys") }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div v-for="(item, index) in keyList" :key="index" class="item" :class="item.status">
|
||||
<div>
|
||||
<div v-for="(item, index) in apiKeyList" :key="index" class="item" :class="apiKeyStatus(item)">
|
||||
<div class="left-part">
|
||||
<div class="circle"></div>
|
||||
<div class="info">
|
||||
<div class="title">{{ item.name }}</div>
|
||||
<div class="status">
|
||||
{{ item.enabled ? $t("apiKey-active") : $t("apiKey-inactive") }}
|
||||
</div>
|
||||
<div class="date">{{ $t("createdAt", { date: item.createdAt }) }}</div>
|
||||
<div class="date">
|
||||
{{ $t("Expires") }}:
|
||||
{{ item.expiresAt || $t("Never") }}
|
||||
</div>
|
||||
<div v-if="item.start" class="start">
|
||||
{{ $t("startsWith", { start: item.start }) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-danger" @click="deleteDialog(item.id)">
|
||||
<font-awesome-icon icon="trash" />
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="legacyKeyList.length > 0">
|
||||
<h4>{{ $t("Legacy API Keys") }}</h4>
|
||||
|
||||
<div v-for="(item, index) in legacyKeyList" :key="index" class="item" :class="item.status">
|
||||
<div class="left-part">
|
||||
<div class="circle"></div>
|
||||
<div class="info">
|
||||
@@ -37,17 +69,7 @@
|
||||
|
||||
<div class="buttons">
|
||||
<div class="btn-group" role="group">
|
||||
<button v-if="item.active" class="btn btn-normal" @click="disableDialog(item.id)">
|
||||
<font-awesome-icon icon="pause" />
|
||||
{{ $t("Disable") }}
|
||||
</button>
|
||||
|
||||
<button v-if="!item.active" class="btn btn-primary" @click="enableKey(item.id)">
|
||||
<font-awesome-icon icon="play" />
|
||||
{{ $t("Enable") }}
|
||||
</button>
|
||||
|
||||
<button class="btn btn-danger" @click="deleteDialog(item.id)">
|
||||
<button class="btn btn-danger" @click="legacyDeleteDialog(item.id)">
|
||||
<font-awesome-icon icon="trash" />
|
||||
{{ $t("Delete") }}
|
||||
</button>
|
||||
@@ -63,15 +85,21 @@
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="disableKey">
|
||||
{{ $t("disableAPIKeyMsg") }}
|
||||
</Confirm>
|
||||
|
||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteKey">
|
||||
{{ $t("deleteAPIKeyMsg") }}
|
||||
</Confirm>
|
||||
|
||||
<APIKeyDialog ref="apiKeyDialog" />
|
||||
<Confirm
|
||||
ref="confirmLegacyDelete"
|
||||
btn-style="btn-danger"
|
||||
:yes-text="$t('Yes')"
|
||||
:no-text="$t('No')"
|
||||
@yes="legacyDeleteKey"
|
||||
>
|
||||
{{ $t("deleteAPIKeyMsg") }}
|
||||
</Confirm>
|
||||
|
||||
<APIKeyDialog ref="apiKeyDialog" @keyAdded="loadAPIKeys" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -86,23 +114,45 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
apiKeyList: [],
|
||||
legacyKeyList: [],
|
||||
selectedKeyID: null,
|
||||
selectedLegacyKeyID: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
keyList() {
|
||||
let result = Object.values(this.$root.apiKeyList);
|
||||
return result;
|
||||
},
|
||||
settings() {
|
||||
return this.$parent.$parent.$parent.settings;
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.loadAPIKeys();
|
||||
},
|
||||
|
||||
methods: {
|
||||
loadAPIKeys() {
|
||||
this.$root.getAPIKeyList((res) => {
|
||||
if (res.ok) {
|
||||
this.apiKeyList = res.apiKeyList;
|
||||
this.legacyKeyList = res.legacyAPIKeyList;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
apiKeyStatus(item) {
|
||||
if (!item.enabled) {
|
||||
return "inactive";
|
||||
}
|
||||
if (item.expiresAt && new Date(item.expiresAt) < new Date()) {
|
||||
return "expired";
|
||||
}
|
||||
return "active";
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog to confirm deletion
|
||||
* @param {number} keyID ID of monitor that is being deleted
|
||||
* Show dialog to confirm deletion of Better Auth key
|
||||
* @param {string} keyID ID of key to delete
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteDialog(keyID) {
|
||||
@@ -111,42 +161,39 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a key
|
||||
* Delete a Better Auth key
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteKey() {
|
||||
this.$root.deleteAPIKey(this.selectedKeyID, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
if (res.ok) {
|
||||
this.loadAPIKeys();
|
||||
this.$root.toastRes(res);
|
||||
} else {
|
||||
this.$root.toastError(res.msg);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Show dialog to confirm pause
|
||||
* @param {number} keyID ID of key to pause
|
||||
* Show dialog to confirm deletion of legacy key
|
||||
* @param {number} keyID ID of key to delete
|
||||
* @returns {void}
|
||||
*/
|
||||
disableDialog(keyID) {
|
||||
this.selectedKeyID = keyID;
|
||||
this.$refs.confirmPause.show();
|
||||
legacyDeleteDialog(keyID) {
|
||||
this.selectedLegacyKeyID = keyID;
|
||||
this.$refs.confirmLegacyDelete.show();
|
||||
},
|
||||
|
||||
/**
|
||||
* Pause API key
|
||||
* Delete a legacy key
|
||||
* @returns {void}
|
||||
*/
|
||||
disableKey() {
|
||||
this.$root.getSocket().emit("disableAPIKey", this.selectedKeyID, (res) => {
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Resume API key
|
||||
* @param {number} id Key to resume
|
||||
* @returns {void}
|
||||
*/
|
||||
enableKey(id) {
|
||||
this.$root.getSocket().emit("enableAPIKey", id, (res) => {
|
||||
legacyDeleteKey() {
|
||||
this.$root.deleteLegacyAPIKey(this.selectedLegacyKeyID, (res) => {
|
||||
if (res.ok) {
|
||||
this.legacyKeyList = res.legacyKeys;
|
||||
}
|
||||
this.$root.toastRes(res);
|
||||
});
|
||||
},
|
||||
@@ -165,6 +212,18 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin-top: 24px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: $dark-font-color;
|
||||
|
||||
.dark & {
|
||||
color: $dark-font-color2;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
@@ -257,6 +316,17 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.start {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
color: $dark-font-color;
|
||||
|
||||
.dark & {
|
||||
color: $dark-font-color2;
|
||||
}
|
||||
}
|
||||
|
||||
.dark {
|
||||
.item {
|
||||
&:hover {
|
||||
|
||||
+15
-6
@@ -39,7 +39,7 @@ export default {
|
||||
monitorList: {},
|
||||
monitorTypeList: {},
|
||||
maintenanceList: {},
|
||||
apiKeyList: {},
|
||||
|
||||
heartbeatList: {},
|
||||
avgPingList: {},
|
||||
uptimeList: {},
|
||||
@@ -164,10 +164,6 @@ export default {
|
||||
this.maintenanceList = data;
|
||||
});
|
||||
|
||||
socket.on("apiKeyList", (data) => {
|
||||
this.apiKeyList = data;
|
||||
});
|
||||
|
||||
socket.on("notificationList", (data) => {
|
||||
this.notificationList = data;
|
||||
});
|
||||
@@ -423,7 +419,7 @@ export default {
|
||||
},
|
||||
|
||||
/**
|
||||
* Send list of API keys
|
||||
* Send list of API keys (callback receives BA keys and legacy keys)
|
||||
* @param {socketCB} callback Callback for socket response
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -434,6 +430,19 @@ export default {
|
||||
socket.emit("getAPIKeyList", callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete specified legacy API key
|
||||
* @param {int} keyID ID of key to delete
|
||||
* @param {socketCB} callback Callback for socket response
|
||||
* @returns {void}
|
||||
*/
|
||||
deleteLegacyAPIKey(keyID, callback) {
|
||||
if (!callback) {
|
||||
callback = () => {};
|
||||
}
|
||||
socket.emit("deleteLegacyAPIKey", keyID, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a monitor
|
||||
* @param {object} monitor Object representing monitor to add
|
||||
|
||||
Reference in New Issue
Block a user