diff --git a/admin/src/components/APIKeyTable.tsx b/admin/src/components/APIKeyTable.tsx index ab97718..2173ce3 100644 --- a/admin/src/components/APIKeyTable.tsx +++ b/admin/src/components/APIKeyTable.tsx @@ -11,17 +11,11 @@ import { } from "@chakra-ui/react"; import { Tooltip } from "@/components/ui/tooltip"; import MyAlert from "./myChakra/MyAlert"; -import { - Trash2, - RefreshCcwDot, - CirclePlus, -} from "lucide-react"; +import { Trash2, RefreshCcwDot, CirclePlus } from "lucide-react"; import Cookies from "js-cookie"; import { useState, useEffect } from "react"; -import { - deleteItem, -} from "@/utils/userActions"; -import AddItemForm from "./AddItemForm"; +import { deleteAPKey } from "@/utils/userActions"; +import AddAPIKey from "./AddAPIKey"; import { formatDateTime } from "@/utils/userFuncs"; type Items = { @@ -57,7 +51,7 @@ const APIKeyTable: React.FC = () => { const fetchData = async () => { setIsLoading(true); try { - const response = await fetch("http://localhost:8002/api/keys", { + const response = await fetch("http://localhost:8002/api/apiKeys", { method: "GET", headers: { Authorization: `Bearer ${Cookies.get("token")}`, @@ -118,7 +112,7 @@ const APIKeyTable: React.FC = () => { }} > - Neuen Gegenstand hinzufügen + Neuen API Key hinzufügen @@ -141,7 +135,7 @@ const APIKeyTable: React.FC = () => { )} {addAPIForm && ( - { setAddAPIForm(false); setReload(!reload); @@ -175,14 +169,12 @@ const APIKeyTable: React.FC = () => { {apiKey.id} {apiKey.apiKey} - - {apiKey.user} - + {apiKey.user} {formatDateTime(apiKey.entry_created_at)} + + + + + ); +}; + +export default AddAPIKey; diff --git a/admin/src/utils/userActions.ts b/admin/src/utils/userActions.ts index beac000..8cf8e1e 100644 --- a/admin/src/utils/userActions.ts +++ b/admin/src/utils/userActions.ts @@ -201,3 +201,44 @@ export const changeSafeState = async (itemId: number) => { return { success: false }; } }; + +export const createAPIentry = async (apiKey: string, user: string) => { + try { + const response = await fetch(`http://localhost:8002/api/createAPIentry`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${Cookies.get("token")}`, + }, + body: JSON.stringify({ apiKey, user }), + }); + if (!response.ok) { + throw new Error("Failed to create API entry"); + } + return { success: true }; + } catch (error) { + console.error("Error creating API entry:", error); + return { success: false }; + } +}; + +export const deleteAPKey = async (apiKeyId: number) => { + try { + const response = await fetch( + `http://localhost:8002/api/deleteAPKey/${apiKeyId}`, + { + method: "DELETE", + headers: { + Authorization: `Bearer ${Cookies.get("token")}`, + }, + } + ); + if (!response.ok) { + throw new Error("Failed to delete API key"); + } + return { success: true }; + } catch (error) { + console.error("Error deleting API key:", error); + return { success: false }; + } +}; diff --git a/backend/routes/api.js b/backend/routes/api.js index 2b08eed..4b35f53 100644 --- a/backend/routes/api.js +++ b/backend/routes/api.js @@ -22,6 +22,9 @@ import { changeUserPasswordFRONTEND, changeInSafeStateV2, updateItemByID, + getAllApiKeys, + createAPIentry, + deleteAPKey, } from "../services/database.js"; import { authenticate, generateToken } from "../services/tokenService.js"; const router = express.Router(); @@ -330,4 +333,77 @@ router.put("/changeSafeState/:itemId", authenticate, async (req, res) => { return res.status(500).json({ message: "Failed to update item safe state" }); }); +router.get("/apiKeys", authenticate, async (req, res) => { + const result = await getAllApiKeys(); + if (result.success) { + return res.status(200).json(result.data); + } + return res.status(500).json({ message: "Failed to fetch API keys" }); +}); + +router.get("/apiKeys/apiV2/:id", authenticate, async (req, res) => { + if (req.params.id !== process.env.ADMIN_ID) { + return res.status(403).json({ message: "Access denied" }); + } + const result = await getAPIkey(); + if (result.success) { + return res.status(200).json(result.data); + } + return res.status(500).json({ message: "Failed to fetch API keys" }); +}); + +router.delete("/deleteAPKey/:id", authenticate, async (req, res) => { + const apiKeyId = req.params.id; + const result = await deleteAPKey(apiKeyId); + if (result.success) { + return res.status(200).json({ message: "API key deleted successfully" }); + } + return res.status(500).json({ message: "Failed to delete API key" }); +}); + +router.post("/createAPIentry", authenticate, async (req, res) => { + const apiKey = req.body.apiKey; + const user = req.body.user; + if (!apiKey || !user) { + return res.status(400).json({ message: "API key and user are required" }); + } + + // Ensure apiKey is a number + const apiKeyNum = Number(apiKey); + if (!Number.isFinite(apiKeyNum)) { + return res.status(400).json({ message: "API key must be a number" }); + } + + const result = await createAPIentry(apiKeyNum, user); + if (result.success) { + return res.status(201).json({ message: "API key created successfully" }); + } + if (result.code === "DUPLICATE") { + return res.status(409).json({ message: "API key already exists" }); + } + return res.status(500).json({ message: "Failed to create API key" }); +}); + +router.get("/apiKeys/validate/:key", async (req, res) => { + try { + const rawKey = req.params.key; + const result = await getAllApiKeys(); + if (!result.success || !Array.isArray(result.data)) { + return res.status(500).json({ valid: false }); + } + + const isValid = result.data.some((entry) => { + const val = String( + entry?.key ?? entry?.apiKey ?? entry?.api_key ?? entry + ); + return val === String(rawKey); + }); + + return res.status(200).json({ valid: isValid }); + } catch (err) { + console.error("validate api key error:", err); + return res.status(500).json({ valid: false }); + } +}); + export default router; diff --git a/backend/routes/apiV2.js b/backend/routes/apiV2.js index b43c02e..bf78ea7 100644 --- a/backend/routes/apiV2.js +++ b/backend/routes/apiV2.js @@ -7,93 +7,110 @@ import { setTakeDateV2, getLoanByCodeV2, getAllLoansV2, + getAPIkey, } from "../services/database.js"; dotenv.config(); const router = express.Router(); +async function validateAPIKey(apiKey) { + try { + const result = await getAPIkey(); + if (!result.success || !Array.isArray(result.data)) return false; + + return result.data.some((row) => { + const val = String(row?.apiKey ?? row?.key ?? row?.api_key); + return val === String(apiKey); + }); + } catch (err) { + console.error("validateAPIKey error:", err); + return false; + } +} + +async function ensureValidApiKey(req, res) { + const isValid = await validateAPIKey(req.params.key); + if (!isValid) { + res.status(403).json({ message: "Access denied" }); + return false; + } + return true; +} + // Route for API to get ALL items from the database router.get("/items/:key", async (req, res) => { - if (req.params.key === process.env.ADMIN_ID) { - const result = await getItemsFromDatabaseV2(); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to fetch items" }); - } + if (!(await ensureValidApiKey(req, res))) return; + + const result = await getItemsFromDatabaseV2(); + if (result.success) { + res.status(200).json({ data: result.data }); } else { - res.status(403).json({ message: "Access denied" }); + res.status(500).json({ message: "Failed to fetch items" }); } }); // Route for API to control the position of an item router.post("/controlInSafe/:key/:itemId/:state", async (req, res) => { - if (req.params.key === process.env.ADMIN_ID) { - const itemId = req.params.itemId; - const state = req.params.state; - if (state === "1" || state === "0") { - const result = await changeInSafeStateV2(itemId, state); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to update item state" }); - } + if (!(await ensureValidApiKey(req, res))) return; + + const itemId = req.params.itemId; + const state = req.params.state; + + if (state === "1" || state === "0") { + const result = await changeInSafeStateV2(itemId, state); + if (result.success) { + res.status(200).json({ data: result.data }); } else { - res.status(400).json({ message: "Invalid state value" }); + res.status(500).json({ message: "Failed to update item state" }); } } else { - res.status(403).json({ message: "Access denied" }); + res.status(400).json({ message: "Invalid state value" }); } }); // Route for API to get a loan by its code router.get("/getLoanByCode/:key/:loan_code", async (req, res) => { - if (req.params.key === process.env.ADMIN_ID) { - const loan_code = req.params.loan_code; + if (!(await ensureValidApiKey(req, res))) return; - const result = await getLoanByCodeV2(loan_code); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(404).json({ message: "Loan not found" }); - } + const loan_code = req.params.loan_code; + const result = await getLoanByCodeV2(loan_code); + if (result.success) { + res.status(200).json({ data: result.data }); + } else { + res.status(404).json({ message: "Loan not found" }); } }); // Route for API to set the return date by the loan code router.post("/setReturnDate/:key/:loan_code", async (req, res) => { - if (req.params.key === process.env.ADMIN_ID) { - const loanCode = req.params.loan_code; + if (!(await ensureValidApiKey(req, res))) return; - const result = await setReturnDateV2(loanCode); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to set return date" }); - } + const loanCode = req.params.loan_code; + const result = await setReturnDateV2(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); } else { - res.status(403).json({ message: "Access denied" }); + res.status(500).json({ message: "Failed to set return date" }); } }); // Route for API to set the take away date by the loan code router.post("/setTakeDate/:key/:loan_code", async (req, res) => { - if (req.params.key === process.env.ADMIN_ID) { - const loanCode = req.params.loan_code; + if (!(await ensureValidApiKey(req, res))) return; - const result = await setTakeDateV2(loanCode); - if (result.success) { - res.status(200).json({ data: result.data }); - } else { - res.status(500).json({ message: "Failed to set take date" }); - } + const loanCode = req.params.loan_code; + const result = await setTakeDateV2(loanCode); + if (result.success) { + res.status(200).json({ data: result.data }); } else { - res.status(403).json({ message: "Access denied" }); + res.status(500).json({ message: "Failed to set take date" }); } }); // Route for API to get ALL loans from the database without sensitive info -router.get("/allLoans", async (req, res) => { +router.get("/allLoans/:key", async (req, res) => { + if (!(await ensureValidApiKey(req, res))) return; + const result = await getAllLoansV2(); if (result.success) { return res.status(200).json(result.data); @@ -101,8 +118,10 @@ router.get("/allLoans", async (req, res) => { return res.status(500).json({ message: "Failed to fetch loans" }); }); -// Route for API to get ALL items form the database without key -router.get("/allItems", async (req, res) => { +// Route for API to get ALL items form the database +router.get("/allItems/:key", async (req, res) => { + if (!(await ensureValidApiKey(req, res))) return; + const result = await getItemsFromDatabaseV2(); if (result.success) { res.status(200).json(result.data); diff --git a/backend/services/database.js b/backend/services/database.js index 4e1492a..5ddcc9c 100644 --- a/backend/services/database.js +++ b/backend/services/database.js @@ -457,3 +457,36 @@ export const getAllLoansV2 = async () => { } return { success: false }; }; + +export const getAllApiKeys = async () => { + const [rows] = await pool.query("SELECT * FROM apiKeys"); + if (rows.length > 0) { + return { success: true, data: rows }; + } + return { success: false }; +}; + +export const createAPIentry = async (apiKey, user) => { + const [result] = await pool.query( + "INSERT INTO apiKeys (apiKey, user) VALUES (?, ?)", + [apiKey, user] + ); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; + +export const deleteAPKey = async (apiKeyId) => { + const [result] = await pool.query("DELETE FROM apiKeys WHERE id = ?", [ + apiKeyId, + ]); + if (result.affectedRows > 0) return { success: true }; + return { success: false }; +}; + +export const getAPIkey = async () => { + const [rows] = await pool.query("SELECT apiKey FROM apiKeys"); + if (rows.length > 0) { + return { success: true, data: rows }; + } + return { success: false }; +};