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==