diff --git a/package.json b/package.json
index 24e6ef9..aad14dd 100644
--- a/package.json
+++ b/package.json
@@ -12,8 +12,10 @@
     "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",
         "@typescript-eslint/eslint-plugin": "^6.4.0",
         "eslint": "8.2.0",
         "eslint-config-airbnb": "19.0.4",
@@ -35,8 +37,11 @@
         "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",
+        "passport-google-oauth20": "^2.0.0",
         "tsx": "^4.5.0",
         "typescript": "*",
         "zod": "^3.22.4"
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/config.ts b/src/config.ts
index ed601c8..38aab55 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -15,4 +15,18 @@ 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<string, string> = {
+    web: "https://www.google.com/",
+    dev: "http://127.0.0.1:3000/auth/dev/",
 };
diff --git a/src/database.ts b/src/database.ts
index 5656b48..ebe9031 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";
 import { EventSchema, EventValidator } from "./services/events/events-schema";
 import {
     SubscriptionValidator,
@@ -36,7 +36,7 @@ function initializeModel(
 
 // Example usage
 export const Database = {
-    ROLES: initializeModel("roles", RoleSchema, RoleInfo),
+    ROLES: initializeModel("roles", RoleSchema, RoleValidator),
     EVENTS: initializeModel("events", EventSchema, EventValidator),
     SUBSCRIPTION: initializeModel(
         "subscription",
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<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 {
+            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/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 fe85ca2..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 { Database } from "../../database";
+import passport from "passport";
+import { Config, DeviceRedirects } from "../../config";
 import { StatusCodes } from "http-status-codes";
-import { Role } from "./auth-schema";
-import { createId } from "@paralleldrive/cuid2";
+import { Strategy as GoogleStrategy, Profile } from "passport-google-oauth20";
+import { createGoogleStrategy, getJwtPayloadFromDatabase } from "./auth-utils";
+import jsonwebtoken from "jsonwebtoken";
+
+const authStrategies: Record<string, GoogleStrategy> = {};
 
 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/utilities.ts b/src/utilities.ts
index ffb1dd8..06a4767 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..33f43b2 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==
@@ -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"
@@ -304,6 +311,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 +635,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"
@@ -653,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"
@@ -879,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"
@@ -1872,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"
@@ -1882,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"
@@ -1914,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"
@@ -2108,6 +2232,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 +2349,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 +2406,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"
@@ -2425,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==
@@ -2783,6 +2949,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 +2986,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==