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

Promote from BLAIS5-4254 to main #386

Merged
merged 42 commits into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
e04ceb9
build: use improved blaise-login components
kristian4res Oct 28, 2024
af4e2d0
wip: add logging
kristian4res Oct 28, 2024
44327f0
wip: add logging
kristian4res Oct 28, 2024
1824256
build: use latest blaise-login-react; use cloud logging (same as DQS …
kristian4res Oct 29, 2024
5f7d124
feat: add and configure gcp cloud logging
kristian4res Oct 29, 2024
3e16abf
fix: trim trailing whitespaces for name and pwd
kristian4res Oct 29, 2024
600ed11
wip: use pino http logger and cloud logging (test with BlaiseAPI)
kristian4res Oct 29, 2024
8cf5e3e
wip: log current user or requester
kristian4res Oct 29, 2024
7c9c1ec
wip: log current user or requester
kristian4res Oct 29, 2024
3759235
wip: log current user or requester
kristian4res Oct 29, 2024
e8723a6
feat: log current user initiating the action
kristian4res Oct 29, 2024
0909143
test: involve auth process in tests by using mock JWT values
kristian4res Oct 29, 2024
5ff600f
feat/refactor: add logging to create, delete and update/edit actions
kristian4res Oct 30, 2024
c7a4e00
refactor: restructure server folder
kristian4res Oct 30, 2024
e937804
test: include log asserts in unit tests
kristian4res Oct 30, 2024
df3c82e
chore: remove unused import
kristian4res Oct 31, 2024
a4ab55a
chore: remove unused import
kristian4res Oct 31, 2024
b376eb8
wip: fix unit test?
kristian4res Oct 31, 2024
7f12c98
wip: fix unit test?
kristian4res Oct 31, 2024
6ecd43c
wip: fix unit test?
kristian4res Oct 31, 2024
525b9cc
wip: fix deploy?
kristian4res Oct 31, 2024
0206e7c
wip: checking logs for roles and permissions
kristian4res Oct 31, 2024
98180f5
chore: remove audit logs endpoint
kristian4res Nov 4, 2024
a9d64b0
test: relocate blaise api tests
kristian4res Nov 4, 2024
f2791cc
refactor: trim whitespace in text user inputs; allow custom IDs for O…
kristian4res Nov 4, 2024
54e8685
test: trim whitespace in text user inputs; allow custom IDs for ONSPa…
kristian4res Nov 4, 2024
b72e261
refactor: assign correct serverparks based on role, before sending to…
kristian4res Nov 4, 2024
1b308a9
refactor: assign correct serverparks based on role, before sending to…
kristian4res Nov 4, 2024
b4b50ef
chore: remove console log
kristian4res Nov 4, 2024
f55a3ac
ref/test: improve logs and add error test case
kristian4res Nov 4, 2024
6a15aaa
ref: improve logs
kristian4res Nov 4, 2024
65e70da
ref: improve logs
kristian4res Nov 4, 2024
96d5950
ref: improve logs
kristian4res Nov 4, 2024
f6d4648
ref: improve logs
kristian4res Nov 4, 2024
fd20b67
ref: improve logs
kristian4res Nov 4, 2024
5a57a47
ref: improve logs
kristian4res Nov 4, 2024
0168d13
fix: sanitize message to prevent log injection
kristian4res Nov 5, 2024
736d33e
chore: remove unused method
kristian4res Nov 5, 2024
5580c25
chore: remove comments
kristian4res Nov 5, 2024
5252d26
build: update design system release reference
kristian4res Nov 5, 2024
fb143be
feat/test: sanitize log messages whilst preserving readability
kristian4res Nov 5, 2024
41721d5
ref: improve code
kristian4res Nov 5, 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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,23 @@
"formik": "2.4.2"
},
"dependencies": {
"@google-cloud/logging": "^9.6.0",
"@google-cloud/profiler": "^4.1.1",
"@testing-library/dom": "^8.3.0",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^14.2.1",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.7.4",
"blaise-api-node-client": "git+https://github.com/ONSdigital/blaise-api-node-client",
"blaise-design-system-react-components": "git+https://github.com/ONSdigital/blaise-design-system-react-components#0.14.0",
"blaise-login-react": "git+https://github.com/ONSdigital/blaise-login-react#1.1.0",
"blaise-design-system-react-components": "git+https://github.com/ONSdigital/blaise-design-system-react-components#0.14.1",
"blaise-login-react": "git+https://github.com/ONSdigital/blaise-login-react#1.1.1",
"dotenv": "^10.0.0",
"ejs": "^3.1.10",
"express": "^4.19.2",
"formik": "2.4.2",
"history": "^4.9.0",
"jest-cucumber": "^3.0.0",
"jsonwebtoken": "^9.0.2",
"lodash": "^4.17.21",
"multer": "^1.4.2",
"number-to-words": "^1.2.4",
Expand Down
125 changes: 0 additions & 125 deletions server/BlaiseAPI/index.ts

This file was deleted.

8 changes: 4 additions & 4 deletions server/index.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import GetNodeServer from "./server";
import pino from "pino";
import { loadConfigFromEnv } from "./Config";
import BlaiseApiClient from "blaise-api-node-client";
import { Auth } from "blaise-login-react/blaise-login-react-server";
import dotenv from "dotenv";
import createLogger from "./logger/pinoLogger";

const port: string = process.env.PORT || "5002";
const logger = pino();

if (process.env.NODE_ENV !== "production") {
dotenv.config();
}
const config = loadConfigFromEnv();
const blaiseApiClient = new BlaiseApiClient(config.BlaiseApiUrl);
const auth = new Auth(config);
const server = GetNodeServer(config, blaiseApiClient, auth);
const pinoLogger = createLogger();
const server = GetNodeServer(config, blaiseApiClient, auth, pinoLogger);
server.listen(port);

logger.info("App is listening on port " + port);
pinoLogger.logger.info("BAM nodejs server is listening on port " + port);
6 changes: 6 additions & 0 deletions server/interfaces/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface AuditLog {
id: string;
timestamp: string;
message: string;
severity: string;
}
1 change: 0 additions & 1 deletion server/interfaces/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AuthConfig } from "blaise-login-react/blaise-login-react-server";
import { string } from "prop-types";

