feat: CRUD repoList with API token auth

This commit is contained in:
Ravinou 2024-07-22 13:25:19 +02:00
commit aa57a19ff1
No known key found for this signature in database
GPG key ID: EEEE670C40F6A4D7
8 changed files with 181 additions and 43 deletions

View file

@ -78,7 +78,6 @@ export default function RepoManage(props) {
headers: {
'Content-type': 'application/json',
},
body: JSON.stringify({ toDelete: true }),
})
.then((response) => {
if (response.ok) {

View file

@ -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);
}

View 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;
});
}

View 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');
}
}

View file

@ -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) {

View file

@ -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)');

View file

@ -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));

View file

@ -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;
}
}