From 5c1e65c409c29500e6e08ec527656e77d2188fa5 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Mon, 8 Apr 2024 01:10:46 -0500
Subject: [PATCH 1/9] Added initial auth workflow

---
 package.json                     |  3 ++
 src/config.ts                    |  5 ++
 src/services/auth/auth-router.ts | 68 +++++++++++++++++-------
 src/services/auth/auth-schema.ts | 12 ++++-
 src/utilities.ts                 |  9 ++--
 yarn.lock                        | 88 +++++++++++++++++++++++++++++++-
 6 files changed, 158 insertions(+), 27 deletions(-)

diff --git a/package.json b/package.json
index dbdbe4f..5706f6d 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
         "@types/express": "^4.17.21",
         "@types/morgan": "^1.9.9",
         "@types/node": "^20.9.3",
+        "@types/passport-google-oauth20": "^2.0.14",
         "@typescript-eslint/eslint-plugin": "^6.4.0",
         "eslint": "8.2.0",
         "eslint-config-airbnb": "19.0.4",
@@ -37,6 +38,8 @@
         "http-status-codes": "^2.3.0",
         "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/src/config.ts b/src/config.ts
index e3a6f9f..9a065d0 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -13,4 +13,9 @@ 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"),
+
+    REDIRECT_URI: "http://localhost:3000/auth/callback",
 };
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index fe85ca2..83cf899 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -1,29 +1,59 @@
 import { Router } from "express";
-import { Database } from "../../database";
+import passport, { AuthenticateOptions } from "passport";
+import { Strategy as GoogleStrategy } from "passport-google-oauth20";
+import { Config } from "../../config";
 import { StatusCodes } from "http-status-codes";
-import { Role } from "./auth-schema";
-import { createId } from "@paralleldrive/cuid2";
+import { Devices } from "./auth-schema";
+
+passport.use(
+    new GoogleStrategy(
+        {
+            clientID: Config.CLIENT_ID,
+            clientSecret: Config.CLIENT_SECRET,
+            callbackURL: Config.REDIRECT_URI,
+        },
+        function (_1, _2, profile, cb) {
+            cb(null, profile);
+        }
+    )
+);
 
 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"];
 
-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);
+    console.log(device, Devices.Values);
+
+    if (!Devices.safeParse(device).success) {
+        return res.status(StatusCodes.BAD_REQUEST).send({ error: "BadDevice" });
     }
+
+    const callbackURL = `${Config.REDIRECT_URI}/${device}`;
+
+    console.log(`|${callbackURL}|`);
+    return passport.authenticate("google", {
+        callbackURL: callbackURL,
+        scope: ["profile", "email"],
+    } as AuthenticateOptions)(req, res);
 });
 
+authRouter.get(
+    "/callback/",
+    (req, _, next) => {
+        console.log("HI!!! params be", req.params, req.query);
+        return next();
+    },
+    passport.authenticate("google", {
+        session: false,
+    }),
+    function (req, res) {
+        console.log("IN HERE");
+        console.log(req.params);
+        console.log(req.query);
+        console.log("redirecting!");
+        return res.redirect("/");
+    }
+);
+
 export default authRouter;
diff --git a/src/services/auth/auth-schema.ts b/src/services/auth/auth-schema.ts
index 1977d83..144680c 100644
--- a/src/services/auth/auth-schema.ts
+++ b/src/services/auth/auth-schema.ts
@@ -3,8 +3,11 @@ import { z } from "zod";
 
 export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
