refactor: auth module to nestjs

This commit is contained in:
Ahmed Bouhuolia
2025-03-30 05:20:50 +02:00
parent 85946d3161
commit 682be715ae
13 changed files with 419 additions and 64 deletions

View File

@@ -24,7 +24,6 @@
"@aws-sdk/s3-request-presigner": "^3.583.0",
"@bigcapital/email-components": "*",
"@bigcapital/pdf-templates": "*",
"@bigcapital/server": "*",
"@bigcapital/utils": "*",
"@casl/ability": "^5.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
@@ -49,6 +48,7 @@
"async": "^3.2.0",
"async-mutex": "^0.5.0",
"axios": "^1.6.0",
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
"bull": "^4.16.3",

View File

@@ -1,25 +1,74 @@
import { Body, Controller, Post, Request } from '@nestjs/common';
import { Body, Controller, Param, Post, Request } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBody, ApiParam } from '@nestjs/swagger';
import { PublicRoute } from './Jwt.guard';
import { AuthenticationApplication } from './AuthApplication.sevice';
import { AuthSignupDto } from './dtos/AuthSignup.dto';
import { AuthSigninDto } from './dtos/AuthSignin.dto';
@ApiTags('Auth')
@Controller('/auth')
@PublicRoute()
export class AuthController {
constructor(private readonly authApp: AuthenticationApplication) {}
@Post('/signin')
@ApiOperation({ summary: 'Sign in a user' })
@ApiBody({ type: AuthSigninDto })
signin(@Request() req: Request, @Body() signinDto: AuthSigninDto) {
return this.authApp.signIn(signinDto);
}
@Post('/signup')
@ApiOperation({ summary: 'Sign up a new user' })
@ApiBody({ type: AuthSignupDto })
signup(@Request() req: Request, @Body() signupDto: AuthSignupDto) {
this.authApp.signUp(signupDto);
return this.authApp.signUp(signupDto);
}
@Post('/signup/confirm')
@ApiOperation({ summary: 'Confirm user signup' })
@ApiBody({
schema: {
type: 'object',
properties: {
email: { type: 'string', example: 'user@example.com' },
token: { type: 'string', example: 'confirmation-token' },
},
},
})
signupConfirm(@Body('email') email: string, @Body('token') token: string) {
return this.authApp.signUpConfirm(email, token);
}
@Post('/send_reset_password')
@ApiOperation({ summary: 'Send reset password email' })
@ApiBody({
schema: {
type: 'object',
properties: {
email: { type: 'string', example: 'user@example.com' },
},
},
})
sendResetPassword(@Body('email') email: string) {
return this.authApp.sendResetPassword(email);
}
@Post('/reset_password/:token')
@ApiOperation({ summary: 'Reset password using token' })
@ApiParam({ name: 'token', description: 'Reset password token' })
@ApiBody({
schema: {
type: 'object',
properties: {
password: { type: 'string', example: 'new-password' },
},
},
})
resetPassword(
@Param('token') token: string,
@Body('password') password: string,
) {
return this.authApp.resetPassword(token, password);
}
}

View File

@@ -10,6 +10,12 @@ import { AuthSignupConfirmResendService } from './commands/AuthSignupConfirmRese
import { AuthSignupConfirmService } from './commands/AuthSignupConfirm.service';
import { AuthSignupService } from './commands/AuthSignup.service';
import { AuthSigninService } from './commands/AuthSignin.service';
import { RegisterTenancyModel } from '../Tenancy/TenancyModels/Tenancy.module';
import { PasswordReset } from './models/PasswordReset';
import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module';
import { AuthenticationMailMesssages } from './AuthMailMessages.esrvice';
const models = [RegisterTenancyModel(PasswordReset)];
@Module({
controllers: [AuthController],
@@ -18,7 +24,10 @@ import { AuthSigninService } from './commands/AuthSignin.service';
secret: 'asdfasdfasdf',
signOptions: { expiresIn: '60s' },
}),
TenantDBManagerModule,
...models,
],
exports: [...models],
providers: [
AuthService,
JwtStrategy,
@@ -29,6 +38,7 @@ import { AuthSigninService } from './commands/AuthSignin.service';
AuthSignupConfirmService,
AuthSignupService,
AuthSigninService,
AuthenticationMailMesssages,
],
})
export class AuthModule {}

View File

