mirror of
https://github.com/Ravinou/borgwarehouse
synced 2026-03-14 14:25:46 +01:00
feat: ✨ CRUD repoList with API token auth
This commit is contained in:
parent
83fe9a5355
commit
aa57a19ff1
8 changed files with 181 additions and 43 deletions
|
|
@ -78,7 +78,6 @@ export default function RepoManage(props) {
|
|||
headers: {
|
||||
'Content-type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ toDelete: true }),
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.ok) {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ export default function Integrations() {
|
|||
setError();
|
||||
//Loading button on submit to avoid multiple send.
|
||||
setIsLoading(true);
|
||||
console.log(data);
|
||||
//Generate a UUIDv4
|
||||
const token = uuidv4();
|
||||
setLastGeneratedToken({ name: data.tokenName, value: token });
|
||||
|
|
@ -105,7 +104,6 @@ export default function Integrations() {
|
|||
} catch (error) {
|
||||
reset();
|
||||
setIsLoading(false);
|
||||
console.log(error);
|
||||
toast.error("Can't generate your token. Contact your administrator.", toastOptions);
|
||||
setTimeout(() => setError(), 4000);
|
||||
}
|
||||
|
|
|
|||
22
helpers/functions/isSshPubKeyDuplicate.js
Normal file
22
helpers/functions/isSshPubKeyDuplicate.js
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* Checks if the given SSH public key is duplicated in the provided repository list by removing the comment part.
|
||||
*
|
||||
* @param {string} pubKey - The SSH public key to check.
|
||||
* @param {Array} repoList - The list of repositories with their SSH public keys.
|
||||
* @returns {boolean} - Returns true if the SSH public key is duplicated, otherwise false.
|
||||
* @throws {Error} - Throws an error if required parameters are missing or invalid.
|
||||
*/
|
||||
export default function isSshPubKeyDuplicate(pubKey, repoList) {
|
||||
if (!pubKey || !repoList || !Array.isArray(repoList)) {
|
||||
throw new Error('Missing or invalid parameters for duplicate SSH public key check.');
|
||||
}
|
||||
|
||||
// Compare the key part only by removing the comment
|
||||
const pubKeyWithoutComment = pubKey.split(' ').slice(0, 2).join(' ');
|
||||
|
||||
// Check if the normalized key is already in the repository list
|
||||
return repoList.some((repo) => {
|
||||
const repoSshKeyWithoutComment = repo.sshPublicKey.split(' ').slice(0, 2).join(' ');
|
||||
return repoSshKeyWithoutComment === pubKeyWithoutComment;
|
||||
});
|
||||
}
|
||||
25
helpers/functions/tokenController.js
Normal file
25
helpers/functions/tokenController.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export default async function tokenController(API_KEY) {
|
||||
const jsonDirectory = path.join(process.cwd(), 'config');
|
||||
try {
|
||||
const usersList = await fs.readFile(jsonDirectory + '/users.json', 'utf8');
|
||||
const users = JSON.parse(usersList);
|
||||
|
||||
const user = users.find(
|
||||
(user) => Array.isArray(user.tokens) && user.tokens.some((token) => token.token === API_KEY)
|
||||
);
|
||||
if (user) {
|
||||
const token = user.tokens.find((token) => token.token === API_KEY);
|
||||
|
||||
if (token && token.permissions && typeof token.permissions === 'object') {
|
||||
return token.permissions;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
throw new Error('Error with tokenController');
|
||||
}
|
||||
}
|
||||
|
|
@ -3,21 +3,43 @@ import path from 'path';
|
|||
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import repoHistory from '../../../helpers/functions/repoHistory';
|
||||
import tokenController from '../../../helpers/functions/tokenController';
|
||||
import isSshPubKeyDuplicate from '../../../helpers/functions/isSshPubKeyDuplicate';
|
||||
const util = require('node:util');
|
||||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'POST') {
|
||||
//Verify that the user is logged in.
|
||||
if (req.method === 'POST') {
|
||||
//AUTHENTICATION
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!session && !authorization) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
try {
|
||||
if (authorization) {
|
||||
const API_KEY = authorization.split(' ')[1];
|
||||
const permissions = await tokenController(API_KEY);
|
||||
if (!permissions) {
|
||||
res.status(403).json({ message: 'Invalid API key' });
|
||||
return;
|
||||
}
|
||||
if (!permissions.write) {
|
||||
res.status(401).json({ message: 'Insufficient permissions' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Internal Server Error' });
|
||||
return;
|
||||
}
|
||||
|
||||
//DATA CONTROL
|
||||
const { alias, sshPublicKey, size, comment, alert, lanCommand, appendOnlyMode } = req.body;
|
||||
//We check that we receive data for each variable. Only "comment" and "lanCommand" are optional in the form.
|
||||
//Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
!alias ||
|
||||
!sshPublicKey ||
|
||||
|
|
@ -25,22 +47,27 @@ export default async function handler(req, res) {
|
|||
typeof appendOnlyMode !== 'boolean' ||
|
||||
(!alert && alert !== 0)
|
||||
) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
res.status(422).json({ message: 'Unexpected data' });
|
||||
return;
|
||||
}
|
||||
|
||||
//ADD REPO
|
||||
try {
|
||||
//console.log('API call (PUT)');
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
let repoList = await fs.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
const sshKeyAlreadyUsed = isSshPubKeyDuplicate(sshPublicKey, repoList);
|
||||
if (sshKeyAlreadyUsed) {
|
||||
res.status(409).json({
|
||||
message:
|
||||
'The SSH key is already used in another repository. Please use another key or delete the key from the other repository.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the first biggest ID available to assign it, so the highest ID is already the last added.
|
||||
let newID = 0;
|
||||
for (let element in repoList) {
|
||||
|
|
|
|||
|
|
@ -3,36 +3,46 @@ import path from 'path';
|
|||
import { authOptions } from '../../../auth/[...nextauth]';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import repoHistory from '../../../../../helpers/functions/repoHistory';
|
||||
import tokenController from '../../../../../helpers/functions/tokenController';
|
||||
const util = require('node:util');
|
||||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'DELETE') {
|
||||
//Verify that the user is logged in.
|
||||
//AUTHENTICATION
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!session && !authorization) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (authorization) {
|
||||
const API_KEY = authorization.split(' ')[1];
|
||||
const permissions = await tokenController(API_KEY);
|
||||
if (!permissions) {
|
||||
res.status(403).json({ message: 'Invalid API key' });
|
||||
return;
|
||||
}
|
||||
if (!permissions.write) {
|
||||
res.status(401).json({ message: 'Insufficient permissions' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Internal Server Error' });
|
||||
return;
|
||||
}
|
||||
|
||||
//If deletion is disabled on the server, return error
|
||||
if (process.env.DISABLE_DELETE_REPO === 'true') {
|
||||
res.status(403).json({
|
||||
status: 403,
|
||||
message: 'Deletion is disabled on this server',
|
||||
});
|
||||
return;
|
||||
}
|
||||
//The data we expect to receive
|
||||
const { toDelete } = req.body;
|
||||
////We check that we receive toDelete and it must be a bool.
|
||||
if (typeof toDelete != 'boolean' || toDelete === false) {
|
||||
//If a variable is empty.
|
||||
res.status(422).json({
|
||||
message: 'Unexpected data',
|
||||
});
|
||||
//A return to make sure we don't go any further if data are incorrect.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (DELETE)');
|
||||
|
|
|
|||
|
|
@ -3,19 +3,41 @@ import path from 'path';
|
|||
import { authOptions } from '../../../auth/[...nextauth]';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import repoHistory from '../../../../../helpers/functions/repoHistory';
|
||||
import tokenController from '../../../../../helpers/functions/tokenController';
|
||||
import isSshPubKeyDuplicate from '../../../../../helpers/functions/isSshPubKeyDuplicate';
|
||||
const util = require('node:util');
|
||||
const exec = util.promisify(require('node:child_process').exec);
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'PUT') {
|
||||
//Verify that the user is logged in.
|
||||
//AUTHENTICATION
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
res.status(401).json({ message: 'You must be logged in.' });
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!session && !authorization) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
//The data we expect to receive
|
||||
try {
|
||||
if (authorization) {
|
||||
const API_KEY = authorization.split(' ')[1];
|
||||
const permissions = await tokenController(API_KEY);
|
||||
if (!permissions) {
|
||||
res.status(403).json({ message: 'Invalid API key' });
|
||||
return;
|
||||
}
|
||||
if (!permissions.write) {
|
||||
res.status(401).json({ message: 'Insufficient permissions' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Internal Server Error' });
|
||||
return;
|
||||
}
|
||||
|
||||
//DATA CONTROL
|
||||
const { alias, sshPublicKey, size, comment, alert, lanCommand, appendOnlyMode } = req.body;
|
||||
//Only "comment" and "lanCommand" are optional in the form.
|
||||
if (
|
||||
|
|
@ -31,6 +53,7 @@ export default async function handler(req, res) {
|
|||
return;
|
||||
}
|
||||
|
||||
//UPDATE REPO
|
||||
try {
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
|
|
@ -38,6 +61,18 @@ export default async function handler(req, res) {
|
|||
//Parse the repoList
|
||||
repoList = JSON.parse(repoList);
|
||||
|
||||
const sshKeyAlreadyUsed = isSshPubKeyDuplicate(
|
||||
sshPublicKey,
|
||||
repoList.filter((repo) => repo.id != parseInt(req.query.slug))
|
||||
);
|
||||
if (sshKeyAlreadyUsed) {
|
||||
res.status(409).json({
|
||||
message:
|
||||
'The SSH key is already used in another repository. Please use another key or delete the key from the other repository.',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
//Find the index of the repo in repoList
|
||||
//NOTE : req.query.slug return a string, so parseInt to use with indexOf.
|
||||
const repoIndex = repoList.map((repo) => repo.id).indexOf(parseInt(req.query.slug));
|
||||
|
|
|
|||
|
|
@ -2,19 +2,39 @@ import fs from 'fs';
|
|||
import path from 'path';
|
||||
import { authOptions } from '../../../pages/api/auth/[...nextauth]';
|
||||
import { getServerSession } from 'next-auth/next';
|
||||
import tokenController from '../../../helpers/functions/tokenController';
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method == 'GET') {
|
||||
//Verify that the user is logged in.
|
||||
if (req.method === 'GET') {
|
||||
// AUTHENTICATION
|
||||
const session = await getServerSession(req, res, authOptions);
|
||||
if (!session) {
|
||||
// res.status(401).json({ message: 'You must be logged in.' });
|
||||
const { authorization } = req.headers;
|
||||
|
||||
if (!session && !authorization) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
//console.log('API call (GET)');
|
||||
if (authorization) {
|
||||
const API_KEY = authorization.split(' ')[1];
|
||||
const permissions = await tokenController(API_KEY);
|
||||
if (!permissions) {
|
||||
res.status(403).json({ message: 'Invalid API key' });
|
||||
return;
|
||||
}
|
||||
if (!permissions.read) {
|
||||
res.status(401).json({ message: 'Insufficient permissions' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: 'Internal Server Error' });
|
||||
return;
|
||||
}
|
||||
|
||||
// GET REPO LIST
|
||||
try {
|
||||
//Find the absolute path of the json directory
|
||||
const jsonDirectory = path.join(process.cwd(), '/config');
|
||||
//Check if the repo.json file exists and initialize it if not.
|
||||
|
|
@ -23,14 +43,10 @@ export default async function handler(req, res) {
|
|||
}
|
||||
//Read the file repo.json
|
||||
let repoList = await fs.promises.readFile(jsonDirectory + '/repo.json', 'utf8');
|
||||
//Parse the JSON
|
||||
repoList = JSON.parse(repoList);
|
||||
//Send the response
|
||||
res.status(200).json({ repoList });
|
||||
} catch (error) {
|
||||
//Log for backend
|
||||
console.log(error);
|
||||
//Log for frontend
|
||||
if (error.code == 'ENOENT') {
|
||||
res.status(500).json({
|
||||
status: 500,
|
||||
|
|
@ -44,5 +60,11 @@ export default async function handler(req, res) {
|
|||
}
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
res.status(405).json({
|
||||
status: 405,
|
||||
message: 'Method Not Allowed ',
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue