From 23ea8d7245d930ce22534fa5db11f37c2daa46e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=AF=BC=EC=A4=80?= <97105309+joonamin@users.noreply.github.com> Date: Sun, 15 Dec 2024 21:16:14 +0900 Subject: [PATCH] feat: oauth2 kakao login (#39) Co-authored-by: Kuwon Sebastian Na Co-authored-by: Jaehyun Yoon --- apps/server/package.json | 3 + apps/server/src/config/index.ts | 2 +- apps/server/src/controllers/v1/index.ts | 2 + apps/server/src/controllers/v1/user.ts | 49 ++++++++ apps/server/src/models/user.ts | 32 ++++- apps/server/src/server.ts | 1 + apps/server/src/types/errors/auth.ts | 25 ++++ apps/server/src/types/errors/index.ts | 1 + apps/server/src/types/provider/kakao.ts | 27 +++++ apps/server/src/utils/auth.ts | 27 +++++ pnpm-lock.yaml | 150 +++++++++++++++++++++++- 11 files changed, 312 insertions(+), 7 deletions(-) create mode 100644 apps/server/src/controllers/v1/user.ts create mode 100644 apps/server/src/types/errors/auth.ts create mode 100644 apps/server/src/types/provider/kakao.ts create mode 100644 apps/server/src/utils/auth.ts diff --git a/apps/server/package.json b/apps/server/package.json index da4829f..1fe980c 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -22,8 +22,10 @@ "express-winston": "^4.2.0", "helmet": "^8.0.0", "hpp": "^0.2.3", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.8.1", "mongoose-paginate-v2": "^1.8.5", + "node-fetch": "^3.3.2", "winston": "^3.16.0", "winston-daily-rotate-file": "^5.0.0" }, @@ -37,6 +39,7 @@ "@types/express": "^5.0.0", "@types/hpp": "^0.2.6", "@types/jest": "^29.5.14", + "@types/jsonwebtoken": "^9.0.7", "nodemon": "^3.1.7", "swc-node": "^1.0.0" } diff --git a/apps/server/src/config/index.ts b/apps/server/src/config/index.ts index f2d4de9..5e1e0d3 100644 --- a/apps/server/src/config/index.ts +++ b/apps/server/src/config/index.ts @@ -2,6 +2,6 @@ import { config } from 'dotenv' config() -export const { NODE_ENV, DB_URI, DB_NAME } = process.env +export const { NODE_ENV, DB_URI, DB_NAME, JWT_SECRET } = process.env export const PORT = Number.parseInt(process.env.PORT) || 5000 diff --git a/apps/server/src/controllers/v1/index.ts b/apps/server/src/controllers/v1/index.ts index cfd31a7..c3ba51a 100644 --- a/apps/server/src/controllers/v1/index.ts +++ b/apps/server/src/controllers/v1/index.ts @@ -1,9 +1,11 @@ import enquiries from './enquiries' import events from './events' import schedules from './schedules' +import user from './user' export default { enquiries, events, schedules, + user, } diff --git a/apps/server/src/controllers/v1/user.ts b/apps/server/src/controllers/v1/user.ts new file mode 100644 index 0000000..42c0bbd --- /dev/null +++ b/apps/server/src/controllers/v1/user.ts @@ -0,0 +1,49 @@ +import asyncify from 'express-asyncify' +import express, { Request, Response } from 'express' +import { IUserInfo, JWTProvider, OAuthPayload } from '@/utils/auth' +import { User, UserModel } from '@/models/user' +import Kakao from '@/types/provider/kakao' +import { KakaoLoginFailedException } from '@/types/errors' + +const router = asyncify(express.Router()) + +router.post('/login', async (req: Request, res: Response) => { + const oauthPayload: OAuthPayload = req.body + + // todo: refactor this code blocks after introducing new oauth provider + // ==> split the code as service, and adding handler for controller logic + if (oauthPayload.provider === 'KAKAO') { + const userInfo: IUserInfo = await Kakao.getUserInfo(oauthPayload) + const user: User = await UserModel.findByProviderId(userInfo.providerId) + if (!user) throw new KakaoLoginFailedException(new Error(`not found ${userInfo.providerId}`)) + const jwt = JWTProvider.issueJwt(user) + await UserModel.setProviderCredentials({ + providerId: userInfo.providerId, + providerAccessToken: oauthPayload.accessToken, + providerRefreshToken: oauthPayload.refreshToken, + }) + res.status(200).json({ + accessToken: jwt, + }) + } +}) + +router.post('/register', async (req: Request, res: Response) => { + const oauthRegisterPayload: OAuthPayload = req.body + + if (oauthRegisterPayload.provider === 'KAKAO') { + const registerInfo: IUserInfo = await Kakao.getUserInfo(oauthRegisterPayload) + const user: User = await UserModel.createUser(registerInfo) + const jwt = JWTProvider.issueJwt(user) + await UserModel.setProviderCredentials({ + providerId: registerInfo.providerId, + providerAccessToken: oauthRegisterPayload.accessToken, + providerRefreshToken: oauthRegisterPayload.refreshToken, + }) + res.status(200).json({ + accessToken: jwt, + }) + } +}) + +export default router diff --git a/apps/server/src/models/user.ts b/apps/server/src/models/user.ts index 4f9d9bb..5e9f356 100644 --- a/apps/server/src/models/user.ts +++ b/apps/server/src/models/user.ts @@ -1,6 +1,7 @@ import mongoose from 'mongoose' import { TimeStamps } from '@typegoose/typegoose/lib/defaultClasses' -import { getModelForClass, prop } from '@typegoose/typegoose' +import { getModelForClass, prop, ReturnModelType } from '@typegoose/typegoose' +import { IUserCredentials, IUserInfo } from '@/utils/auth' export class User extends TimeStamps { public _id: mongoose.Types.ObjectId @@ -13,6 +14,35 @@ export class User extends TimeStamps { @prop() public providerId: string + + @prop() + public providerAccessToken: string + + @prop() + public providerRefreshToken: string + + public toJSON() { + return { + _id: this._id, + nickname: this.nickname, + provider: this.provider, + } + } + + public static async findByProviderId(this: ReturnModelType, providerId: string) { + return await this.findOne({ providerId: providerId }).exec() + } + + public static async createUser(this: ReturnModelType, info: IUserInfo) { + return await this.create(info) + } + + public static async setProviderCredentials(this: ReturnModelType, credentials: IUserCredentials) { + return await this.findOneAndUpdate( + { providerId: credentials.providerId }, + { providerAccessToken: credentials.providerAccessToken, providerRefreshToken: credentials.providerRefreshToken }, + ).exec() + } } export const UserModel = getModelForClass(User) diff --git a/apps/server/src/server.ts b/apps/server/src/server.ts index 2993a22..cc8fe4f 100644 --- a/apps/server/src/server.ts +++ b/apps/server/src/server.ts @@ -36,6 +36,7 @@ export default class Server { this.app.use('/v1/enquiries', controllers.v1.enquiries) this.app.use('/v1/events', controllers.v1.events) this.app.use('/v1/schedules', controllers.v1.schedules) + this.app.use('/v1/user', controllers.v1.user) } setPostMiddleware() { diff --git a/apps/server/src/types/errors/auth.ts b/apps/server/src/types/errors/auth.ts new file mode 100644 index 0000000..c5bea70 --- /dev/null +++ b/apps/server/src/types/errors/auth.ts @@ -0,0 +1,25 @@ +import { APIError } from '@/types/errors/error' + +export class OAuthUserInfoException extends APIError { + constructor(cause: Error | string = null) { + super(422, 4220, 'auth http client exception', cause) + Object.setPrototypeOf(this, OAuthUserInfoException.prototype) + Error.captureStackTrace(this, OAuthUserInfoException) + } +} + +export class KakaoLoginFailedException extends APIError { + constructor(cause: Error | string = null) { + super(404, 4040, 'Failed to login with kakao login information', cause) + Object.setPrototypeOf(this, KakaoLoginFailedException.prototype) + Error.captureStackTrace(this, KakaoLoginFailedException) + } +} + +export class KakaoRegisterFailedException extends APIError { + constructor(cause: Error | string = null) { + super(400, 4000, 'Failed to register user information', cause) + Object.setPrototypeOf(this, KakaoRegisterFailedException) + Error.captureStackTrace(this, KakaoRegisterFailedException) + } +} diff --git a/apps/server/src/types/errors/index.ts b/apps/server/src/types/errors/index.ts index 2e02eb8..063ccc7 100644 --- a/apps/server/src/types/errors/index.ts +++ b/apps/server/src/types/errors/index.ts @@ -1,2 +1,3 @@ export * from './error' export * from './server' +export * from './auth' diff --git a/apps/server/src/types/provider/kakao.ts b/apps/server/src/types/provider/kakao.ts new file mode 100644 index 0000000..9e660e2 --- /dev/null +++ b/apps/server/src/types/provider/kakao.ts @@ -0,0 +1,27 @@ +import { OAuthUserInfoException } from '@/types/errors' +import { OAuthPayload } from '@/utils/auth' +import fetch from 'node-fetch' + +export class Kakao { + public static async getUserInfo(payload: OAuthPayload) { + const response = await fetch('https://kapi.kakao.com/v2/user/me', { + method: 'GET', + headers: { + Authorization: `Bearer ${payload.accessToken}`, + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }, + }).catch(e => { + throw new OAuthUserInfoException(e) + }) + + const data = await response.json() + + return { + nickname: data.kakao_account.profile.nickname, + provider: 'KAKAO', + providerId: data.id, + } + } +} + +export default Kakao diff --git a/apps/server/src/utils/auth.ts b/apps/server/src/utils/auth.ts new file mode 100644 index 0000000..4874cfa --- /dev/null +++ b/apps/server/src/utils/auth.ts @@ -0,0 +1,27 @@ +import { User } from '@/models/user' +import jwt from 'jsonwebtoken' +import { JWT_SECRET } from '@/config' + +export interface OAuthPayload { + accessToken: string + refreshToken: string + provider: string +} + +export interface IUserInfo { + nickname: string + provider: string + providerId: string +} + +export interface IUserCredentials { + providerId: string + providerAccessToken: string + providerRefreshToken: string +} + +export class JWTProvider { + public static issueJwt(user: User): string { + return jwt.sign({ issuer: 'FIENMEE', user: user }, JWT_SECRET, { expiresIn: '30m' }) + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d965837..495228c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -172,12 +172,18 @@ importers: hpp: specifier: ^0.2.3 version: 0.2.3 + jsonwebtoken: + specifier: ^9.0.2 + version: 9.0.2 mongoose: specifier: ^8.8.1 version: 8.8.1(socks@2.8.3) mongoose-paginate-v2: specifier: ^1.8.5 version: 1.8.5 + node-fetch: + specifier: ^3.3.2 + version: 3.3.2 winston: specifier: ^3.16.0 version: 3.16.0 @@ -212,6 +218,9 @@ importers: '@types/jest': specifier: ^29.5.14 version: 29.5.14 + '@types/jsonwebtoken': + specifier: ^9.0.7 + version: 9.0.7 nodemon: specifier: ^3.1.7 version: 3.1.7 @@ -1903,6 +1912,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/jsonwebtoken@9.0.7': + resolution: {integrity: sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -2487,6 +2499,9 @@ packages: resolution: {integrity: sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==} engines: {node: '>=16.20.1'} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -2804,6 +2819,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -2961,6 +2980,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -3396,6 +3418,10 @@ packages: fecha@4.2.3: resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -3488,6 +3514,10 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -4246,10 +4276,20 @@ packages: jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.1: + resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + kareem@2.6.3: resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} engines: {node: '>=12.0.0'} @@ -4325,9 +4365,30 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.throttle@4.1.1: resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} @@ -4664,6 +4725,10 @@ packages: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + node-fetch@2.7.0: resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} engines: {node: 4.x || >=6.0.0} @@ -4673,6 +4738,10 @@ packages: encoding: optional: true + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-forge@1.3.1: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} @@ -6046,6 +6115,10 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -8071,9 +8144,7 @@ snapshots: transitivePeerDependencies: - '@babel/core' - '@babel/preset-env' - - bufferutil - supports-color - - utf-8-validate '@react-native/normalize-colors@0.76.2': {} @@ -8388,6 +8459,10 @@ snapshots: '@types/json5@0.0.29': {} + '@types/jsonwebtoken@9.0.7': + dependencies: + '@types/node': 20.17.6 + '@types/keyv@3.1.4': dependencies: '@types/node': 20.17.6 @@ -9129,6 +9204,8 @@ snapshots: bson@6.9.0: {} + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} buffer@5.7.1: @@ -9483,6 +9560,8 @@ snapshots: damerau-levenshtein@1.0.8: {} + data-uri-to-buffer@4.0.1: {} + data-uri-to-buffer@6.0.2: {} data-urls@3.0.2: @@ -9610,6 +9689,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + ee-first@1.1.1: {} electron-to-chromium@1.5.55: {} @@ -9816,7 +9899,7 @@ snapshots: debug: 4.3.7(supports-color@5.5.0) enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -9829,7 +9912,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -9864,7 +9947,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.12.2(eslint@8.57.1)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -10204,6 +10287,11 @@ snapshots: fecha@4.2.3: {} + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -10313,6 +10401,10 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + forwarded@0.2.0: {} fresh@0.5.2: {} @@ -11322,6 +11414,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + 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.1.1 + ms: 2.1.3 + semver: 7.6.3 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -11329,6 +11434,17 @@ snapshots: object.assign: 4.1.5 object.values: 1.2.0 + jwa@1.4.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.1 + safe-buffer: 5.2.1 + kareem@2.6.3: {} keyv@4.5.4: @@ -11408,8 +11524,22 @@ snapshots: lodash.debounce@4.0.8: {} + 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.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.throttle@4.1.1: {} lodash@4.17.21: {} @@ -11829,10 +11959,18 @@ snapshots: dependencies: minimatch: 3.1.2 + node-domexception@1.0.0: {} + node-fetch@2.7.0: dependencies: whatwg-url: 5.0.0 + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-forge@1.3.1: {} node-int64@0.4.0: {} @@ -13367,6 +13505,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-streams-polyfill@3.3.3: {} + webidl-conversions@3.0.1: {} webidl-conversions@7.0.0: {}