-export const RoleInfo = z.object({
-    userId: z.coerce.string().cuid2(),
+export const Devices = z.enum(["web"]);
+
+export const RoleValidator = z.object({
+    userId: z.coerce.string().regex(/user[0-9]*/),
+    email: z.coerce.string().email(),
     roles: z.array(Role),
 });
 
@@ -14,6 +17,11 @@ export const RoleSchema = new Schema({
         required: true,
         unique: true,
     },
+    email: {
+        type: String,
+        required: true,
+        unique: true,
+    },
     roles: {
         type: [String],
         enum: Role.Values,
diff --git a/src/utilities.ts b/src/utilities.ts
index 685ef19..39fc28b 100644
--- a/src/utilities.ts
+++ b/src/utilities.ts
@@ -29,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 981781b..7fad951 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -255,7 +255,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==
@@ -304,6 +304,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"
@@ -596,6 +628,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"
@@ -2108,6 +2145,11 @@ 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==
 
+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.1.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -2220,6 +2262,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"
@@ -2245,6 +2319,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"
@@ -2783,6 +2862,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"
@@ -2815,7 +2899,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==

From 149d707ab9c5a73aeada1a4b360d2a607d9fa905 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 02:07:00 -0500
Subject: [PATCH 2/9] Auth template working

---
 src/config.ts                    |  8 +++-
 src/services/auth/auth-router.ts | 66 +++++++++++++-------------------
 src/services/auth/auth-schema.ts |  2 -
 src/services/auth/auth-utils.ts  | 18 +++++++++
 4 files changed, 52 insertions(+), 42 deletions(-)
 create mode 100644 src/services/auth/auth-utils.ts

diff --git a/src/config.ts b/src/config.ts
index 9a065d0..228dcc8 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -17,5 +17,11 @@ export const Config = {
     CLIENT_ID: getEnv("OAUTH_GOOGLE_CLIENT_ID"),
     CLIENT_SECRET: getEnv("OAUTH_GOOGLE_CLIENT_SECRET"),
 
-    REDIRECT_URI: "http://localhost:3000/auth/callback",
+    // REDIRECT_URI: "http://localhost:3000/auth/callback",
+    AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/",
+    // AUTH_CALLBACK_URI_BASE: "https://api.reflectionsprojections.org/auth/callback",
+};
+
+export const DeviceRedirects: Record<string, string> = {
+    web: `https://www.reflectionsprojections.org/`,
 };
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 83cf899..8642907 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -1,58 +1,46 @@
 import { Router } from "express";
-import passport, { AuthenticateOptions } from "passport";
-import { Strategy as GoogleStrategy } from "passport-google-oauth20";
-import { Config } from "../../config";
+import passport from "passport";
+import { DeviceRedirects } from "../../config";
 import { StatusCodes } from "http-status-codes";
-import { Devices } from "./auth-schema";
-
-passport.use(
-    new GoogleStrategy(
-        {
-            clientID: Config.CLIENT_ID,
-            clientSecret: Config.CLIENT_SECRET,
-            callbackURL: Config.REDIRECT_URI,
-        },
-        function (_1, _2, profile, cb) {
-            cb(null, profile);
-        }
-    )
-);
+import { Strategy as GoogleStrategy } from "passport-google-oauth20";
+import { createGoogleStrategy } from "./auth-utils";
+
+const authStrategies: Record<string, GoogleStrategy> = {};
 
 const authRouter = Router();
 
 authRouter.get("/login/:DEVICE/", (req, res) => {
-    const device = req.params["DEVICE"];
+    const device = req.params.DEVICE;
 
-    console.log(device, Devices.Values);
-
-    if (!Devices.safeParse(device).success) {
+    // 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" });
     }
 
-    const callbackURL = `${Config.REDIRECT_URI}/${device}`;
+    // 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]);
 
-    console.log(`|${callbackURL}|`);
-    return passport.authenticate("google", {
-        callbackURL: callbackURL,
+    return passport.authenticate(device, {
         scope: ["profile", "email"],
-    } as AuthenticateOptions)(req, res);
+    })(req, res);
 });
 
 authRouter.get(
-    "/callback/",
-    (req, _, next) => {
-        console.log("HI!!! params be", req.params, req.query);
-        return next();
-    },
-    passport.authenticate("google", {
-        session: false,
-    }),
+    "/callback/:DEVICE",
+    (req, res, next) =>
+        // Check based on the pre-existing strategy name
+        passport.authenticate(req.params.DEVICE, {
+            session: false,
+        })(req, res, next),
     function (req, res) {
-        console.log("IN HERE");
-        console.log(req.params);
-        console.log(req.query);
-        console.log("redirecting!");
-        return res.redirect("/");
+        const redirectUri = `${DeviceRedirects[req.params.DEVICE]}`
+        return res.redirect(redirectUri);
     }
 );
 
diff --git a/src/services/auth/auth-schema.ts b/src/services/auth/auth-schema.ts
index 144680c..15a2e8d 100644
--- a/src/services/auth/auth-schema.ts
+++ b/src/services/auth/auth-schema.ts
@@ -3,8 +3,6 @@ import { z } from "zod";
 
 export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
-export const Devices = z.enum(["web"]);
-
 export const RoleValidator = z.object({
     userId: z.coerce.string().regex(/user[0-9]*/),
     email: z.coerce.string().email(),
diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts
new file mode 100644
index 0000000..5b512bf
--- /dev/null
+++ b/src/services/auth/auth-utils.ts
@@ -0,0 +1,18 @@
+// Create a function to generate GoogleStrategy instances
+import { Strategy as GoogleStrategy } from "passport-google-oauth20";
+import { Config } from "../../config";
+
+export function createGoogleStrategy(device: string) {
+    return new GoogleStrategy(
+        {
+            clientID: Config.CLIENT_ID,
+            clientSecret: Config.CLIENT_SECRET,
+            callbackURL: `${Config.AUTH_CALLBACK_URI_BASE}${device}`,
+        },
+        async function (_1, _2, profile, cb) {
+            // Add profile to database here
+            console.log(profile);
+            cb(null, profile);
+        }
+    );
+}

From 87e2d3a40d045de53cf775bd787713948a96b2e1 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 02:22:25 -0500
Subject: [PATCH 3/9] Added changes for database insertion

---
 src/database.ts                  |  4 +--
 src/services/auth/auth-router.ts |  2 +-
 src/services/auth/auth-schema.ts | 42 +++++++++++++++++++-------------
 src/services/auth/auth-utils.ts  | 19 ++++++++++++---
 4 files changed, 44 insertions(+), 23 deletions(-)

diff --git a/src/database.ts b/src/database.ts
index 2e8c4c5..63c5d12 100644
--- a/src/database.ts
+++ b/src/database.ts
@@ -1,5 +1,5 @@
 import mongoose, { Schema } from "mongoose";
-import { RoleInfo, RoleSchema } from "./services/auth/auth-schema";
+import { RoleValidator, RoleSchema } from "./services/auth/auth-schema";
 
 mongoose.set("toObject", { versionKey: false });
 
@@ -31,5 +31,5 @@ function initializeModel(
 
 // Example usage
 export const Database = {
-    ROLES: initializeModel("roles", RoleSchema, RoleInfo),
+    ROLES: initializeModel("roles", RoleSchema, RoleValidator),
 };
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 8642907..148ebfa 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -39,7 +39,7 @@ authRouter.get(
             session: false,
         })(req, res, next),
     function (req, res) {
-        const redirectUri = `${DeviceRedirects[req.params.DEVICE]}`
+        const redirectUri = `${DeviceRedirects[req.params.DEVICE]}`;
         return res.redirect(redirectUri);
     }
 );
diff --git a/src/services/auth/auth-schema.ts b/src/services/auth/auth-schema.ts
index 15a2e8d..619cf7a 100644
--- a/src/services/auth/auth-schema.ts
+++ b/src/services/auth/auth-schema.ts
@@ -5,25 +5,33 @@ export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
 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,
+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,
+        },
     },
-    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
index 5b512bf..129940c 100644
--- a/src/services/auth/auth-utils.ts
+++ b/src/services/auth/auth-utils.ts
@@ -1,6 +1,8 @@
 // Create a function to generate GoogleStrategy instances
 import { Strategy as GoogleStrategy } from "passport-google-oauth20";
 import { Config } from "../../config";
+import { Role } from "./auth-schema";
+import { Database } from "../../database";
 
 export function createGoogleStrategy(device: string) {
     return new GoogleStrategy(
@@ -9,10 +11,21 @@ export function createGoogleStrategy(device: string) {
             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) {
-            // Add profile to database here
-            console.log(profile);
-            cb(null, profile);
+            const userId = `user${profile.id}`;
+            const name = profile.displayName;
+            const email = profile._json.email;
+            const roles = [Role.Enum.USER];
+
+            Database.ROLES.findOneAndUpdate(
+                { userId: userId },
+                { userId, name, email, roles },
+                { upsert: true }
+            )
+                .then(() => cb(null, profile))
+                .catch((err) => cb(err, profile));
         }
     );
 }

From a8b2cd6064711381c77b225990026c0de7078df8 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 02:41:54 -0500
Subject: [PATCH 4/9] Added JWT integration

---
 package.json                     |  2 +
 src/config.ts                    |  5 +-
 src/services/auth/auth-router.ts | 27 +++++++---
 src/services/auth/auth-utils.ts  | 10 ++++
 yarn.lock                        | 89 +++++++++++++++++++++++++++++++-
 5 files changed, 125 insertions(+), 8 deletions(-)

diff --git a/package.json b/package.json
index 5706f6d..e064d03 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
     "devDependencies": {
         "@types/dotenv": "^8.2.0",
         "@types/express": "^4.17.21",
+        "@types/jsonwebtoken": "^9.0.6",
         "@types/morgan": "^1.9.9",
         "@types/node": "^20.9.3",
         "@types/passport-google-oauth20": "^2.0.14",
@@ -36,6 +37,7 @@
         "dotenv": "^16.4.5",
         "express": "^4.19.1",
         "http-status-codes": "^2.3.0",
+        "jsonwebtoken": "^9.0.2",
         "mongoose": "^8.2.3",
         "morgan": "^1.10.0",
         "passport": "^0.7.0",
diff --git a/src/config.ts b/src/config.ts
index 228dcc8..deb7dc2 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -20,8 +20,11 @@ export const Config = {
     // REDIRECT_URI: "http://localhost:3000/auth/callback",
     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<string, string> = {
-    web: `https://www.reflectionsprojections.org/`,
+    web: "https://www.google.com/",
 };
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 148ebfa..6a33379 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -1,9 +1,10 @@
 import { Router } from "express";
 import passport from "passport";
-import { DeviceRedirects } from "../../config";
+import { Config, DeviceRedirects } from "../../config";
 import { StatusCodes } from "http-status-codes";
-import { Strategy as GoogleStrategy } from "passport-google-oauth20";
-import { createGoogleStrategy } from "./auth-utils";
+import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20";
+import { createGoogleStrategy, getJwtPayloadFromDatabase } from "./auth-utils";
+import jsonwebtoken from "jsonwebtoken";
 
 const authStrategies: Record<string, GoogleStrategy> = {};
 
@@ -38,9 +39,23 @@ authRouter.get(
         passport.authenticate(req.params.DEVICE, {
             session: false,
         })(req, res, next),
-    function (req, res) {
-        const redirectUri = `${DeviceRedirects[req.params.DEVICE]}`;
-        return res.redirect(redirectUri);
+    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);
+        }
     }
 );
 
diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts
index 129940c..08e82ab 100644
--- a/src/services/auth/auth-utils.ts
+++ b/src/services/auth/auth-utils.ts
@@ -29,3 +29,13 @@ export function createGoogleStrategy(device: string) {
         }
     );
 }
+
+
+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;
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 7fad951..33f43b2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -280,6 +280,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"
@@ -690,6 +697,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"
@@ -916,6 +928,13 @@ dotenv@*, dotenv@^16.4.5:
   resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz"
   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"
@@ -1909,6 +1928,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.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a"
@@ -1919,6 +1954,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"
@@ -1951,11 +2003,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.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
@@ -2504,7 +2591,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==

From f1951fef57c96906969cc473dc467296be004579 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 02:54:03 -0500
Subject: [PATCH 5/9] Added support for dev auth endpoint

---
 src/config.ts                    | 1 +
 src/services/auth/auth-router.ts | 6 +++++-
 src/services/auth/auth-utils.ts  | 2 +-
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/config.ts b/src/config.ts
index deb7dc2..5d6d7ae 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -27,4 +27,5 @@ export const Config = {
 
 export const DeviceRedirects: Record<string, string> = {
     web: "https://www.google.com/",
+    dev: "http://127.0.0.1:3000/auth/dev/"
 };
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 6a33379..92d376c 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -51,7 +51,7 @@ authRouter.get(
         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}`;
+            const redirectUri = DeviceRedirects[req.params.DEVICE] + `token=${token}`;
             return res.redirect(redirectUri);
         } catch (error) {
             next(error);
@@ -59,4 +59,8 @@ authRouter.get(
     }
 );
 
+authRouter.get("/dev/", (req, res) => {
+    return res.status(StatusCodes.OK).json({"Token": req.headers.authorization});
+})
+
 export default authRouter;
diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts
index 08e82ab..d163571 100644
--- a/src/services/auth/auth-utils.ts
+++ b/src/services/auth/auth-utils.ts
@@ -9,7 +9,7 @@ export function createGoogleStrategy(device: string) {
         {
             clientID: Config.CLIENT_ID,
             clientSecret: Config.CLIENT_SECRET,
-            callbackURL: `${Config.AUTH_CALLBACK_URI_BASE}${device}`,
+            callbackURL: Config.AUTH_CALLBACK_URI_BASE + device,
         },
 
         // Strategy -> insert user into database if they don't exist

From 65ac4cc67b3a8bf5bb639664cf9ed65278e5ded6 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 10:37:21 -0500
Subject: [PATCH 6/9] Initial role checking middleware

---
 src/config.ts                    |  3 +--
 src/middleware/role-checker.ts   | 13 +++++++++++++
 src/services/auth/auth-router.ts |  4 ++--
 3 files changed, 16 insertions(+), 4 deletions(-)
 create mode 100644 src/middleware/role-checker.ts

diff --git a/src/config.ts b/src/config.ts
index 5d6d7ae..2620159 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -17,9 +17,8 @@ export const Config = {
     CLIENT_ID: getEnv("OAUTH_GOOGLE_CLIENT_ID"),
     CLIENT_SECRET: getEnv("OAUTH_GOOGLE_CLIENT_SECRET"),
 
-    // REDIRECT_URI: "http://localhost:3000/auth/callback",
     AUTH_CALLBACK_URI_BASE: "http://localhost:3000/auth/callback/",
-    // AUTH_CALLBACK_URI_BASE: "https://api.reflectionsprojections.org/auth/callback",
+    // AUTH_CALLBACK_URI_BASE: "https://api.reflectionsprojections.org/auth/callback/",
 
     JWT_SIGNING_SECRET: getEnv("JWT_SIGNING_SECRET"),
     JWT_EXPIRATION_TIME: "1 day",
diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts
new file mode 100644
index 0000000..6999070
--- /dev/null
+++ b/src/middleware/role-checker.ts
@@ -0,0 +1,13 @@
+import { NextFunction } from "express";
+import { Role } from "../services/auth/auth-schema";
+import {z} from "zod";
+
+export default function RoleChecker(req: Request, res: Response, next: NextFunction) {
+    const jwt = req.headers.get("authorization");
+    console.log(jwt);
+
+    return function (requiredRoles: z.infer<typeof Role>[], weakVerification: boolean = false) {
+        console.log("in here")
+        next()
+    }
+}
\ No newline at end of file
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 92d376c..7b8bc9a 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -51,7 +51,7 @@ authRouter.get(
         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}`;
+            const redirectUri = DeviceRedirects[req.params.DEVICE] + `?token=${token}`;
             return res.redirect(redirectUri);
         } catch (error) {
             next(error);
@@ -60,7 +60,7 @@ authRouter.get(
 );
 
 authRouter.get("/dev/", (req, res) => {
-    return res.status(StatusCodes.OK).json({"Token": req.headers.authorization});
+    return res.status(StatusCodes.OK).json(req.query);
 })
 
 export default authRouter;

From 5888d71943f1043436ac6c96b9def95994f76231 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 11:16:59 -0500
Subject: [PATCH 7/9] Added rolechecker support

---
 src/database.ts                  |  1 -
 src/middleware/role-checker.ts   | 66 ++++++++++++++++++++++++++++----
 src/services/auth/auth-models.ts |  8 ++++
 src/services/auth/auth-schema.ts |  2 +-
 src/services/auth/auth-utils.ts  |  4 +-
 5 files changed, 68 insertions(+), 13 deletions(-)
 create mode 100644 src/services/auth/auth-models.ts

diff --git a/src/database.ts b/src/database.ts
index 8f0cc67..ebe9031 100644
--- a/src/database.ts
+++ b/src/database.ts
@@ -37,7 +37,6 @@ function initializeModel(
 // Example usage
 export const Database = {
     ROLES: initializeModel("roles", RoleSchema, RoleValidator),
-    ROLES: initializeModel("roles", RoleSchema, RoleInfo),
     EVENTS: initializeModel("events", EventSchema, EventValidator),
     SUBSCRIPTION: initializeModel(
         "subscription",
diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts
index 6999070..8bcd50b 100644
--- a/src/middleware/role-checker.ts
+++ b/src/middleware/role-checker.ts
@@ -1,13 +1,63 @@
-import { NextFunction } from "express";
-import { Role } from "../services/auth/auth-schema";
+import { NextFunction, Request, Response } from "express";
+import { JwtPayload, 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(req: Request, res: Response, next: NextFunction) {
-    const jwt = req.headers.get("authorization");
-    console.log(jwt);
 
-    return function (requiredRoles: z.infer<typeof Role>[], weakVerification: boolean = false) {
-        console.log("in here")
-        next()
+export default function RoleChecker (requiredRoles: z.infer<typeof Role>[], 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 {
+            console.log("in");
+            const payloadData = jsonwebtoken.verify(jwt, Config.JWT_SIGNING_SECRET);
+            const payload = JwtPayload.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)
+        }
     }
 }
\ No newline at end of file
diff --git a/src/services/auth/auth-models.ts b/src/services/auth/auth-models.ts
new file mode 100644
index 0000000..a34b6db
--- /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 JwtPayload =  z.object({
+    userId: z.string(),
+    roles: Role.array(),
+})
\ No newline at end of file
diff --git a/src/services/auth/auth-schema.ts b/src/services/auth/auth-schema.ts
index 619cf7a..b688077 100644
--- a/src/services/auth/auth-schema.ts
+++ b/src/services/auth/auth-schema.ts
@@ -1,7 +1,7 @@
 import { Schema } from "mongoose";
 import { z } from "zod";
+import { Role } from "./auth-models";
 
-export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
 export const RoleValidator = z.object({
     userId: z.coerce.string().regex(/user[0-9]*/),
diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts
index d163571..fbb4e11 100644
--- a/src/services/auth/auth-utils.ts
+++ b/src/services/auth/auth-utils.ts
@@ -1,7 +1,6 @@
 // Create a function to generate GoogleStrategy instances
 import { Strategy as GoogleStrategy } from "passport-google-oauth20";
 import { Config } from "../../config";
-import { Role } from "./auth-schema";
 import { Database } from "../../database";
 
 export function createGoogleStrategy(device: string) {
@@ -17,11 +16,10 @@ export function createGoogleStrategy(device: string) {
             const userId = `user${profile.id}`;
             const name = profile.displayName;
             const email = profile._json.email;
-            const roles = [Role.Enum.USER];
 
             Database.ROLES.findOneAndUpdate(
                 { userId: userId },
-                { userId, name, email, roles },
+                { userId, name, email },
                 { upsert: true }
             )
                 .then(() => cb(null, profile))

From dc4b9c8665c1cc42dc5a089125266b1a3fe9ce7f Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 11:17:54 -0500
Subject: [PATCH 8/9] Added linting

---
 src/config.ts                    |  2 +-
 src/middleware/role-checker.ts   | 26 +++++++++++++++-----------
 src/services/auth/auth-models.ts |  4 ++--
 src/services/auth/auth-router.ts | 19 +++++++++++++------
 src/services/auth/auth-schema.ts |  1 -
 src/services/auth/auth-utils.ts  | 10 ++++++----
 6 files changed, 37 insertions(+), 25 deletions(-)

diff --git a/src/config.ts b/src/config.ts
index 1239660..38aab55 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -28,5 +28,5 @@ export const Config = {
 
 export const DeviceRedirects: Record<string, string> = {
     web: "https://www.google.com/",
-    dev: "http://127.0.0.1:3000/auth/dev/"
+    dev: "http://127.0.0.1:3000/auth/dev/",
 };
diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts
index 8bcd50b..6fc5cf8 100644
--- a/src/middleware/role-checker.ts
+++ b/src/middleware/role-checker.ts
@@ -1,12 +1,14 @@
 import { NextFunction, Request, Response } from "express";
 import { JwtPayload, Role } from "../services/auth/auth-models";
-import {z} from "zod";
+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<typeof Role>[], weakVerification: boolean = false){
+export default function RoleChecker(
+    requiredRoles: z.infer<typeof Role>[],
+    weakVerification: boolean = false
+) {
     return function (req: Request, res: Response, next: NextFunction) {
         const jwt = req.headers.authorization;
 
@@ -15,12 +17,15 @@ export default function RoleChecker (requiredRoles: z.infer<typeof Role>[], weak
                 next();
             }
 
-            return res.status(StatusCodes.BAD_REQUEST).json({error: "NoJWT"})
+            return res.status(StatusCodes.BAD_REQUEST).json({ error: "NoJWT" });
         }
-    
+
         try {
             console.log("in");
-            const payloadData = jsonwebtoken.verify(jwt, Config.JWT_SIGNING_SECRET);
+            const payloadData = jsonwebtoken.verify(
+                jwt,
+                Config.JWT_SIGNING_SECRET
+            );
             const payload = JwtPayload.parse(payloadData);
             res.locals.payload = payload;
 
@@ -30,7 +35,7 @@ export default function RoleChecker (requiredRoles: z.infer<typeof Role>[], weak
             if (weakVerification) {
                 next();
             }
-            
+
             if (requiredRoles.length == 0) {
                 next();
             }
@@ -55,9 +60,8 @@ export default function RoleChecker (requiredRoles: z.infer<typeof Role>[], weak
             }
 
             throw error;
-            
         } catch (error) {
-            next(error)
+            next(error);
         }
-    }
-}
\ No newline at end of file
+    };
+}
diff --git a/src/services/auth/auth-models.ts b/src/services/auth/auth-models.ts
index a34b6db..bda56ab 100644
--- a/src/services/auth/auth-models.ts
+++ b/src/services/auth/auth-models.ts
@@ -2,7 +2,7 @@ import { z } from "zod";
 
 export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
-export const JwtPayload =  z.object({
+export const JwtPayload = z.object({
     userId: z.string(),
     roles: Role.array(),
-})
\ No newline at end of file
+});
diff --git a/src/services/auth/auth-router.ts b/src/services/auth/auth-router.ts
index 7b8bc9a..8ab4301 100644
--- a/src/services/auth/auth-router.ts
+++ b/src/services/auth/auth-router.ts
@@ -42,16 +42,23 @@ authRouter.get(
     async function (req, res, next) {
         // Authentication failed - redirect to login
         if (req.user == undefined) {
-            return res.redirect(`/auth/login/${req.params.DEVICE}`)
+            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}`;
+            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);
@@ -61,6 +68,6 @@ authRouter.get(
 
 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 b688077..9434d11 100644
--- a/src/services/auth/auth-schema.ts
+++ b/src/services/auth/auth-schema.ts
@@ -2,7 +2,6 @@ import { Schema } from "mongoose";
 import { z } from "zod";
 import { Role } from "./auth-models";
 
-
 export const RoleValidator = z.object({
     userId: z.coerce.string().regex(/user[0-9]*/),
     name: z.coerce.string(),
diff --git a/src/services/auth/auth-utils.ts b/src/services/auth/auth-utils.ts
index fbb4e11..b4a3c2e 100644
--- a/src/services/auth/auth-utils.ts
+++ b/src/services/auth/auth-utils.ts
@@ -28,12 +28,14 @@ export function createGoogleStrategy(device: string) {
     );
 }
 
-
 export async function getJwtPayloadFromDatabase(userId: string) {
-    const payload = await Database.ROLES.findOne({userId: userId}).select(["userId", "roles"]);
+    const payload = await Database.ROLES.findOne({ userId: userId }).select([
+        "userId",
+        "roles",
+    ]);
     if (!payload) {
         throw new Error("NoUserFound");
     }
-    
+
     return payload;
-}
\ No newline at end of file
+}

From 7aab40db50a455a08606b5360ff8dea7447c09a6 Mon Sep 17 00:00:00 2001
From: Aydan Pirani <aydanpirani@gmail.com>
Date: Thu, 11 Apr 2024 13:27:13 -0500
Subject: [PATCH 9/9] Fixed naming conventions

---
 src/app.ts                       | 1 -
 src/middleware/role-checker.ts   | 6 +++---
 src/services/auth/auth-models.ts | 2 +-
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/src/app.ts b/src/app.ts
index bfd276b..397879a 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -28,7 +28,6 @@ app.use("/event", eventRouter);
 app.use("/subscription", subscriptionRouter);
 
 app.get("/status", (_, res) => {
-    console.log(StatusCodes.OK);
     return res.status(StatusCodes.OK).send("API is alive!");
 });
 
diff --git a/src/middleware/role-checker.ts b/src/middleware/role-checker.ts
index 6fc5cf8..4c672a4 100644
--- a/src/middleware/role-checker.ts
+++ b/src/middleware/role-checker.ts
@@ -1,5 +1,5 @@
 import { NextFunction, Request, Response } from "express";
-import { JwtPayload, Role } from "../services/auth/auth-models";
+import { JwtPayloadValidator, Role } from "../services/auth/auth-models";
 import { z } from "zod";
 import jsonwebtoken from "jsonwebtoken";
 import { Config } from "../config";
@@ -21,12 +21,12 @@ export default function RoleChecker(
         }
 
         try {
-            console.log("in");
             const payloadData = jsonwebtoken.verify(
                 jwt,
                 Config.JWT_SIGNING_SECRET
             );
-            const payload = JwtPayload.parse(payloadData);
+
+            const payload = JwtPayloadValidator.parse(payloadData);
             res.locals.payload = payload;
 
             const error = new Error("InvalidRoles");
diff --git a/src/services/auth/auth-models.ts b/src/services/auth/auth-models.ts
index bda56ab..2dbed28 100644
--- a/src/services/auth/auth-models.ts
+++ b/src/services/auth/auth-models.ts
@@ -2,7 +2,7 @@ import { z } from "zod";
 
 export const Role = z.enum(["USER", "ADMIN", "CORPORATE"]);
 
-export const JwtPayload = z.object({
+export const JwtPayloadValidator = z.object({
     userId: z.string(),
     roles: Role.array(),
 });