Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR for Issue#332 [BE: API key & code snippet] #347

Closed
wants to merge 70 commits into from
Closed
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
711a43f
added `apiKey` and `serverUrl` cols to `teams` rel and gen migration
d02ev Nov 15, 2024
7a18427
added backend API and business logic for `serverUrl` and `apiKey`
d02ev Nov 15, 2024
3fcbb84
fixed controller name typo
d02ev Nov 15, 2024
84c34e9
removed `apiKey` hashing before storing
d02ev Nov 15, 2024
a29edc0
added FE connection to BE
d02ev Nov 15, 2024
94e2e21
coderabbit refactors
d02ev Nov 15, 2024
83020d5
added encryption to `apiKey`
d02ev Nov 16, 2024
92a7566
coderabbit refactors
d02ev Nov 16, 2024
bff615b
feat: Adding a utility function to make settings rbac
aryanp-86 Nov 11, 2024
29f675f
Refactored the utility function
aryanp-86 Nov 13, 2024
f5307c7
Refactored teamtab and settings to ensure consistency
aryanp-86 Nov 14, 2024
5bd78ad
Added reducer functionality for tab change
aryanp-86 Nov 15, 2024
178ed3e
Removed tabValue from authProvider
aryanp-86 Nov 15, 2024
ca02815
refactored teams page
erenfn Nov 15, 2024
8fe21ae
coderabbit
erenfn Nov 15, 2024
f9e05e7
team tab refactor
erenfn Nov 15, 2024
74b5bca
Draft1 - Refactor data validations for controllers in backend
eulerbutcooler Oct 16, 2024
2c279da
auth validation - backend + frontend
eulerbutcooler Oct 29, 2024
b375c61
Auth validations - frontend and backend
eulerbutcooler Nov 12, 2024
a7f3dea
undid some comments
eulerbutcooler Nov 12, 2024
9456dd5
Fixed onBlur validation checks
eulerbutcooler Nov 13, 2024
f73bb8d
Enhanced login page with sign-up link, improved validation for name a…
eulerbutcooler Nov 14, 2024
a1e30b1
Added suggested edits
eulerbutcooler Nov 14, 2024
97cc6a3
undid a comment
eulerbutcooler Nov 14, 2024
f1802ea
more edits
eulerbutcooler Nov 14, 2024
234d093
updated the auth tests and made minor changes to name regex
eulerbutcooler Nov 16, 2024
2b210b2
admin register routes
erenfn Nov 16, 2024
1fc68e6
Update docker-compose.yml
erenfn Nov 16, 2024
1dd7964
Merge branch 'develop' into d02ev/issue#332
d02ev Nov 29, 2024
afde799
update error toast and added config fetch
d02ev Nov 29, 2024
19553c1
BE refactors
d02ev Dec 3, 2024
3ebaf7f
Merge branch 'develop' into d02ev/issue#332
d02ev Dec 7, 2024
eeb97c3
resolved merge conflict
d02ev Dec 7, 2024
55c9865
comment resolution
d02ev Dec 7, 2024
6247039
Merge branch 'develop' into d02ev/issue#332
d02ev Dec 14, 2024
df26b0c
updated setConfig validations
d02ev Dec 14, 2024
3298934
feat: add column action url to models
DeboraSerra Dec 10, 2024
af26153
feat: add migrations
DeboraSerra Dec 10, 2024
23081e3
feat: add validation on controller to actionUrl column
DeboraSerra Dec 10, 2024
bf2b3b0
feat: add action url to banner in front
DeboraSerra Dec 13, 2024
a14f07a
feat: add action url to popup front
DeboraSerra Dec 13, 2024
1e87754
chore: add proptypes to banner and popup content
DeboraSerra Dec 14, 2024
75db228
chore: improve url validation on model
DeboraSerra Dec 14, 2024
09e76c9
chore: improve url validation on front
DeboraSerra Dec 14, 2024
7ce60ba
chore: improve url validation on controller
DeboraSerra Dec 14, 2024
5d11055
fix: fix error return on popup
DeboraSerra Dec 14, 2024
47defaa
chore: remove console log from popup front
DeboraSerra Dec 14, 2024
9a9c734
test: fix test on front
DeboraSerra Dec 14, 2024
ceec1e9
test: fix test on back
DeboraSerra Dec 14, 2024
2ce3cad
fix: fix error return on banner
DeboraSerra Dec 14, 2024
5b3aef0
feat: add column action url to models
DeboraSerra Dec 10, 2024
23d97d0
feat: add validation on controller to actionUrl column
DeboraSerra Dec 10, 2024
db9c7d1
feat: add action url to banner in front
DeboraSerra Dec 13, 2024
3cd8584
feat: add action url to popup front
DeboraSerra Dec 13, 2024
777cce7
chore: add proptypes to banner and popup content
DeboraSerra Dec 14, 2024
32aa954
chore: improve url validation on model
DeboraSerra Dec 14, 2024
f9e5748
chore: improve url validation on front
DeboraSerra Dec 14, 2024
9ef45c4
chore: improve url validation on controller
DeboraSerra Dec 14, 2024
b47b6f5
fix: fix error return on popup
DeboraSerra Dec 14, 2024
2018c5d
chore: remove console log from popup front
DeboraSerra Dec 14, 2024
43fbb22
test: fix test on back
DeboraSerra Dec 14, 2024
161bf4c
fix: fix error return on banner
DeboraSerra Dec 14, 2024
2bf8716
guide init
erenfn Dec 10, 2024
6039fe4
resolve dockerfile
erenfn Dec 10, 2024
2bdf41f
find incomplete guides done
erenfn Dec 11, 2024
b368d09
Update guide.routes.js
erenfn Dec 11, 2024
dd16c3c
added getPopupByApiAndClientId
erenfn Dec 11, 2024
b57420c
Merge branch 'develop' into d02ev/issue#332
d02ev Dec 16, 2024
2be6c41
resolved merge issues
d02ev Dec 16, 2024
3e945a6
updated server url and api key allowed values
d02ev Dec 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions backend/.env
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ EMAIL_ENABLE=false
# JWT Secret Key
JWT_SECRET="NKrbO2lpCsOpVAlqAPsjZ0tZXzIoKru7gAmYZ7XlHn0=qqwqeq"