@@ -0,0 +1,61 @@
import { Injectable } from '@nestjs/common';
import { SystemUser } from '../System/models/SystemUser';
import { ModelObject } from 'objection';
import { ConfigService } from '@nestjs/config';
import { Mail } from '../Mail/Mail';
@Injectable()
export class AuthenticationMailMesssages {
constructor(private readonly configService: ConfigService) {}
/**
* Sends reset password message.
* @param {ISystemUser} user - The system user.
* @param {string} token - Reset password token.
* @returns {Mail}
*/
sendResetPasswordMessage(user: ModelObject<SystemUser>, token: string) {
const baseURL = this.configService.get('baseURL');
return new Mail()
.setSubject('Bigcapital - Password Reset')
.setView('mail/ResetPassword.html')
.setTo(user.email)
.setAttachments([
{
filename: 'bigcapital.png',
path: `${global.__views_dir}/images/bigcapital.png`,
cid: 'bigcapital_logo',
},
])
.setData({
resetPasswordUrl: `${baseURL}/auth/reset_password/${token}`,
first_name: user.firstName,
last_name: user.lastName,
});
}
/**
* Sends signup verification mail.
* @param {string} email - Email address
* @param {string} fullName - User name.
* @param {string} token - Verification token.
* @returns {Mail}
*/
sendSignupVerificationMail(email: string, fullName: string, token: string) {
const baseURL = this.configService.get('baseURL');
const verifyUrl = `${baseURL}/auth/email_confirmation?token=${token}&email=${email}`;
return new Mail()
.setSubject('Bigcapital - Verify your email')
.setView('mail/SignupVerifyEmail.html')
.setTo(email)
.setAttachments([
{
filename: 'bigcapital.png',
path: `${global.__views_dir}/images/bigcapital.png`,
cid: 'bigcapital_logo',
},
])
.setData({ verifyUrl, fullName });
}
}

View File

