Skip to content

Commit

Permalink
Merge pull request #658 from cisagov/implement-logging
Browse files Browse the repository at this point in the history
Resolve issue with api-keys and logger
  • Loading branch information
schmelz21 authored Oct 2, 2024
2 parents 0462afb + a0446be commit 1c5e30a
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 56 deletions.
33 changes: 16 additions & 17 deletions backend/src/api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ if (

const handlerToExpress =
(handler, message?: RecordMessage, action?: string) => async (req, res) => {
const logger = new Logger(req);
const { statusCode, body } = await handler(
{
pathParameters: req.params,
Expand All @@ -60,12 +59,12 @@ const handlerToExpress =
{}
);
// Add additional status codes that we may return for succesfull requests
if (statusCode === 200) {
if (message && action) {

if (message && action) {
const logger = new Logger(req);
if (statusCode === 200) {
logger.record(action, 'success', message, body);
}
} else {
if (message && action) {
} else {
logger.record(action, 'fail', message, body);
}
}
Expand Down Expand Up @@ -637,7 +636,7 @@ authenticatedRoute.post(
'/v2/organizations/:organizationId/users',
handlerToExpress(
organizations.addUserV2,
async (req, user) => {
async (req, token) => {
const orgId = req?.params?.organizationId;
const userId = req?.body?.userId;
const role = req?.body?.role;
Expand All @@ -646,15 +645,15 @@ authenticatedRoute.post(
const userRecord = await User.findOne({ where: { id: userId } });
return {
timestamp: new Date(),
userPerformedAssignment: user?.data?.id,
userPerformedAssignment: token?.id,
organization: orgRecord,
role: role,
user: userRecord
};
}
return {
timestamp: new Date(),
userId: user?.data?.id,
userId: token?.id,
updatePayload: req.body
};
},
Expand Down Expand Up @@ -689,8 +688,8 @@ authenticatedRoute.post(
'/users',
handlerToExpress(
users.invite,
async (req, user, responseBody) => {
const userId = user?.data?.id;
async (req, token, responseBody) => {
const userId = token?.id;
if (userId) {
const userRecord = await User.findOne({ where: { id: userId } });
return {
Expand All @@ -702,7 +701,7 @@ authenticatedRoute.post(
}
return {
timestamp: new Date(),
userId: user.data?.id,
userId: token?.id,
invitePayload: req.body,
createdUserRecord: responseBody
};
Expand All @@ -715,9 +714,9 @@ authenticatedRoute.delete(
'/users/:userId',
handlerToExpress(
users.del,
async (req, user, res) => {
async (req, token, res) => {
const userId = req?.params?.userId;
const userPerformedRemovalId = user?.data?.id;
const userPerformedRemovalId = token?.id;
if (userId && userPerformedRemovalId) {
const userPerformdRemovalRecord = await User.findOne({
where: { id: userPerformedRemovalId }
Expand All @@ -730,7 +729,7 @@ authenticatedRoute.delete(
}
return {
timestamp: new Date(),
userPerformedRemoval: user.data?.id,
userPerformedRemoval: token?.id,
userRemoved: req.params.userId
};
},
Expand Down Expand Up @@ -763,10 +762,10 @@ authenticatedRoute.put(
checkGlobalAdminOrRegionAdmin,
handlerToExpress(
users.registrationApproval,
async (req, user) => {
async (req, token) => {
return {
timestamp: new Date(),
userId: user?.data?.id,
userId: token?.id,
userToApprove: req.params.userId
};
},
Expand Down
86 changes: 47 additions & 39 deletions backend/src/tools/logger.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { Request } from 'express';
import { decode } from 'jsonwebtoken';
import { User } from '../models';
import { attempt, unescape } from 'lodash';
import * as jwt from 'jsonwebtoken';
import { ApiKey, User } from '../models';
import { Log } from '../models/log';
import { getRepository, Repository } from 'typeorm';
import { UserToken } from 'src/api/auth';
import { createHash } from 'crypto';

type AccessTokenPayload = {
id: string;
email: string;
iat: string;
exp: string;
};

type LoggerUserState = {
Expand All @@ -25,7 +23,7 @@ type RecordPayload = object & {
export type RecordMessage =
| ((
request: Request,
user: LoggerUserState,
token: AccessTokenPayload | undefined,
responseBody?: object
) => Promise<RecordPayload>)
| RecordPayload;
Expand All @@ -48,15 +46,15 @@ export class Logger {
responseBody?: object | string
) {
try {
if (!this.user.ready && this.user.attempts > 0) {
await this.fetchUser();
}

if (!this.logRep) {
const logRepository = getRepository(Log);
this.logRep = logRepository;
}

if (!this.token) {
await this.parseToken();
}

const parsedResponseBody =
typeof responseBody === 'string' &&
responseBody !== 'User registration approved.'
Expand All @@ -65,8 +63,9 @@ export class Logger {

const payload =
typeof messageOrCB === 'function'
? await messageOrCB(this.request, this.user, parsedResponseBody)
? await messageOrCB(this.request, this.token, parsedResponseBody)
: messageOrCB;

const logRecord = await this.logRep.create({
payload: payload as object,
createdAt: payload?.timestamp,
Expand All @@ -80,40 +79,49 @@ export class Logger {
}
}

async fetchUser() {
if (this.token) {
const user = await User.findOne({ id: this.token.id });
if (user) {
this.user = {
data: user,
ready: true,
attempts: 0
};
async parseToken() {
const authorizationHeader = this.request.headers.authorization;

if (!authorizationHeader) {
throw 'Missing token/api key';
}

if (/^[A-Fa-f0-9]{32}$/.test(authorizationHeader)) {
// API Key Logic
const hashedKey = createHash('sha256')
.update(authorizationHeader)
.digest('hex');
const apiKey = await ApiKey.findOne(
{ hashedKey },
{ relations: ['user'] }
);

if (!apiKey) {
throw 'Invalid API key';
}

// Update last used and assign token
apiKey.lastUsed = new Date();
await apiKey.save();

this.token = { id: apiKey.user.id };
} else {
// JWT Logic
try {
const parsedUserFromJwt = jwt.verify(
authorizationHeader,
process.env.JWT_SECRET!
) as UserToken;
this.token = { id: parsedUserFromJwt.id };
} catch (err) {
throw 'Invalid JWT token';
}
this.user = {
data: undefined,
ready: false,
attempts: this.user.attempts + 1
};
}
}

// Constructor takes a request and sets it to a class variable
constructor(req: Request) {
this.request = req;
this.logId = '123123123123';
const authToken = req.headers.authorization;
if (authToken) {
const tokenPayload = decode(
authToken as string
) as unknown as AccessTokenPayload;
this.token = tokenPayload;
User.findOne({ id: this.token.id }).then((user) => {
if (user) {
this.user = { data: user, ready: true, attempts: 0 };
}
});
}
}
}

Expand Down

0 comments on commit 1c5e30a

Please sign in to comment.