# API KEY ENCRYPTION SECRET KEY
API_KEY_ENCRYPTION_KEY="\wd?jqf7o5*v;[{/s$9_u3`h+i4r2(^ymt'#|kn-@~!x1ea6pb"

TEST_DB_USERNAME=user123
TEST_DB_PASSWORD=password123
TEST_DB_NAME=onboarding_db_test
Expand Down
3 changes: 3 additions & 0 deletions backend/.env.production
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ PROD_DB_PORT=5432

# JWT Secret Key
JWT_SECRET=your_prod_jwt_secret_key_here

# API KEY ENCRYPTION SECRET KEY
API_KEY_ENCRYPTION_KEY=your_prod_api_key_encryption_key_here
3 changes: 3 additions & 0 deletions backend/.env.test
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ POSTGRES_DB=onboarding_db_test

# JWT Secret Key
JWT_SECRET=your_test_jwt_secret_key_here

# API KEY ENCRYPTION SECRET KEY
API_KEY_ENCRYPTION_KEY=your_prod_api_key_encryption_key_here
1 change: 1 addition & 0 deletions backend/config/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = {
popups: [userRole.ADMIN],
hints: [userRole.ADMIN],
banners: [userRole.ADMIN],
serverUrlAndApiKey: [userRole.ADMIN],
links: [userRole.ADMIN],
tours: [userRole.ADMIN],
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
'use strict';

module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.addColumn('teams', 'apiKey', {
type: Sequelize.TEXT,
allowNull: true,
defaultValue: null,
})
await queryInterface.addColumn('teams', 'serverUrl', {
type: Sequelize.TEXT,
allowNull: true,
defaultValue: null,
})
},

async down (queryInterface, Sequelize) {
await queryInterface.removeColumn('teams', 'apiKey')
await queryInterface.removeColumn('teams', 'serverUrl')
}
};
84 changes: 63 additions & 21 deletions backend/src/controllers/team.controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
const settings = require("../../config/settings");
const TeamService = require("../service/team.service");
const { internalServerError } = require("../utils/errors.helper");
const { MAX_ORG_NAME_LENGTH, ORG_NAME_REGEX } = require('../utils/constants.helper');
const { MAX_ORG_NAME_LENGTH, ORG_NAME_REGEX, VALID_URL_REGEX } = require('../utils/constants.helper');
const db = require("../models");
const { decryptApiKey, encryptApiKey } = require("../utils/team.helper");
const { validationResult } = require("express-validator");

const Team = db.Team;
const teamService = new TeamService();
Expand Down Expand Up @@ -61,21 +63,39 @@ const getTeamCount = async (req, res) => {
}
};

