diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d837ecb..774f09f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,16 +1,10 @@ -name: Prettier on Push and PR +name: Build on: push: - branches: - - "main" - - "dev**" - pull_request: - branches: - - "main" jobs: - prettier: + build: runs-on: ubuntu-latest steps: @@ -18,7 +12,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "18.x" + cache: 'yarn' - name: Install dependencies run: yarn - - name: Run Prettier - run: npx prettier --write . + - name: Build + run: yarn build \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0092e4f..9dccd8e 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,13 +1,7 @@ -name: Lint on Push and PR +name: Lint on: push: - branches: - - "main" - - "dev**" - pull_request: - branches: - - "main" jobs: lint: @@ -18,7 +12,8 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "18.x" + cache: 'yarn' - name: Install dependencies run: yarn - name: Run ESLint - run: npx eslint . + run: npx eslint . \ No newline at end of file diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml new file mode 100644 index 0000000..701d6da --- /dev/null +++ b/.github/workflows/prettier.yml @@ -0,0 +1,19 @@ +name: Prettier + +on: + push: + +jobs: + prettier: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "18.x" + cache: 'yarn' + - name: Install dependencies + run: yarn + - name: Run Prettier + run: npx prettier --write . diff --git a/README.md b/README.md index 9e111b0..cb79c58 100644 --- a/README.md +++ b/README.md @@ -1 +1,10 @@ -# rp-api +# API for Reflections | Projections 2024 + +Contributors: + +- Aydan Pirani +- Divya Koya +- Riya Patel +- Jacob Chang +- Alex Yang +- Shreenija Daggavolu diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..7e68363 --- /dev/null +++ b/appspec.yml @@ -0,0 +1,22 @@ +version: 0.0 +os: linux +files: + - source: / + destination: /home/ubuntu/rp-api +file_exists_behavior: OVERWRITE +hooks: + BeforeInstall: + - location: scripts/install_dependencies.sh + timeout: 300 + runas: root + AfterInstall: + - location: scripts/build.sh + timeout: 300 + runas: root + ApplicationStart: + - location: scripts/install_dependencies.sh + timeout: 300 + runas: root + - location: scripts/reload_server.sh + timeout: 300 + runas: root diff --git a/package.json b/package.json index 6ecac82..e0cb02b 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "private": true, "version": "0.0.0", "scripts": { - "dev": "ENV=DEVELOPMENT nodemon -w src/ -x tsx src/app.ts", - "start": "ENV=PRODUCTION tsx src/app.ts", + "dev": "nodemon -w src/ -x tsx src/app.ts", + "start": "tsx src/app.ts", "lint": "yarn prettier . --write", "kill": "kill -9 $(lsof -t -i :3000)", "build": "tsc" @@ -13,8 +13,10 @@ "@types/dotenv": "^8.2.0", "@types/express": "^4.17.21", "@types/express-rate-limit": "^6.0.0", + "@types/jsonwebtoken": "^9.0.6", "@types/morgan": "^1.9.9", "@types/node": "^20.9.3", + "@types/passport-google-oauth20": "^2.0.14", "@typescript-eslint/eslint-plugin": "^6.4.0", "cross-env": "^7.0.3", "eslint": "8.2.0", @@ -34,13 +36,18 @@ "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "axios": "^1.6.8", + "@types/cors": "^2.8.17", "body-parser": "^1.20.2", + "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.1", "express-rate-limit": "^6.0.0", "http-status-codes": "^2.3.0", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.2.3", "morgan": "^1.10.0", + "passport": "^0.7.0", + "passport-google-oauth20": "^2.0.0", "tsx": "^4.5.0", "typescript": "*", "zod": "^3.22.4" diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100644 index 0000000..1d8b9e3 --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/ubuntu/rp-api +yarn build diff --git a/scripts/install_dependencies.sh b/scripts/install_dependencies.sh new file mode 100644 index 0000000..2f6b228 --- /dev/null +++ b/scripts/install_dependencies.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/ubuntu/rp-api +sudo yarn diff --git a/scripts/reload_server.sh b/scripts/reload_server.sh new file mode 100644 index 0000000..44b2694 --- /dev/null +++ b/scripts/reload_server.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd /home/ubuntu/rp-api +sudo pm2 reload RP_API diff --git a/src/app.ts b/src/app.ts index d801277..aad5995 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,14 +1,19 @@ import express from "express"; import { StatusCodes } from "http-status-codes"; -import morgan from "morgan"; -import bodyParser from "body-parser"; import { Config } from "./config"; import { connectToDatabase } from "./utilities"; import { rateLimiter } from "./middleware/rateLimiter"; +import cors from "cors"; +import morgan from "morgan"; +import bodyParser from "body-parser"; import errorHandler from "./middleware/error-handler"; +import attendeeRouter from "./services/attendees/attendee-router"; import authRouter from "./services/auth/auth-router"; +import eventRouter from "./services/events/event-router"; +import registrationRouter from "./services/registration/registration-router"; +import subscriptionRouter from "./services/subscription/subscription-router"; const app = express(); @@ -18,15 +23,21 @@ app.disable("etag"); app.use(rateLimiter); +app.use(cors()); + // To display the logs every time app.use("/", morgan("dev")); app.use("/", bodyParser.json()); +// API routes +app.use("/attendee", attendeeRouter); app.use("/auth", authRouter); +app.use("/event", eventRouter); +app.use("/registration", registrationRouter); +app.use("/subscription", subscriptionRouter); app.get("/status", (_, res) => { - console.log(StatusCodes.OK); return res.status(StatusCodes.OK).send("API is alive!"); }); @@ -38,5 +49,6 @@ app.use(errorHandler); app.listen(Config.DEFAULT_APP_PORT, async () => { await connectToDatabase(); + process.send?.("ready"); console.log("Server is listening on port 3000..."); }); diff --git a/src/config.ts b/src/config.ts index 43e4759..4c87b55 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,6 +7,8 @@ dotenv.config(); export const Environment = z.enum(["PRODUCTION", "DEVELOPMENT", "TESTING"]); +export const MailingListName = z.enum(["rp_interest"]); + export const Config = { DEFAULT_APP_PORT: 3000, ENV: Environment.parse(getEnv("ENV")), @@ -14,4 +16,19 @@ export const Config = { DATABASE_USERNAME: getEnv("DATABASE_USERNAME"), DATABASE_PASSWORD: getEnv("DATABASE_PASSWORD"), DATABASE_HOST: getEnv("DATABASE_HOST"), + + CLIENT_ID: getEnv("OAUTH_GOOGLE_CLIENT_ID"), + CLIENT_SECRET: getEnv("OAUTH_GOOGLE_CLIENT_SECRET"), + + // AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/", + AUTH_CALLBACK_URI_BASE: + "https://api.reflectionsprojections.org/auth/callback/", + + JWT_SIGNING_SECRET: getEnv("JWT_SIGNING_SECRET"), + JWT_EXPIRATION_TIME: "1 day", +}; + +export const DeviceRedirects: Record = { + web: "https://www.google.com/", + dev: "https://api.reflectionsprojections.org/auth/dev/", }; diff --git a/src/database.ts b/src/database.ts index 9d54a8c..b8fbb1f 100644 --- a/src/database.ts +++ b/src/database.ts @@ -1,5 +1,18 @@ import mongoose, { Schema } from "mongoose"; -import { RoleInfo, RoleSchema } from "./services/auth/auth-schema"; +import { + AttendeeSchema, + AttendeeValidator, +} from "./services/attendees/attendee-schema"; +import { RoleValidator, RoleSchema } from "./services/auth/auth-schema"; +import { EventSchema, EventValidator } from "./services/events/events-schema"; +import { + RegistrationSchema, + RegistrationValidator, +} from "./services/registration/registration-schema"; +import { + SubscriptionSchemaValidator, + SubscriptionSchema, +} from "./services/subscription/subscription-schema"; mongoose.set("toObject", { versionKey: false }); @@ -31,5 +44,17 @@ function initializeModel( // Example usage export const Database = { - ROLES: initializeModel("roles", RoleSchema, RoleInfo), + ROLES: initializeModel("roles", RoleSchema, RoleValidator), + EVENTS: initializeModel("events", EventSchema, EventValidator), + SUBSCRIPTIONS: initializeModel( + "subscriptions", + SubscriptionSchema, + SubscriptionSchemaValidator + ), + ATTENDEES: initializeModel("attendees", AttendeeSchema, AttendeeValidator), + REGISTRATION: initializeModel( + "registration", + RegistrationSchema, + RegistrationValidator + ), }; diff --git a/src/middleware/cors-middleware.ts b/src/middleware/cors-middleware.ts new file mode 100644 index 0000000..599548f --- /dev/null +++ b/src/middleware/cors-middleware.ts @@ -0,0 +1,25 @@ +// import cors from "cors"; + +// // Allow CORS for Netlify deploy previews +// const allowedOrigins = ["https://reflectionsprojections.org"]; +// // Function to check if the origin matches the deploy preview format +// function isNetlifyDeployPreview(origin: string) { +// const regex = new RegExp("deploy-preview-[0-9]*(--rp2024.netlify.app)(.*)"); +// return regex.test(origin); +// } +// +// const corsMiddleware = cors({ +// origin: function (origin, callback) { +// if ( +// !origin || +// allowedOrigins.includes(origin) || +// isNetlifyDeployPreview(origin) +// ) { +// callback(null, true); +// } else { +// callback(new Error("Not allowed by CORS")); +// } +// }, +// }); + +// export default cors(); diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts new file mode 100644 index 0000000..4c672a4 --- /dev/null +++ b/src/middleware/role-checker.ts @@ -0,0 +1,67 @@ +import { NextFunction, Request, Response } from "express"; +import { JwtPayloadValidator, Role } from "../services/auth/auth-models"; +import { z } from "zod"; +import jsonwebtoken from "jsonwebtoken"; +import { Config } from "../config"; +import { StatusCodes } from "http-status-codes"; + +export default function RoleChecker( + requiredRoles: z.infer[], + weakVerification: boolean = false +) { + return function (req: Request, res: Response, next: NextFunction) { + const jwt = req.headers.authorization; + + if (jwt == undefined) { + if (weakVerification) { + next(); + } + + return res.status(StatusCodes.BAD_REQUEST).json({ error: "NoJWT" }); + } + + try { + const payloadData = jsonwebtoken.verify( + jwt, + Config.JWT_SIGNING_SECRET + ); + + const payload = JwtPayloadValidator.parse(payloadData); + res.locals.payload = payload; + + const error = new Error("InvalidRoles"); + const userRoles = payload.roles; + + if (weakVerification) { + next(); + } + + if (requiredRoles.length == 0) { + next(); + } + + // Admins (staff) can access any endpoint + if (userRoles.includes(Role.Enum.ADMIN)) { + next(); + } + + // Corporate role can access corporate only endpoints + if (requiredRoles.includes(Role.Enum.CORPORATE)) { + if (userRoles.includes(Role.Enum.CORPORATE)) { + next(); + } + } + + // Need to be a user to access user endpoints (app users) + if (requiredRoles.includes(Role.Enum.USER)) { + if (userRoles.includes(Role.Enum.USER)) { + next(); + } + } + + throw error; + } catch (error) { + next(error); + } + }; +} diff --git a/src/services/attendees/attendee-router.ts b/src/services/attendees/attendee-router.ts new file mode 100644 index 0000000..8006361 --- /dev/null +++ b/src/services/attendees/attendee-router.ts @@ -0,0 +1,43 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { AttendeeValidator } from "./attendee-schema"; +import { Database } from "../../database"; + +const attendeeRouter = Router(); + +// Create a new attendee +attendeeRouter.post("/", async (req, res, next) => { + try { + const attendeeData = AttendeeValidator.parse(req.body); + const attendee = new Database.ATTENDEES(attendeeData); + await attendee.save(); + + return res.status(StatusCodes.CREATED).json(attendeeData); + } catch (error) { + next(error); + } +}); + +// Check if a user email exists +attendeeRouter.get("/:email", async (req, res, next) => { + try { + const { email } = req.params; + + // Check if the user exists in the database + const userExists = await Database.ATTENDEES.exists({ email }); + + if (!userExists) { + return { error: "DoesNotExist" }; + } + + const user = await Database.ATTENDEES.findOne({ + email, + }); + + return res.status(StatusCodes.OK).json(user); + } catch (error) { + next(error); + } +}); + +export default attendeeRouter; diff --git a/src/services/attendees/attendee-schema.ts b/src/services/attendees/attendee-schema.ts new file mode 100644 index 0000000..358795d --- /dev/null +++ b/src/services/attendees/attendee-schema.ts @@ -0,0 +1,26 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for attendee +const AttendeeValidator = z.object({ + userId: z.string(), + name: z.string(), + email: z.string().email(), + events: z.array(z.string()), + dietary_restrictions: z.string(), + priority_expiry: z.date().nullable().optional(), + points: z.number().min(0).default(0), +}); + +// Mongoose schema for attendee +const AttendeeSchema = new mongoose.Schema({ + userId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + events: [{ type: mongoose.Schema.Types.ObjectId, ref: "Event" }], + dietary_restrictions: { type: String, required: true }, + priority_expiry: { type: Date, default: null }, + points: { type: Number, default: 0 }, +}); + +export { AttendeeSchema, AttendeeValidator }; diff --git a/src/services/auth/auth-models.ts b/src/services/auth/auth-models.ts new file mode 100644 index 0000000..2dbed28 --- /dev/null +++ b/src/services/auth/auth-models.ts @@ -0,0 +1,8 @@ +import { z } from "zod"; + +export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]); + +export const JwtPayloadValidator = z.object({ + userId: z.string(), + roles: Role.array(), +}); diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts index fa2e865..8ab4301 100644 --- a/src/services/auth/auth-router.ts +++ b/src/services/auth/auth-router.ts @@ -1,29 +1,73 @@ import { Router } from "express"; +import passport from "passport"; +import { Config, DeviceRedirects } from "../../config"; import { StatusCodes } from "http-status-codes"; -import { createId } from "@paralleldrive/cuid2"; -import { Database } from "../../database"; -import { Role } from "./auth-schema"; +import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20"; +import { createGoogleStrategy, getJwtPayloadFromDatabase } from "./auth-utils"; +import jsonwebtoken from "jsonwebtoken"; + +const authStrategies: Record = {}; const authRouter = Router(); -authRouter.get("/", async (req, res) => { - const result = await Database.ROLES.find(); - const mappedResult = result.map((item) => item.toObject()); - return res.status(StatusCodes.OK).send(mappedResult); +authRouter.get("/login/:DEVICE/", (req, res) => { + const device = req.params.DEVICE; + + // Check if this is a valid device (i.e. does a redirectURI exist for it) + if (!(device in DeviceRedirects)) { + return res.status(StatusCodes.BAD_REQUEST).send({ error: "BadDevice" }); + } + + // Check if we've already created an auth strategy for the device + // If not, create a new one + if (!(device in authStrategies)) { + authStrategies[device] = createGoogleStrategy(device); + } + + // Use the pre-created strategy + passport.use(device, authStrategies[device]); + + return passport.authenticate(device, { + scope: ["profile", "email"], + })(req, res); }); -authRouter.post("/", async (_, res, next) => { - const user = { - userId: createId(), - roles: [Role.Enum.USER], - }; - - try { - const result = (await Database.ROLES.create(user)).toObject(); - return res.status(StatusCodes.CREATED).send(result); - } catch (err) { - next(err); +authRouter.get( + "/callback/:DEVICE", + (req, res, next) => + // Check based on the pre-existing strategy name + passport.authenticate(req.params.DEVICE, { + session: false, + })(req, res, next), + async function (req, res, next) { + // Authentication failed - redirect to login + if (req.user == undefined) { + return res.redirect(`/auth/login/${req.params.DEVICE}`); + } + const userData = req.user as Profile; + const userId = `user${userData.id}`; + + // Generate the JWT, and redirect to JWT initialization + try { + const jwtPayload = ( + await getJwtPayloadFromDatabase(userId) + ).toObject(); + const token = jsonwebtoken.sign( + jwtPayload, + Config.JWT_SIGNING_SECRET, + { expiresIn: Config.JWT_EXPIRATION_TIME } + ); + const redirectUri = + DeviceRedirects[req.params.DEVICE] + `?token=${token}`; + return res.redirect(redirectUri); + } catch (error) { + next(error); + } } +); + +authRouter.get("/dev/", (req, res) => { + return res.status(StatusCodes.OK).json(req.query); }); export default authRouter; diff --git a/src/services/auth/auth-schema.ts b/src/services/auth/auth-schema.ts index 1977d83..9434d11 100644 --- a/src/services/auth/auth-schema.ts +++ b/src/services/auth/auth-schema.ts @@ -1,23 +1,36 @@ import { Schema } from "mongoose"; import { z } from "zod"; +import { Role } from "./auth-models"; -export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]); - -export const RoleInfo = z.object({ - userId: z.coerce.string().cuid2(), +export const RoleValidator = z.object({ + userId: z.coerce.string().regex(/user[0-9]*/), + name: z.coerce.string(), + email: z.coerce.string().email(), roles: z.array(Role), }); -export const RoleSchema = new Schema({ - userId: { - type: String, - required: true, - unique: true, - }, - roles: { - type: [String], - enum: Role.Values, - default: [], - required: true, +export const RoleSchema = new Schema( + { + userId: { + type: String, + required: true, + unique: true, + }, + name: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + roles: { + type: [String], + enum: Role.Values, + default: [], + required: true, + }, }, -}); + { timestamps: { createdAt: "createdAt" } } +); diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts new file mode 100644 index 0000000..b4a3c2e --- /dev/null +++ b/src/services/auth/auth-utils.ts @@ -0,0 +1,41 @@ +// Create a function to generate GoogleStrategy instances +import { Strategy as GoogleStrategy } from "passport-google-oauth20"; +import { Config } from "../../config"; +import { Database } from "../../database"; + +export function createGoogleStrategy(device: string) { + return new GoogleStrategy( + { + clientID: Config.CLIENT_ID, + clientSecret: Config.CLIENT_SECRET, + callbackURL: Config.AUTH_CALLBACK_URI_BASE + device, + }, + + // Strategy -> insert user into database if they don't exist + async function (_1, _2, profile, cb) { + const userId = `user${profile.id}`; + const name = profile.displayName; + const email = profile._json.email; + + Database.ROLES.findOneAndUpdate( + { userId: userId }, + { userId, name, email }, + { upsert: true } + ) + .then(() => cb(null, profile)) + .catch((err) => cb(err, profile)); + } + ); +} + +export async function getJwtPayloadFromDatabase(userId: string) { + const payload = await Database.ROLES.findOne({ userId: userId }).select([ + "userId", + "roles", + ]); + if (!payload) { + throw new Error("NoUserFound"); + } + + return payload; +} diff --git a/src/services/events/event-router.ts b/src/services/events/event-router.ts new file mode 100644 index 0000000..f7e154a --- /dev/null +++ b/src/services/events/event-router.ts @@ -0,0 +1,82 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { EventValidator } from "./event-schema"; +import { Database } from "../../database"; + +const eventRouter = Router(); + +// Create a new event +eventRouter.post("/", async (req, res, next) => { + try { + const eventData = EventValidator.parse(req.body); + const event = new Database.EVENTS(eventData); + await event.save(); + return res.status(StatusCodes.CREATED).json(event); + } catch (error) { + next(error); + } +}); + +// Get all events +eventRouter.get("/", async (req, res, next) => { + try { + const events = await Database.EVENTS.find(); + return res.status(StatusCodes.OK).json(events); + } catch (error) { + next(error); + } +}); + +// Get event by ID +eventRouter.get("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findOneAndUpdate( + { _id: req.params.id }, + { new: true } + ); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +// Update event +eventRouter.patch("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findOneAndUpdate( + { _id: req.params.id }, + req.body, + { new: true } + ); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +// Delete event +eventRouter.delete("/:id", async (req, res, next) => { + try { + const event = await Database.EVENTS.findByIdAndDelete(req.params.id); + + if (!event) { + return { error: "DoesNotExist" }; + } + + return res.status(StatusCodes.OK).json(event); + } catch (error) { + next(error); + } +}); + +export default eventRouter; diff --git a/src/services/events/event-schema.ts b/src/services/events/event-schema.ts new file mode 100644 index 0000000..73890cd --- /dev/null +++ b/src/services/events/event-schema.ts @@ -0,0 +1,47 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for event +const EventValidator = z.object({ + name: z.string(), + description: z.string(), + start_time: z.date(), + end_time: z.date(), + attendees: z.array(z.string()), + location: z.array( + z.object({ + description: z.string(), + tags: z.array(z.string()), + latitude: z.number(), + longitude: z.number(), + }) + ), + virtual: z.boolean(), + points: z.number().min(0).default(0), + imageUrl: z.string().nullable().optional(), + visible: z.boolean().default(false), +}); + +// Mongoose schema for location +const LocationSchema = new mongoose.Schema({ + description: { type: String, required: true }, + tags: [{ type: String }], + latitude: { type: Number, required: true }, + longitude: { type: Number, required: true }, +}); + +// Mongoose schema for event +const EventSchema = new mongoose.Schema({ + name: { type: String, required: true }, + description: { type: String, required: true }, + start_time: { type: Date, required: true }, + end_time: { type: Date, required: true }, + attendees: [{ type: mongoose.Schema.Types.ObjectId, ref: "Attendee" }], + location: [LocationSchema], + virtual: { type: Boolean, required: true }, + points: { type: Number, default: 0 }, + imageUrl: { type: String, default: null }, + visible: { type: Boolean, default: false }, +}); + +export { EventSchema, EventValidator, LocationSchema }; diff --git a/src/services/events/events-router.ts b/src/services/events/events-router.ts new file mode 100644 index 0000000..ea035a8 --- /dev/null +++ b/src/services/events/events-router.ts @@ -0,0 +1,19 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { EventValidator } from "./events-schema"; +import { Database } from "../../database"; + +const eventRouter = Router(); + +eventRouter.post("/", async (req, res, next) => { + try { + const validatedData = EventValidator.parse(req.body); + const event = new Database.EVENTS(validatedData); + await event.save(); + return res.status(StatusCodes.CREATED).json(event.toObject()); + } catch (error) { + next(error); + } +}); + +export default eventRouter; diff --git a/src/services/events/events-schema.ts b/src/services/events/events-schema.ts new file mode 100644 index 0000000..b9731e2 --- /dev/null +++ b/src/services/events/events-schema.ts @@ -0,0 +1,35 @@ +import { Schema } from "mongoose"; +import { z } from "zod"; + +export const EventValidator = z.object({ + eventId: z.coerce.string(), + name: z.string(), + startTime: z.coerce.date(), + endTime: z.coerce.date(), + points: z.number().min(0), +}); + +export const EventSchema = new Schema({ + eventId: { + type: String, + required: true, + unique: true, + }, + name: { + type: String, + required: true, + }, + startTime: { + type: Date, + required: true, + }, + endTime: { + type: Date, + required: true, + }, + + points: { + type: Number, + required: true, + }, +}); diff --git a/src/services/registration/registration-router.ts b/src/services/registration/registration-router.ts new file mode 100644 index 0000000..8b18e65 --- /dev/null +++ b/src/services/registration/registration-router.ts @@ -0,0 +1,10 @@ +import { Router } from "express"; +// import { StatusCodes } from "http-status-codes"; +// import { RegistrationValidator } from "./registration-schema"; +// import { Database } from "../../database"; + +const registrationRouter = Router(); + +// TODO: registration routes + +export default registrationRouter; diff --git a/src/services/registration/registration-schema.ts b/src/services/registration/registration-schema.ts new file mode 100644 index 0000000..ab19968 --- /dev/null +++ b/src/services/registration/registration-schema.ts @@ -0,0 +1,50 @@ +import mongoose from "mongoose"; +import { z } from "zod"; + +// Zod schema for registration +const RegistrationValidator = z.object({ + userId: z.string(), + name: z.string(), + email: z.string().email(), + studentInfo: z.object({ + university: z.string().nonempty(), + graduation: z.string().nullable().optional(), + major: z.string().nullable().optional(), + }), + dietary_restrictions: z.string(), + age: z.number().nullable().optional(), + gender: z.string().nullable().optional(), + race: z.array(z.string()).nullable().optional(), + ethnicity: z.array(z.string()).nullable().optional(), + first_gen: z.string().nullable().optional(), + hear_about_rp: z.array(z.string()).nullable().optional(), + portfolio: z.string().nullable().optional(), + job_interest: z.array(z.string()).nullable().optional(), + interest_mech_puzzle: z.array(z.string()).nullable().optional(), + has_resume: z.boolean(), +}); + +// Mongoose schema for registration +const RegistrationSchema = new mongoose.Schema({ + userId: { type: String, required: true, unique: true }, + name: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + studentInfo: { + university: { type: String, required: true }, + graduation: { type: String, default: null }, + major: { type: String, default: null }, + }, + dietary_restrictions: { type: String, required: true }, + age: { type: Number, default: null }, + gender: { type: String, default: null }, + race: [{ type: String }], + ethnicity: [{ type: String }], + first_gen: { type: String, default: null }, + hear_about_rp: [{ type: String }], + portfolio: { type: String, default: null }, + job_interest: [{ type: String }], + interest_mech_puzzle: [{ type: String }], + has_resume: { type: Boolean, default: false }, +}); + +export { RegistrationSchema, RegistrationValidator }; diff --git a/src/services/subscription/subscription-router.ts b/src/services/subscription/subscription-router.ts new file mode 100644 index 0000000..93523ef --- /dev/null +++ b/src/services/subscription/subscription-router.ts @@ -0,0 +1,28 @@ +import { Router } from "express"; +import { StatusCodes } from "http-status-codes"; +import { SubscriptionValidator } from "./subscription-schema"; +import { Database } from "../../database"; +// import corsMiddleware from "../../middleware/cors-middleware"; +import cors from "cors"; + +const subscriptionRouter = Router(); + +// Create a new subscription +subscriptionRouter.post("/", cors(), async (req, res, next) => { + try { + // Validate the incoming user subscription + const subscriptionData = SubscriptionValidator.parse(req.body); + + // Upsert the user info into the corresponding Subscription collection + await Database.SUBSCRIPTIONS.findOneAndUpdate( + { mailingList: subscriptionData.mailingList }, + { $addToSet: { subscriptions: subscriptionData.email } }, + { upsert: true, new: true } + ); + return res.status(StatusCodes.CREATED).json(subscriptionData); + } catch (error) { + next(error); + } +}); + +export default subscriptionRouter; diff --git a/src/services/subscription/subscription-schema.ts b/src/services/subscription/subscription-schema.ts new file mode 100644 index 0000000..63a2fcf --- /dev/null +++ b/src/services/subscription/subscription-schema.ts @@ -0,0 +1,27 @@ +import mongoose from "mongoose"; +import { z } from "zod"; +import { MailingListName } from "../../config"; + +// Zod schema for incoming user subscriptions +const SubscriptionValidator = z.object({ + email: z.string().email(), + mailingList: MailingListName, +}); + +// Zod schema for validating subscription lists +const SubscriptionSchemaValidator = z.object({ + mailingList: MailingListName, + subscriptions: z.array(z.string().email()), +}); + +// Mongoose schema for subscription +const SubscriptionSchema = new mongoose.Schema({ + mailingList: { type: String, required: true }, + subscriptions: [{ type: String, required: true }], +}); + +export { + SubscriptionValidator, + SubscriptionSchemaValidator, + SubscriptionSchema, +}; diff --git a/src/utilities.ts b/src/utilities.ts index e009740..06a4767 100644 --- a/src/utilities.ts +++ b/src/utilities.ts @@ -13,9 +13,8 @@ function getDatabaseUrl() { const host = Config.DATABASE_HOST; let database; if (isProd()) { - return ""; - } - if (isDev()) { + database = `prod`; + } else if (isDev()) { database = `dev-${username}`; } @@ -30,9 +29,10 @@ export function isDev() { return Config.ENV == Environment.enum.DEVELOPMENT; } -export function getEnv(target: string) { - if (process.env[target] === undefined) { - throw new Error(`env value ${target} not found, exiting...`); +export function getEnv(key: string): string { + const val = process.env[key]; + if (val === undefined) { + throw new Error(`env value ${key} not found, exiting...`); } - return process.env[target]; + return val; } diff --git a/yarn.lock b/yarn.lock index 34f0d82..3e68106 100644 --- a/yarn.lock +++ b/yarn.lock @@ -238,6 +238,13 @@ dependencies: "@types/node" "*" +"@types/cors@^2.8.17": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + "@types/dotenv@^8.2.0": version "8.2.0" resolved "https://registry.npmjs.org/@types/dotenv/-/dotenv-8.2.0.tgz" @@ -262,7 +269,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.21": +"@types/express@*", "@types/express@^4.17.21": version "4.17.21" resolved "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -287,6 +294,13 @@ resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonwebtoken@^9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3" + integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== + dependencies: + "@types/node" "*" + "@types/mime@*": version "3.0.4" resolved "https://registry.npmjs.org/@types/mime/-/mime-3.0.4.tgz" @@ -311,6 +325,38 @@ dependencies: undici-types "~5.26.4" +"@types/oauth@*": + version "0.9.4" + resolved "https://registry.yarnpkg.com/@types/oauth/-/oauth-0.9.4.tgz#dcbab5efa2f34f312b915f80685760ccc8111e0a" + integrity sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A== + dependencies: + "@types/node" "*" + +"@types/passport-google-oauth20@^2.0.14": + version "2.0.14" + resolved "https://registry.yarnpkg.com/@types/passport-google-oauth20/-/passport-google-oauth20-2.0.14.tgz#6facba6f73f0aff3888564ebc907afe295c30fba" + integrity sha512-ZaZpRUAeMl3vy298ulKO1wGLn9SQtj/CyIfZL/Px5xU9pybMiQU3mhXDCBiWSbg0EK9uXT4ZoWC3ktuWY+5fwQ== + dependencies: + "@types/express" "*" + "@types/passport" "*" + "@types/passport-oauth2" "*" + +"@types/passport-oauth2@*": + version "1.4.15" + resolved "https://registry.yarnpkg.com/@types/passport-oauth2/-/passport-oauth2-1.4.15.tgz#34f2684f53aad36e664cd01ca9879224229f47e7" + integrity sha512-9cUTP/HStNSZmhxXGuRrBJfEWzIEJRub2eyJu3CvkA+8HAMc9W3aKdFhVq+Qz1hi42qn+GvSAnz3zwacDSYWpw== + dependencies: + "@types/express" "*" + "@types/oauth" "*" + "@types/passport" "*" + +"@types/passport@*": + version "1.0.16" + resolved "https://registry.yarnpkg.com/@types/passport/-/passport-1.0.16.tgz#5a2918b180a16924c4d75c31254c31cdca5ce6cf" + integrity sha512-FD0qD5hbPWQzaM0wHUnJ/T0BBCJBxCeemtnCwc/ThhTg3x9jfrAcRUmj5Dopza+MfFS9acTe3wk7rcVnRIp/0A== + dependencies: + "@types/express" "*" + "@types/qs@*": version "6.9.14" resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz" @@ -617,6 +663,11 @@ balanced-match@^1.0.0: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64url@3.x.x: + version "3.0.1" + resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" + integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== + basic-auth@~2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz" @@ -674,6 +725,11 @@ bson@^6.2.0: resolved "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz" integrity sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + builtin-modules@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz" @@ -786,14 +842,15 @@ core-js-pure@^3.30.2: resolved "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.36.1.tgz" integrity sha512-NXCvHvSVYSrewP0L5OhltzXeWFJLo2AL2TYnj6iLV3Bw8mM62wAQMNgUCRI6EBu6hVVpbCxmOPlxh1Ikw2PfUA== -cross-env@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" - integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== dependencies: - cross-spawn "^7.0.1" + object-assign "^4" + vary "^1" -cross-spawn@^7.0.1, cross-spawn@^7.0.2: +cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -919,6 +976,13 @@ dotenv@*, dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -1931,6 +1995,22 @@ json5@^1.0.2: dependencies: minimist "^1.2.0" +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + "jsx-ast-utils@^2.4.1 || ^3.0.0", jsx-ast-utils@^3.2.1: version "3.3.5" resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz" @@ -1941,6 +2021,23 @@ json5@^1.0.2: object.assign "^4.1.4" object.values "^1.1.6" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + kareem@2.5.1: version "2.5.1" resolved "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz" @@ -1973,11 +2070,46 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -2167,7 +2299,12 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -object-assign@^4.1.1: +oauth@0.10.x: + version "0.10.0" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.10.0.tgz#3551c4c9b95c53ea437e1e21e46b649482339c58" + integrity sha512-1orQ9MT1vHFGQxhuy7E/0gECD3fd2fCC+PIX+/jgmU/gI3EpRocXtmtvxCO5x3WZ443FLTLFWNDjl5MPJf9u+Q== + +object-assign@^4, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -2279,6 +2416,38 @@ parseurl@~1.3.3: resolved "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== +passport-google-oauth20@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz#0d241b2d21ebd3dc7f2b60669ec4d587e3a674ef" + integrity sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ== + dependencies: + passport-oauth2 "1.x.x" + +passport-oauth2@1.x.x: + version "1.8.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.8.0.tgz#55725771d160f09bbb191828d5e3d559eee079c8" + integrity sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA== + dependencies: + base64url "3.x.x" + oauth "0.10.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + integrity sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA== + +passport@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/passport/-/passport-0.7.0.tgz#3688415a59a48cf8068417a8a8092d4492ca3a05" + integrity sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ== + dependencies: + passport-strategy "1.x.x" + pause "0.0.1" + utils-merge "^1.0.1" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" @@ -2304,6 +2473,11 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pause@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/pause/-/pause-0.0.1.tgz#1d408b3fdb76923b9543d96fb4c9dfd535d9cb5d" + integrity sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" @@ -2489,7 +2663,7 @@ safe-buffer@5.1.2: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1: +safe-buffer@5.2.1, safe-buffer@^5.0.1: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -2847,6 +3021,11 @@ typescript@*: resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz" integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +uid2@0.0.x: + version "0.0.4" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.4.tgz#033f3b1d5d32505f5ce5f888b9f3b667123c0a44" + integrity sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA== + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz" @@ -2879,7 +3058,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x, utils-merge@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== @@ -2889,7 +3068,7 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz" integrity sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw== -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==