Skip to content

Commit

Permalink
Auth added to all endpoints
Browse files Browse the repository at this point in the history
  • Loading branch information
jacoblurie29 committed Apr 6, 2024
1 parent bffc135 commit 221f747
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 33 deletions.
10 changes: 8 additions & 2 deletions src/config/server.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import routes from '../routes/routes';
import router from '../routes/routes';
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import type { Request, Response } from 'express';

export const configureServer = () => {
const app = express();
Expand All @@ -21,7 +22,12 @@ export const configureServer = () => {
}

// Routes
routes(app);
app.use(router);

// 404 - No route found
router.use((_: Request, res: Response) => {
res.status(404).send('404: Page not found');
});

// Create the server
return app;
Expand Down
61 changes: 55 additions & 6 deletions src/controllers/User.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { trimPassenger } from '../util/trim';
import Airtable from 'airtable';
import Joi from 'joi';
import clerkClient from '@clerk/clerk-sdk-node';
import type { WithAuthProp } from '@clerk/clerk-sdk-node';
import type { Request, Response } from 'express';
import type { PassengerData } from '../interfaces/passenger/passenger.interface';

/**
* This function returns all passengers connected to a user
* This function returns user data if it exists in the database
*
* Steps to complete:
* 1. Get the first name, last name, and birthdate from the request body, if it doesn't exist return a 400
Expand All @@ -17,7 +19,7 @@ import type { PassengerData } from '../interfaces/passenger/passenger.interface'
* @param req - the request object
* @param res - the response object
*/
export const createUser = async (req: Request, res: Response) => {
export const createUser = async (req: WithAuthProp<Request>, res: Response) => {
// given a first name, last name, and birthdate, check if a user exists in the database
const schema = Joi.object({
firstName: Joi.string().required(),
Expand All @@ -34,10 +36,12 @@ export const createUser = async (req: Request, res: Response) => {
return;
}

// Format it like this: Cardenas, Jessica | 1989-11-10, birthday is a javascript date object
const formattedUserId = `${req.body.lastName}, ${req.body.firstName} | ${
req.body.birthdate.split('T')[0]
}`;
// birthdate is formatted MM-DD-YYYY, change it to YYYY-MM-DD
const newBirthdateParts = req.body.birthdate.split('-');
const newBirthdate = `${newBirthdateParts[2]}-${newBirthdateParts[0]}-${newBirthdateParts[1]}`;

// Format it like this: Cardenas, Jessica | 1989-11-10,
const formattedUserId = `${req.body.lastName}, ${req.body.firstName} | ${newBirthdate}`;

const base = new Airtable({
apiKey: process.env.AIRTABLE_API_KEY || '',
Expand All @@ -54,7 +58,52 @@ export const createUser = async (req: Request, res: Response) => {
return res.status(400).send('User does not exist');
}

if (process.env.ENVIRONMENT !== 'test') {
try {
await clerkClient.users.updateUserMetadata(req.auth.userId || '', {
publicMetadata: {
onboardComplete: false,
},
});
} catch (error) {
return res.status(500).send('Error updating user metadata');
}
}

return res
.status(200)
.send(trimPassenger(passenger[0] as unknown as PassengerData));
};

/**
* Links a user to an Airtable record.
*
* @param req - The request object containing the user's authentication information.
* @param res - The response object used to send the result of the operation.
* @returns A response indicating whether the user was successfully linked to the Airtable record.
*/
export const linkUserToAirtableRecord = async (
req: WithAuthProp<Request>,
res: Response
) => {
const { airtableRecordId } = req.body;

if (!airtableRecordId) {
return res.status(400).send('Airtable ID is required');
}

if (process.env.ENVIRONMENT !== 'test') {
try {
await clerkClient.users.updateUserMetadata(req.auth.userId || '', {
publicMetadata: {
airtableRecordId,
onboardComplete: true,
},
});
} catch (error) {
return res.status(500).send('Error updating user metadata');
}
}

return res.status(200).send('User linked to Airtable record');
};
40 changes: 40 additions & 0 deletions src/middleware/validateAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ClerkExpressWithAuth } from '@clerk/clerk-sdk-node';
import dotenv from 'dotenv';
import type { WithAuthProp } from '@clerk/clerk-sdk-node';
import type { Request, Response, NextFunction } from 'express';
dotenv.config();

/**
* Middleware function to validate user authentication.
* If the environment is not test, it uses ClerkExpressWithAuth to validate the user's session.
* If the user is authenticated, it calls the next middleware function.
* If the user is not authenticated, it returns a 403 status code.
* If the environment is test, it calls the next middleware function without authentication.
*
* @param req - The Express request object.
* @param res - The Express response object.
* @param next - The next middleware function.
*/
const validateAuth = (
req: WithAuthProp<Request>,
res: Response,
next: NextFunction
) => {
// If the environment is not test, use ClerkExpressWithAuth to validate the user's session
if (process.env.ENVIRONMENT !== 'test') {
// Use ClerkExpressWithAuth to validate the user's session then call next() if the user is authenticated
ClerkExpressWithAuth({})(req, res, async () => {
if (req.auth.sessionId && req.auth.userId) {
return next();
}

// If the user is not authenticated, return a 403 status code
return res.status(401).send('Unauthorized');
});
} else {
// If the environment is test, call next() to continue (no authentication is required in test environment)
return next();
}
};

export default validateAuth;
58 changes: 33 additions & 25 deletions src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,43 @@ import {
updateFlightRequest,
getFlightLegsById,
} from '../controllers/FlightRequest.controller';
import { createUser } from '../controllers/User.controller';
import type { Express, Request, Response } from 'express';
import {
createUser,
linkUserToAirtableRecord,
} from '../controllers/User.controller';
import validateAuth from '../middleware/validateAuth';
import express from 'express';
import type { LooseAuthProp } from '@clerk/clerk-sdk-node';
import type { Request, Response } from 'express';

const routes = (app: Express) => {
// healthcheck
app.get('/healthcheck', (_: Request, res: Response) => res.sendStatus(200));
declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace Express {
interface Request extends LooseAuthProp {}
}
}

/* User Controller */
app.post('/user/', createUser);
// Protected routes (require authentication)
const router = express.Router();

/* Passenger Controller Routes */
app.get('/passenger/accompanying', getAllPassengersForUser);
app.get('/passenger/:id', getPassengerById);
app.post('/passenger/:id', createPassenger);
app.put('/passenger/:id', updatePassenger);
// healthcheck
router.get('/healthcheck', (_: Request, res: Response) => res.sendStatus(200));

/* Flight Request Controller Routes */
app.get('/requests/', getAllFlightRequestsForUser);
app.get('/requests/:id', getFlightRequestById);
app.get('/requests/:id/legs', getFlightLegsById);
app.post('/requests/', createFlightRequest);
app.put('/requests/:id', updateFlightRequest);
/* User Controller */
router.post('/user/', validateAuth, createUser);
router.post('/user/link', validateAuth, linkUserToAirtableRecord);

// 404
app.use((_: Request, res: Response) => {
res.status(404).send('404: Page not found');
});
/* Passenger Controller Routes */
router.get('/passenger/accompanying', validateAuth, getAllPassengersForUser);
router.get('/passenger/:id', validateAuth, getPassengerById);
router.post('/passenger/:id', validateAuth, createPassenger);
router.put('/passenger/:id', validateAuth, updatePassenger);

return app;
};
/* Flight Request Controller Routes */
router.get('/requests/', validateAuth, getAllFlightRequestsForUser);
router.get('/requests/:id', validateAuth, getFlightRequestById);
router.get('/requests/:id/legs', validateAuth, getFlightLegsById);
router.post('/requests/', validateAuth, createFlightRequest);
router.put('/requests/:id', validateAuth, updateFlightRequest);

export default routes;
export default router;

0 comments on commit 221f747

Please sign in to comment.