const getServerUrlAndApiKey = async (req, res) => {
try {
let { serverUrl, apiKey } = await teamService.fetchServerUrlAndApiKey();
apiKey = decryptApiKey(apiKey);
const data = {
serverUrl,
apiKey
}
return res.status(200).json(data);
} catch (err) {
const { statusCode, payload } = internalServerError(
"GET_SERVER_URL_AND_API_KEY_ERROR",
err.message
);
res.status(statusCode).json(payload);
}
};
d02ev marked this conversation as resolved.
Show resolved Hide resolved

const getTeamDetails = async (req, res) => {
try {
const data = await teamService.getTeam();
if (!data || !data.team || !data.users) {
throw new Error("Team data not found");
}
const result = {
name: data.team.name,
users: data.users.map((user)=> ({
id: user.id,
name: user.name,
email: user.email,
role: settings.user.roleName[user.role],
createdAt: new Intl.DateTimeFormat('en-US').format(user.createdAt)
})),
name: data.team.name,
users: data.users.map((user) => ({
id: user.id,
name: user.name,
email: user.email,
role: settings.user.roleName[user.role],
createdAt: new Intl.DateTimeFormat('en-US').format(user.createdAt)
})),
}
return res.status(200).json(result);
} catch (err) {
Expand All @@ -88,19 +108,41 @@ const getTeamDetails = async (req, res) => {
};

const updateTeamDetails = async (req, res) => {
const { name } = req.body;
try {
await teamService.updateTeam(name);
return res.status(200).json({ message: "Team Details Updated Successfully" });
} catch (err) {
const { statusCode, payload } = internalServerError(
"UPDATE_TEAM_ERROR",
err.message,
);
res.status(statusCode).json(payload);
}
const { name } = req.body;
try {
await teamService.updateTeam(name);
return res.status(200).json({ message: "Team Details Updated Successfully" });
} catch (err) {
const { statusCode, payload } = internalServerError(
"UPDATE_TEAM_ERROR",
err.message,
);
res.status(statusCode).json(payload);
}
};

const setConfig = async (req, res) => {
const validationErrors = validationResult(req);

if (!validationErrors.isEmpty()) {
return res.status(400).json({ errors: validationErrors.array() });
}

try {
const { serverUrl, apiKey } = req.body;
const encryptedApiKey = encryptApiKey(apiKey);

await teamService.addServerUrlAndApiKey(serverUrl, encryptedApiKey);
return res.status(200).json({ message: "Server URL and API Key Set Successfully" });
} catch (err) {
const { statusCode, payload } = internalServerError(
"SET_CONFIG_ERROR",
err.message
)
res.status(statusCode).json(payload);
}
}

const removeMember = async (req, res) => {
const userId = req.user.id;
const { memberId } = req.params;
Expand Down Expand Up @@ -130,4 +172,4 @@ const changeRole = async (req, res) => {
}
}

module.exports = { setOrganisation, getTeamDetails, updateTeamDetails, removeMember, changeRole, getTeamCount, teamService };
module.exports = { setOrganisation, getTeamDetails, getServerUrlAndApiKey, updateTeamDetails, removeMember, changeRole, getTeamCount, setConfig, teamService };
10 changes: 10 additions & 0 deletions backend/src/models/Team.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.STRING(50),
allowNull: false,
},
apiKey: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: null,
},
serverUrl: {
type: DataTypes.TEXT,
allowNull: true,
defaultValue: null,
},
createdAt: {
type: DataTypes.DATE,
allowNull: false,
Expand Down
5 changes: 5 additions & 0 deletions backend/src/routes/team.routes.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
const express = require("express");
const {
setOrganisation,
setConfig,
getTeamDetails,
getServerUrlAndApiKey,
getTeamCount,
updateTeamDetails,
removeMember,
Expand All @@ -14,17 +16,20 @@ const {
const authenticateJWT = require("../middleware/auth.middleware");
const accessGuard = require("../middleware/accessGuard.middleware");
const settings = require("../../config/settings");
const { validateSetConfig } = require("../utils/team.helper");

const router = express.Router();
const teamPermissions = settings.team.permissions;

router.get("/details", authenticateJWT, getTeamDetails);
router.get('/get-config', authenticateJWT, accessGuard(teamPermissions.serverUrlAndApiKey), getServerUrlAndApiKey);
router.get("/count", getTeamCount);

router.post("/set-organisation", authenticateJWT, accessGuard(teamPermissions.setOrg), setOrganisation);
router.post("/invite", authenticateJWT, accessGuard(teamPermissions.invite), sendTeamInvite);
router.put("/update", authenticateJWT, accessGuard(teamPermissions.update), updateTeamDetails);
router.put("/change-role", authenticateJWT, accessGuard(teamPermissions.changeRole), changeRole);
router.put('/set-config', authenticateJWT, accessGuard(teamPermissions.serverUrlAndApiKey), validateSetConfig, setConfig);

router.delete("/remove/:memberId", authenticateJWT, accessGuard(teamPermissions.removeUser), removeMember);
router.get('/get-all-invites', authenticateJWT, accessGuard(teamPermissions.removeUser), getAllInvites);
Expand Down
32 changes: 32 additions & 0 deletions backend/src/service/team.service.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { where } = require("sequelize");
const settings = require("../../config/settings");
const db = require("../models");
const Team = db.Team;
Expand Down Expand Up @@ -128,6 +129,37 @@ class TeamService {
throw new Error(`Failed to update user role ~ ${err.message}`);
}
}

async addServerUrlAndApiKey(serverUrl, apiKey) {
const transaction = await sequelize.transaction();
try {
await Team.update({
serverUrl,
apiKey
}, {
where: {}
}, {
transaction
});
d02ev marked this conversation as resolved.
Show resolved Hide resolved
await transaction.commit();
} catch (err) {
await transaction.rollback();
throw new Error("Failed to add server url and api key");
}
d02ev marked this conversation as resolved.
Show resolved Hide resolved
}
d02ev marked this conversation as resolved.
Show resolved Hide resolved

async fetchServerUrlAndApiKey() {
try {
const team = await Team.findOne({
limit: 1,

});
const { serverUrl, apiKey } = team;
return { serverUrl, apiKey };
} catch (err) {
throw new Error("Failed to fetch server url and api key");
}
}
d02ev marked this conversation as resolved.
Show resolved Hide resolved
}

module.exports = TeamService;
2 changes: 2 additions & 0 deletions backend/src/utils/constants.helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ module.exports = Object.freeze({
},
MAX_ORG_NAME_LENGTH: 100,
ORG_NAME_REGEX: /^[a-zA-Z0-9\s\-_&.]+$/,
URL_PROTOCOL_REGEX: /^(https?:\/\/)/,
URL_DOMAIN_REGEX: /^https?:\/\/([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/,
});

63 changes: 63 additions & 0 deletions backend/src/utils/team.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const jwt = require('jsonwebtoken');
const { URL_PROTOCOL_REGEX, URL_DOMAIN_REGEX } = require('./constants.helper');
const { check } = require('express-validator');

require('dotenv').config();

const encryptApiKey = (apiKey) => {
return jwt.sign(apiKey, process.env.API_KEY_ENCRYPTION_KEY);
};
d02ev marked this conversation as resolved.
Show resolved Hide resolved

const decryptApiKey = (apiKey) => {
try {
return jwt.verify(apiKey, process.env.API_KEY_ENCRYPTION_KEY);
} catch (err) {
return null;
}
}

const validateServerUrl = url => {
const errors = [];

if (!URL_PROTOCOL_REGEX.test(url)) {
errors.push("Invalid or missing protocol (must be 'http://' or 'https://').")
}

const domainMatch = url.match(URL_DOMAIN_REGEX);
if (!domainMatch) {
errors.push("Invalid domain name (must include a valid top-level domain like '.com').");
} else {
const domain = domainMatch[1];
if (!/^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(domain)) {
errors.push(`Malformed domain: '${domain}'.`);
}
}

if (errors.length === 0) {
return { valid: true, errors: null }
}

return { valid: false, errors }
};

const validateSetConfig = [
check('apiKey')
.exists().withMessage('API Key is required')
.isString().withMessage('API Key must be a string')
.trim()
.notEmpty().withMessage('API Key cannot be empty'),

check('serverUrl')
.optional()
.isString().withMessage('Server URL must be a string')
.trim()
.custom(value => {
const result = validateServerUrl(value);
if (result.valid) {
return true;
}
throw new Error(result.errors);
})
];

module.exports = { encryptApiKey, decryptApiKey, validateSetConfig };
32 changes: 16 additions & 16 deletions frontend/dist/index.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bluewave Onboarding</title>
<script type="module" crossorigin src="/assets/index-BZ7MeYpO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CpCaKGtQ.css">
</head>

<body>
<div id="root"></div>

</body>

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bluewave Onboarding</title>
<script type="module" crossorigin src="/assets/index-BZ7MeYpO.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-CpCaKGtQ.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
Loading
Loading