export interface CustomConfig extends AuthConfig {
BlaiseApiUrl: string
Expand Down
59 changes: 59 additions & 0 deletions server/logger/cloudLogging.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { Logging } from "@google-cloud/logging";
import { IncomingMessage } from "http";
import { AuditLog } from "../interfaces/logger";

export function formatLogMessage(text: string): string {
const message = text.replace(/[^\x20-\x7E\r\n]+/g, "");
const logFormat = "AUDIT_LOG: message";
return logFormat.replace("message", message);
}

export default class AuditLogger {
projectId: string;
logger: Logging;
logName: string;

constructor(projectId: string) {
this.projectId = projectId;
this.logger = new Logging({ projectId: this.projectId });
this.logName = `projects/${this.projectId}/logs/stdout`;
}

info(logger: IncomingMessage["log"], message: string): void {
const log = formatLogMessage(message);
logger.info(log);
Fixed Show fixed Hide fixed
Dismissed Show dismissed Hide dismissed
}

error(logger: IncomingMessage["log"], message: string): void {
const log = formatLogMessage(message);
logger.error(log);
Dismissed Show dismissed Hide dismissed
}

async getLogs(): Promise<AuditLog[]> {
const auditLogs: AuditLog[] = [];
const log = this.logger.log(this.logName);
const [entries] = await log.getEntries({ filter: "jsonPayload.message=~\"^AUDIT_LOG: \"", maxResults: 50 });
for (const entry of entries) {
let id = "";
let timestamp = "";
let severity = "INFO";
if (entry.metadata.insertId != null) {
id = entry.metadata.insertId;
}
if (entry.metadata.timestamp != null) {
timestamp = entry.metadata.timestamp.toString();
}
if (entry.metadata.severity != null) {
severity = entry.metadata.severity.toString();
}
auditLogs.push({
id: id,
timestamp: timestamp,
message: entry.data.message.replace(/^AUDIT_LOG: /, ""),
severity: severity
});
}
return auditLogs;
}
}
File renamed without changes.
148 changes: 148 additions & 0 deletions server/routes/blaiseApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import express, { Request, Response, Router } from "express";
import { CustomConfig } from "../interfaces/server";
import { Auth } from "blaise-login-react/blaise-login-react-server";
import BlaiseApiClient from "blaise-api-node-client";
import AuditLogger from "../logger/cloudLogging";

export default function blaiseApi(config: CustomConfig, auth: Auth, blaiseApiClient: BlaiseApiClient, auditLogger: AuditLogger): Router {
const router = express.Router();

router.get("/api/roles", auth.Middleware, async function (req: Request, res: Response) {
res.status(200).json(await blaiseApiClient.getUserRoles());
});

router.get("/api/users", auth.Middleware, async function (req: Request, res: Response) {
res.status(200).json(await blaiseApiClient.getUsers());
});

router.patch("/api/users/:user/rolesAndPermissions", auth.Middleware, async function (req: Request, res: Response) {
const currentUser = auth.GetUser(auth.GetToken(req));
const { role } = req.body;
const user = req.params.user;
let newServerParks = [""];
Fixed Show fixed Hide fixed
let newDefaultServerPark = "";

if (!req.params.user || !req.body.role) {
return res.status(400).json("No user or role provided");
}

const roleServerParksOverride = config.RoleToServerParksMap[role];
if (roleServerParksOverride != null) {
newServerParks = roleServerParksOverride;
newDefaultServerPark = roleServerParksOverride[0];
} else {
const defaultServerPark = config.RoleToServerParksMap["DEFAULT"];
newServerParks = defaultServerPark;
newDefaultServerPark = defaultServerPark[0];
}

try {
await blaiseApiClient.changeUserRole(user, role);
await blaiseApiClient.changeUserServerParks(user, newServerParks, newDefaultServerPark);
const successMessage = `${currentUser.name || "Unknown user"} has successfully updated user role and permissions to ${role} for ${user}`;
auditLogger.info(req.log, successMessage);
return res.status(200).json({
message: "Successfully updated user role and permissions to " + role + " for " + user
});
} catch (error) {
const errorMessage = `Error whilst trying to update user role and permissions to ${role} for ${req.params.user}, with error message: ${error}`;
auditLogger.info(req.log, `${currentUser.name || "Unknown user"} has failed to update user role and permissions to ${role} for ${user}`);
auditLogger.error(req.log, errorMessage);
return res.status(500).json({
message: "Failed to update user role and permissions to " + role + " for " + user
});
}
});

router.get("/api/users/:user", auth.Middleware, async function (req: Request, res: Response) {
if (!req.params.user) {
return res.status(400).json("No user provided");
}

try {
const user = await blaiseApiClient.getUser(req.params.user);
const successMessage = `Successfully fetched user details for ${req.params.user}`;
return res.status(200).json({
message: successMessage,
data: user
});
} catch (error) {
const errorMessage = `Error whilst trying to retrieve user ${req.params.user}: ${error}`;
return res.status(500).json({
message: errorMessage,
error: error
});
}
});

router.get("/api/change-password/:user", auth.Middleware, async function (req: Request, res: Response) {
const currentUser = auth.GetUser(auth.GetToken(req));
let { password } = req.headers;

if (Array.isArray(password)) {
password = password.join("");
}

if (!req.params.user || !password) {
return res.status(400).json("No user or password provided");
}

blaiseApiClient.changePassword(req.params.user, password).then(() => {
auditLogger.info(req.log, `${currentUser.name || "Unknown"} has successfully changed the password for ${req.params.user}`);
return res.status(204).json(null);
}).catch((error: unknown) => {
auditLogger.info(req.log, `${currentUser.name || "Unknown"} has failed to change the password for ${req.params.user}`);
auditLogger.error(req.log, `Error whilst trying to change password for ${req.params.user}: ${error}`);
return res.status(500).json(error);
});
});

router.delete("/api/users", auth.Middleware, async function (req: Request, res: Response) {
try {
const currentUser = auth.GetUser(auth.GetToken(req));
let { user } = req.headers;

if (Array.isArray(user)) {
user = user.join("");
}

if (!user) {
auditLogger.error(req.log, "No user provided for deletion");
return res.status(400).json();
}
auditLogger.info(req.log, `${currentUser.name || "Unknown"} has successfully deleted user called ${user}`);
return res.status(204).json(await blaiseApiClient.deleteUser(user));
} catch (error) {
auditLogger.error(req.log, `Error whilst trying to delete user, ${req.headers.user}, with error message: ${error}`);
return res.status(500).json(error);
}
});

router.post("/api/users", auth.Middleware, async function (req: Request, res: Response) {
try {
const currentUser = auth.GetUser(auth.GetToken(req));
const data = req.body;

if(!data.role) {
return res.status(400).json({ message: "No role provided for user creation" });
}

const roleServerParksOverride = config.RoleToServerParksMap[data.role];
if (roleServerParksOverride != null) {
data.serverParks = roleServerParksOverride;
data.defaultServerPark = roleServerParksOverride[0];
} else {
const defaultServerPark = config.RoleToServerParksMap["DEFAULT"];
data.serverParks = defaultServerPark;
data.defaultServerPark = defaultServerPark[0];
}
auditLogger.info(req.log, `${currentUser.name || "Unknown"} has successfully created user, ${data.name}, with an assigned role of ${data.role}`);
return res.status(200).json(await blaiseApiClient.createUser(data));
} catch (error) {
auditLogger.error(req.log, `Error whilst trying to create new user, ${req.body.name}, with error message: ${error}`);
return res.status(500).json(error);
}
});

return router;
}
Loading
Loading