@@ -1,10 +1,88 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Inject, Injectable } from '@nestjs/common';
import { SystemUser } from '@/modules/System/models/SystemUser';
import { PasswordReset } from '../models/PasswordReset';
import { ServiceError } from '@/modules/Items/ServiceError';
import { ERRORS } from '../Auth.constants';
import { hashPassword } from '../Auth.utils';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
import { IAuthResetedPasswordEventPayload } from '../Auth.interfaces';
@Injectable()
export class AuthResetPasswordService {
resetPassword(token: string, password: string): Promise<{ message: string }> {
return new Promise((resolve) => {
resolve({ message: 'Reset password link sent to your email' });
});
/**
* @param {ConfigService} configService - Config service.
* @param {EventEmitter2} eventEmitter - Event emitter.
* @param {typeof SystemUser} systemUserModel
* @param {typeof PasswordReset} passwordResetModel - Reset password model.
*/
constructor(
private readonly configService: ConfigService,
private readonly eventEmitter: EventEmitter2,
@Inject(SystemUser.name)
private readonly systemUserModel: typeof SystemUser,
@Inject(PasswordReset.name)
private readonly passwordResetModel: typeof PasswordReset,
) {}
/**
* Resets a user password from given token.
* @param {string} token - Password reset token.
* @param {string} password - New Password.
* @return {Promise<void>}
*/
public async resetPassword(token: string, password: string): Promise<void> {
// Finds the password reset token.
const tokenModel = await this.passwordResetModel
.query()
.findOne('token', token);
// In case the password reset token not found throw token invalid error..
if (!tokenModel) {
throw new ServiceError(ERRORS.TOKEN_INVALID);
}
const resetPasswordSeconds = this.configService.get('resetPasswordSeconds');
// Different between tokne creation datetime and current time.
if (moment().diff(tokenModel.createdAt, 'seconds') > resetPasswordSeconds) {
// Deletes the expired token by expired token email.
await this.deletePasswordResetToken(tokenModel.email);
throw new ServiceError(ERRORS.TOKEN_EXPIRED);
}
const user = await this.systemUserModel
.query()
.findOne({ email: tokenModel.email });
if (!user) {
throw new ServiceError(ERRORS.USER_NOT_FOUND);
}
const hashedPassword = await hashPassword(password);
await this.systemUserModel
.query()
.findById(user.id)
.update({ password: hashedPassword });
// Deletes the used token.
await this.deletePasswordResetToken(tokenModel.email);
// Triggers `onResetPassword` event.
await this.eventEmitter.emitAsync(events.auth.resetPassword, {
user,
token,
password,
} as IAuthResetedPasswordEventPayload);
}
/**
* Deletes the password reset token by the given email.
* @param {string} email
* @returns {Promise}
*/
private async deletePasswordResetToken(email: string) {
return PasswordReset.query().where('email', email).delete();
}
}

View File

@@ -1,10 +1,70 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import * as uniqid from 'uniqid';
import {
IAuthSendedResetPassword,
IAuthSendingResetPassword,
} from '../Auth.interfaces';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PasswordReset } from '../models/PasswordReset';
import { SystemUser } from '@/modules/System/models/SystemUser';
import { events } from '@/common/events/events';
@Injectable()
export class AuthSendResetPasswordService {
sendResetPassword(email: string): Promise<{ message: string }> {
return new Promise((resolve) => {
resolve({ message: 'Reset password link sent to your email' });
/**
* @param {EventEmitter2} eventPublisher - Event emitter.
* @param {typeof PasswordReset} resetPasswordModel - Password reset model.
* @param {typeof SystemUser} systemUserModel - System user model.
*/
constructor(
private readonly eventPublisher: EventEmitter2,
@Inject(PasswordReset.name)
private readonly resetPasswordModel: typeof PasswordReset,
@Inject(SystemUser.name)
private readonly systemUserModel: typeof SystemUser,
) {}
/**
* Sends the given email reset password email.
* @param {string} email - Email address.
*/
async sendResetPassword(email: string): Promise<void> {
const user = await this.systemUserModel
.query()
.findOne({ email })
.throwIfNotFound();
const token: string = uniqid();
// Triggers sending reset password event.
await this.eventPublisher.emitAsync(events.auth.sendingResetPassword, {
user,
token,
} as IAuthSendingResetPassword);
// Delete all stored tokens of reset password that associate to the give email.
this.deletePasswordResetToken(email);
// Creates a new password reset row with unique token.
const passwordReset = await this.resetPasswordModel.query().insert({
email,
token,
});
// Triggers sent reset password event.
await this.eventPublisher.emitAsync(events.auth.sendResetPassword, {
user,
token,
} as IAuthSendedResetPassword);
}
/**
* Deletes the password reset token by the given email.
* @param {string} email
* @returns {Promise}
*/
private async deletePasswordResetToken(email: string) {
return this.resetPasswordModel.query().where('email', email).delete();
}
}

View File

@@ -1,4 +1,5 @@
import crypto from 'crypto';
import * as crypto from 'crypto';
import * as moment from 'moment';
import { events } from '@/common/events/events';
import { ServiceError } from '@/modules/Items/ServiceError';
import { SystemUser } from '@/modules/System/models/SystemUser';

View File

@@ -0,0 +1,22 @@
import { SystemModel } from '@/modules/System/models/SystemModel';
export class PasswordReset extends SystemModel {
readonly email: string;
readonly token: string;
readonly createdAt: Date;
/**
* Table name
*/
static get tableName() {
return 'password_resets';
}
/**
* Timestamps columns.
*/
get timestamps() {
return ['createdAt'];
}
}

View File

@@ -103,8 +103,8 @@ export class PlanSubscription extends mixin(SystemModel) {
* Relations mappings.
*/
static get relationMappings() {
const Tenant = require('system/models/Tenant');
const Plan = require('system/models/Subscriptions/Plan');
const { TenantModel } = require('../../System/models/TenantModel');
const { Plan } = require('./Plan');
return {
/**
@@ -112,7 +112,7 @@ export class PlanSubscription extends mixin(SystemModel) {
*/
tenant: {
relation: Model.BelongsToOneRelation,
modelClass: Tenant.default,
modelClass: TenantModel,
join: {
from: 'subscription_plan_subscriptions.tenantId',
to: 'tenants.id',
@@ -124,7 +124,7 @@ export class PlanSubscription extends mixin(SystemModel) {
*/
plan: {
relation: Model.BelongsToOneRelation,
modelClass: Plan.default,
modelClass: Plan,
join: {
from: 'subscription_plan_subscriptions.planId',
to: 'subscription_plans.id',

View File

@@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common';
import { Knex } from 'knex';
import { Global, Inject, Injectable } from '@nestjs/common';
import uniqid from 'uniqid';
import * as uniqid from 'uniqid';
import { TenantRepository as TenantBaseRepository } from '@/common/repository/TenantRepository';
import { SystemKnexConnection } from '../SystemDB/SystemDB.constants';
import { TenantModel } from '../models/TenantModel';

164
pnpm-lock.yaml generated
View File

@@ -496,9 +496,6 @@ importers:
'@bigcapital/pdf-templates':
specifier: '*'
version: link:../../shared/pdf-templates
'@bigcapital/server':
specifier: '*'
version: link:../server
'@bigcapital/utils':
specifier: '*'
version: link:../../shared/bigcapital-utils
@@ -571,6 +568,9 @@ importers:
axios:
specifier: ^1.6.0
version: 1.7.7
bcrypt:
specifier: ^5.1.1
version: 5.1.1
bcryptjs:
specifier: ^2.4.3
version: 2.4.3
@@ -7229,6 +7229,24 @@ packages:
resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==}
engines: {node: '>=8'}
/@mapbox/node-pre-gyp@1.0.11:
resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
hasBin: true
dependencies:
detect-libc: 2.0.3
https-proxy-agent: 5.0.1
make-dir: 3.1.0
node-fetch: 2.6.7
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
semver: 7.6.3
tar: 6.2.1
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/@mdx-js/react@2.3.0(react@18.3.1):
resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==}
peerDependencies:
@@ -7751,7 +7769,7 @@ packages:
node-gyp: 10.1.0
node-gyp-build: 4.8.1
prebuildify: 6.0.1
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
dev: false
@@ -7786,7 +7804,7 @@ packages:
pretty-bytes: 5.6.0
request-ip: 3.3.0
ringbufferjs: 2.0.0
semver: 7.6.2
semver: 7.6.3
sync-request: 6.1.0
unescape: 1.0.1
unescape-js: 1.1.4
@@ -7844,7 +7862,7 @@ packages:
resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
semver: 7.6.2
semver: 7.6.3
/@npmcli/git@5.0.7:
resolution: {integrity: sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==}
@@ -7856,7 +7874,7 @@ packages:
proc-log: 4.2.0
promise-inflight: 1.0.1
promise-retry: 2.0.1
semver: 7.6.2
semver: 7.6.3
which: 4.0.0
transitivePeerDependencies:
- bluebird
@@ -10744,7 +10762,7 @@ packages:
prompts: 2.4.2
puppeteer-core: 2.1.1
read-pkg-up: 7.0.1
semver: 7.6.2
semver: 7.6.3
simple-update-notifier: 2.0.0
strip-json-comments: 3.1.1
tempy: 1.0.1
@@ -10962,7 +10980,7 @@ packages:
pretty-hrtime: 1.0.3
prompts: 2.4.2
read-pkg-up: 7.0.1
semver: 7.6.2
semver: 7.6.3
serve-favicon: 2.5.0
telejson: 7.2.0
tiny-invariant: 1.3.3
@@ -11078,7 +11096,7 @@ packages:
memoizerific: 1.11.3
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
semver: 7.6.2
semver: 7.6.3
store2: 2.14.3
telejson: 7.2.0
ts-dedent: 2.2.0
@@ -12876,7 +12894,7 @@ packages:
graphemer: 1.4.0
ignore: 5.3.1
natural-compare-lite: 1.4.0
semver: 7.6.2
semver: 7.6.3
tsutils: 3.21.0(typescript@4.9.5)
typescript: 4.9.5
transitivePeerDependencies:
@@ -13117,7 +13135,7 @@ packages:
glob: 7.2.3
is-glob: 4.0.3
lodash: 4.17.21
semver: 7.6.2
semver: 7.6.3
tsutils: 3.21.0(typescript@4.9.5)
typescript: 4.9.5
transitivePeerDependencies:
@@ -13223,7 +13241,7 @@ packages:
'@typescript-eslint/typescript-estree': 5.62.0(typescript@3.9.10)
eslint: 8.57.0
eslint-scope: 5.1.1
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
- typescript
@@ -13243,7 +13261,7 @@ packages:
'@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5)
eslint: 8.57.0
eslint-scope: 5.1.1
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
- typescript
@@ -13263,7 +13281,7 @@ packages:
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.6.3)
eslint: 9.13.0
eslint-scope: 5.1.1
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
- typescript
@@ -13751,6 +13769,10 @@ packages:
deprecated: Use your platform's native atob() and btoa() methods instead
dev: false
/abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
dev: false
/abbrev@2.0.0:
resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -14190,11 +14212,19 @@ packages:
/aproba@2.0.0:
resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
dev: true
/archy@1.0.0:
resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
/are-we-there-yet@2.0.0:
resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
dependencies:
delegates: 1.0.0
readable-stream: 3.6.2
dev: false
/are-we-there-yet@3.0.1:
resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -15146,6 +15176,18 @@ packages:
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
dev: false
/bcrypt@5.1.1:
resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==}
engines: {node: '>= 10.0.0'}
requiresBuild: true
dependencies:
'@mapbox/node-pre-gyp': 1.0.11
node-addon-api: 5.1.0
transitivePeerDependencies:
- encoding
- supports-color
dev: false
/bcryptjs@2.4.3:
resolution: {integrity: sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==}
dev: false
@@ -15580,7 +15622,7 @@ packages:
/builtins@5.1.0:
resolution: {integrity: sha512-SW9lzGTLvWTP1AY8xeAMZimqDrIaSdLQUcVr9DMef51niJ022Ri87SwRRKYm4A6iHfkPaiVUu/Duw2Wc4J7kKg==}
dependencies:
semver: 7.6.2
semver: 7.6.3
dev: true
/bull@4.16.4:
@@ -16526,7 +16568,6 @@ packages:
/console-control-strings@1.1.0:
resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
dev: true
/constant-case@3.0.4:
resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==}
@@ -16615,7 +16656,7 @@ packages:
handlebars: 4.7.8
json-stringify-safe: 5.0.1
meow: 8.1.2
semver: 7.6.2
semver: 7.6.3
split: 1.0.1
dev: true
@@ -17092,7 +17133,7 @@ packages:
postcss-modules-scope: 3.2.0(postcss@8.4.47)
postcss-modules-values: 4.0.0(postcss@8.4.47)
postcss-value-parser: 4.2.0
semver: 7.6.2
semver: 7.6.3
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
/css-loader@6.11.0(webpack@5.96.1):
@@ -17114,7 +17155,7 @@ packages:
postcss-modules-scope: 3.2.0(postcss@8.4.47)
postcss-modules-values: 4.0.0(postcss@8.4.47)
postcss-value-parser: 4.2.0
semver: 7.6.2
semver: 7.6.3
webpack: 5.96.1(esbuild@0.18.20)
dev: true
@@ -17702,7 +17743,6 @@ packages:
/delegates@1.0.0:
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
dev: true
/denque@1.5.1:
resolution: {integrity: sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==}
@@ -17767,7 +17807,6 @@ packages:
engines: {node: '>=8'}
requiresBuild: true
dev: false
optional: true
/detect-newline@3.1.0:
resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
@@ -18123,7 +18162,7 @@ packages:
'@one-ini/wasm': 0.1.1
commander: 10.0.1
minimatch: 9.0.1
semver: 7.6.2
semver: 7.6.3
dev: false
/ee-first@1.1.1:
@@ -20232,7 +20271,7 @@ packages:
memfs: 3.5.3
minimatch: 3.1.2
schema-utils: 2.7.0
semver: 7.6.2
semver: 7.6.3
tapable: 1.1.3
typescript: 4.9.5
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
@@ -20504,6 +20543,22 @@ packages:
/functions-have-names@1.2.3:
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
/gauge@3.0.2:
resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
engines: {node: '>=10'}
deprecated: This package is no longer supported.
dependencies:
aproba: 2.0.0
color-support: 1.1.3
console-control-strings: 1.1.0
has-unicode: 2.0.1
object-assign: 4.1.1
signal-exit: 3.0.7
string-width: 4.2.3
strip-ansi: 6.0.1
wide-align: 1.1.5
dev: false
/gauge@4.0.4:
resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -20702,7 +20757,7 @@ packages:
hasBin: true
dependencies:
meow: 8.1.2
semver: 7.6.2
semver: 7.6.3
dev: true
/git-up@7.0.0:
@@ -21192,7 +21247,6 @@ packages:
/has-unicode@2.0.1:
resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
dev: true
/has-value@0.3.1:
resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
@@ -22664,7 +22718,7 @@ packages:
'@babel/parser': 7.26.1
'@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -23487,7 +23541,7 @@ packages:
jest-util: 27.5.1
natural-compare: 1.4.0
pretty-format: 27.5.1
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
dev: false
@@ -23515,7 +23569,7 @@ packages:
jest-util: 29.7.0
natural-compare: 1.4.0
pretty-format: 29.7.0
semver: 7.6.2
semver: 7.6.3
transitivePeerDependencies:
- supports-color
dev: true
@@ -26117,13 +26171,17 @@ packages:
engines: {node: '>=10'}
requiresBuild: true
dependencies:
semver: 7.6.2
semver: 7.6.3
dev: false
optional: true
/node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
/node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
dev: false
/node-cache@4.2.1:
resolution: {integrity: sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==}
engines: {node: '>= 0.4.6'}
@@ -26198,7 +26256,7 @@ packages:
make-fetch-happen: 13.0.1
nopt: 7.2.1
proc-log: 3.0.0
semver: 7.6.2
semver: 7.6.3
tar: 6.2.1
which: 4.0.0
transitivePeerDependencies:
@@ -26272,6 +26330,14 @@ packages:
update-notifier: 2.5.0
dev: false
/nopt@5.0.0:
resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
engines: {node: '>=6'}
hasBin: true
dependencies:
abbrev: 1.1.1
dev: false
/nopt@7.2.1:
resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -26293,7 +26359,7 @@ packages:
dependencies:
hosted-git-info: 4.1.0
is-core-module: 2.13.1
semver: 7.6.2
semver: 7.6.3
validate-npm-package-license: 3.0.4
dev: true
@@ -26303,7 +26369,7 @@ packages:
dependencies:
hosted-git-info: 6.1.1
is-core-module: 2.13.1
semver: 7.6.2
semver: 7.6.3
validate-npm-package-license: 3.0.4
dev: true
@@ -26313,7 +26379,7 @@ packages:
dependencies:
hosted-git-info: 7.0.2
is-core-module: 2.13.1
semver: 7.6.2
semver: 7.6.3
validate-npm-package-license: 3.0.4
dev: true
@@ -26366,7 +26432,7 @@ packages:
resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
dependencies:
semver: 7.6.2
semver: 7.6.3
dev: true
/npm-normalize-package-bin@1.0.1:
@@ -26384,7 +26450,7 @@ packages:
dependencies:
hosted-git-info: 6.1.1
proc-log: 3.0.0
semver: 7.6.2
semver: 7.6.3
validate-npm-package-name: 5.0.0
dev: true
@@ -26394,7 +26460,7 @@ packages:
dependencies:
hosted-git-info: 7.0.2
proc-log: 4.2.0
semver: 7.6.2
semver: 7.6.3
validate-npm-package-name: 5.0.0
dev: true
@@ -26432,7 +26498,7 @@ packages:
npm-install-checks: 6.3.0
npm-normalize-package-bin: 3.0.1
npm-package-arg: 11.0.2
semver: 7.6.2
semver: 7.6.3
dev: true
/npm-registry-fetch@14.0.5:
@@ -26511,6 +26577,16 @@ packages:
path-key: 4.0.0
dev: true
/npmlog@5.0.1:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
dependencies:
are-we-there-yet: 2.0.0
console-control-strings: 1.1.0
gauge: 3.0.2
set-blocking: 2.0.0
dev: false
/npmlog@6.0.2:
resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -28130,7 +28206,7 @@ packages:
cosmiconfig: 7.1.0
klona: 2.0.6
postcss: 8.4.38
semver: 7.6.2
semver: 7.6.3
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@4.10.0)
dev: false
@@ -28144,7 +28220,7 @@ packages:
cosmiconfig: 8.3.6(typescript@5.6.3)
jiti: 1.21.0
postcss: 8.4.47
semver: 7.6.2
semver: 7.6.3
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
transitivePeerDependencies:
- typescript
@@ -28160,7 +28236,7 @@ packages:
cosmiconfig: 8.3.6(typescript@5.6.3)
jiti: 1.21.0
postcss: 8.4.47
semver: 7.6.2
semver: 7.6.3
webpack: 5.96.1(esbuild@0.18.20)
transitivePeerDependencies:
- typescript
@@ -31417,7 +31493,6 @@ packages:
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
engines: {node: '>=10'}
hasBin: true
dev: true
/send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
@@ -31703,7 +31778,7 @@ packages:
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
engines: {node: '>=10'}
dependencies:
semver: 7.6.2
semver: 7.6.3
dev: true
/sinon@7.5.0:
@@ -35235,7 +35310,6 @@ packages:
resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
dependencies:
string-width: 4.2.3
dev: true
/widest-line@2.0.1:
resolution: {integrity: sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==}