Implement first part of login route
parent
c15ef3b936
commit
f947df4610
|
@ -11,3 +11,6 @@ MONGODB_PASSWORD=
|
|||
MOBGODB_DATABASE=automod
|
||||
REDIS_URI=redis://
|
||||
PREFIX=/
|
||||
|
||||
# Set this to update the geoip database automatically. See docs of `geoip-lite` for more info
|
||||
MAXMIND_API_KEY=
|
||||
|
|
|
@ -24,9 +24,11 @@
|
|||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/swagger": "^7.0.12",
|
||||
"@types/geoip-lite": "^1.4.1",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"geoip-lite": "^1.4.7",
|
||||
"lib": "workspace:*",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
|
|
|
@ -1,4 +1,32 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
import { Body, Controller, Ip, Post } from '@nestjs/common';
|
||||
import { ApiProperty, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { NoAuthentication } from './authdata.decorator';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
export class LoginData {
|
||||
@ApiProperty({ description: 'The user ID you are trying to log in as' })
|
||||
user: string;
|
||||
}
|
||||
|
||||
export class LoginResponse {
|
||||
@ApiProperty()
|
||||
token: string;
|
||||
@ApiProperty()
|
||||
otp: string;
|
||||
}
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {}
|
||||
@ApiTags('authentication')
|
||||
export class AuthController {
|
||||
constructor(private auth: AuthService) {}
|
||||
|
||||
@Post('login')
|
||||
@NoAuthentication()
|
||||
@ApiResponse({ type: LoginResponse })
|
||||
async login(
|
||||
@Body() data: LoginData,
|
||||
@Ip() ip: string,
|
||||
): Promise<LoginResponse> {
|
||||
return await this.auth.createLoginRequest(data.user, ip);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
|
@ -70,3 +71,15 @@ export class AuthGuard implements CanActivate {
|
|||
return token;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoAuthGuard implements CanActivate {
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request: Request = context.switchToHttp().getRequest();
|
||||
if (request.header('authorization')) {
|
||||
throw new ForbiddenException(
|
||||
'This route may not be called with authentication',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { DBAuthToken } from 'lib';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { DBAuthToken, RE_ULID, generateAuthToken, generateOTP } from 'lib';
|
||||
import { ulid } from 'ulid';
|
||||
import { lookup as ipLookup } from 'geoip-lite';
|
||||
import { DatabaseService } from 'src/database/database.service';
|
||||
import { LoginResponse } from './auth.controller';
|
||||
import { UserAuditLogType } from 'lib/dist/cjs/types/database/user_audit_log';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
@ -15,6 +24,65 @@ export class AuthService {
|
|||
if (tokenData.expires && tokenData.expires.getTime() <= Date.now()) {
|
||||
throw new UnauthorizedException('Token expired');
|
||||
}
|
||||
if (tokenData.pending) {
|
||||
throw new UnauthorizedException('Token not activated yet');
|
||||
}
|
||||
return tokenData;
|
||||
}
|
||||
|
||||
async createLoginRequest(user: string, ip: string): Promise<LoginResponse> {
|
||||
if (!new RegExp(RE_ULID).test(user)) {
|
||||
throw new BadRequestException(
|
||||
"The provided user doesn't appear to be a valid user ID",
|
||||
);
|
||||
}
|
||||
|
||||
const tokenId = ulid();
|
||||
const token = generateAuthToken();
|
||||
let otp: string | undefined = undefined;
|
||||
|
||||
// Eliminate possibility of duplicate OTPs
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const generated = generateOTP();
|
||||
const count = await this.db.getDb().authTokens.countDocuments({
|
||||
otp: generated,
|
||||
user: user,
|
||||
});
|
||||
|
||||
if (!count) {
|
||||
otp = generated;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!otp) {
|
||||
throw new InternalServerErrorException('Unable to find a free OTP');
|
||||
}
|
||||
|
||||
await this.db.getDb().authTokens.insertOne({
|
||||
_id: tokenId,
|
||||
user: user,
|
||||
token: token,
|
||||
otp: otp,
|
||||
expires: new Date(Date.now() + 1000 * 60 * 5),
|
||||
});
|
||||
|
||||
const ipData = ipLookup(ip);
|
||||
|
||||
await this.db.getDb().userAuditLog.insertOne({
|
||||
_id: ulid(),
|
||||
user: user,
|
||||
type: UserAuditLogType.LoginAttempt,
|
||||
location: {
|
||||
country: ipData?.country,
|
||||
region: ipData?.region,
|
||||
city: ipData?.city,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
token: token,
|
||||
otp: otp,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
SetMetadata,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { AuthenticationData, AuthGuard } from './auth.guard';
|
||||
import { AuthenticationData, AuthGuard, NoAuthGuard } from './auth.guard';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
ApiForbiddenResponse,
|
||||
|
@ -45,3 +45,12 @@ export function Auth() {
|
|||
export function ServerAuth(level: 'member' | 'moderator' | 'administrator') {
|
||||
return applyDecorators(SetMetadata('access_level', level));
|
||||
}
|
||||
|
||||
export function NoAuthentication() {
|
||||
return applyDecorators(
|
||||
UseGuards(NoAuthGuard),
|
||||
ApiForbiddenResponse({
|
||||
description: 'Access to the resource is forbidden',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,15 +9,16 @@ import { META } from './meta';
|
|||
import { checkEnv } from 'lib';
|
||||
import { config } from 'dotenv';
|
||||
import { Logger, ValidationPipe } from '@nestjs/common';
|
||||
import { maxmindUpdater } from './util';
|
||||
|
||||
config();
|
||||
if (process.env.NODE_ENV != 'production') {
|
||||
Logger.log('$NODE_ENV is not set; Loading .env.dev');
|
||||
config({ path: '../.env.dev' });
|
||||
}
|
||||
checkEnv(['MONGODB_URI', 'MONGODB_DATABASE', 'REDIS_URI']);
|
||||
|
||||
async function bootstrap() {
|
||||
config();
|
||||
if (process.env.NODE_ENV != 'production') {
|
||||
Logger.log('$NODE_ENV is not set; Loading .env.dev');
|
||||
config({ path: '../.env.dev' });
|
||||
}
|
||||
checkEnv(['MONGODB_URI', 'MONGODB_DATABASE', 'REDIS_URI']);
|
||||
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.getHttpAdapter().getInstance().disable('x-powered-by');
|
||||
|
||||
|
@ -43,5 +44,8 @@ async function bootstrap() {
|
|||
app.useGlobalPipes(new ValidationPipe({ skipMissingProperties: false }));
|
||||
|
||||
await app.listen(3000);
|
||||
|
||||
maxmindUpdater();
|
||||
}
|
||||
|
||||
bootstrap();
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { startWatchingDataUpdate } from 'geoip-lite';
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export function maxmindUpdater() {
|
||||
startWatchingDataUpdate(() => {
|
||||
Logger.log(`GeoIP dataset has been updated`);
|
||||
});
|
||||
|
||||
const apiKey = process.env.MAXMIND_API_KEY?.trim();
|
||||
if (apiKey?.match(/\s/g)) {
|
||||
// This should protect against command injection in case an attacker somehow controls the API key variable
|
||||
throw new Error(`Maxmind API key includes a whitespace`);
|
||||
}
|
||||
if (apiKey) {
|
||||
Logger.log(
|
||||
`Maxmind API key was provided; Background geoip updates are enabled`,
|
||||
);
|
||||
|
||||
async function update() {
|
||||
Logger.log(`Checking for GeoIP updates`);
|
||||
const process = exec(
|
||||
`cd node_modules/geoip-lite && npm run-script updatedb license_key=${apiKey}`,
|
||||
);
|
||||
|
||||
process.on('close', (code) => {
|
||||
if (code == 0) Logger.log('GeoIP update completed with no error');
|
||||
else Logger.warn('GeoIP update exited with exit code ' + code);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV != 'development') {
|
||||
// GeoIP only updates files if the hash has changed, so it's fine to run this on every startup
|
||||
setTimeout(update, 1000 * 30);
|
||||
setInterval(update, 1000 * 60 * 60 * 24);
|
||||
} else {
|
||||
Logger.log(
|
||||
`Not scheduling GeoIP update tasks as we're running in development mode`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ import { DBServerLog } from "../types/database/server_logs.js";
|
|||
import { DBMember } from "../types/database/members.js";
|
||||
import { DBIncrementingIDs } from "../types/database/incrementing_ids.js";
|
||||
import { DBAuthToken } from "../types/database/auth_tokens.js";
|
||||
import { DBUserAuditLog } from "../types/database/user_audit_log.js";
|
||||
|
||||
export async function connectDb(uri: string, database: string) {
|
||||
const logger = createLogger();
|
||||
|
@ -23,6 +24,7 @@ export async function connectDb(uri: string, database: string) {
|
|||
return {
|
||||
incrementingIDs:
|
||||
db.collection<DBIncrementingIDs>("incrementing_ids"),
|
||||
userAuditLog: db.collection<DBUserAuditLog>("user_audit_log"),
|
||||
timedActions: db.collection<DBTimedAction>("timed_actions"),
|
||||
infractions: db.collection<DBInfraction>("infractions"),
|
||||
serverLogs: db.collection<DBServerLog>("server_logs"),
|
||||
|
|
|
@ -2,13 +2,14 @@ import { fileURLToPath } from "url";
|
|||
import { API, Channel, Message, Server, ServerMember, User } from "revolt.js";
|
||||
import path from "path";
|
||||
import fs from "fs/promises";
|
||||
import crypto from "crypto";
|
||||
import { AutomodClient } from "../types/AutomodClient";
|
||||
import { RE_MENTION_USER, RE_ULID } from "./regex.js";
|
||||
import { DBInfraction } from "../types/database/infraction";
|
||||
import { EventProtocol } from "revolt.js/src/events";
|
||||
import { inspect } from "util";
|
||||
import { OrID } from "../types/OrID";
|
||||
import { AutomodDatabase } from "./db";
|
||||
import { createLogger } from "./logger";
|
||||
|
||||
/**
|
||||
* Replacement for __dirname which the nodejs people so graciously took away.
|
||||
|
@ -574,3 +575,11 @@ export const limitLength = (input: string, max: number) =>
|
|||
export const memberIsModerator = async (
|
||||
member: ServerMember | null | undefined,
|
||||
) => (member ? member.hasPermission(member.server!, "KickMembers") : false);
|
||||
|
||||
export const generateAuthToken = () => {
|
||||
return crypto.randomBytes(64).toString("base64");
|
||||
};
|
||||
|
||||
export const generateOTP = () => {
|
||||
return crypto.randomBytes(3).toString("hex");
|
||||
};
|
||||
|
|
|
@ -5,12 +5,18 @@ export type DBAuthToken = {
|
|||
// The actual token
|
||||
token: string;
|
||||
|
||||
// If pending, this OTP can be used to activate the token.
|
||||
otp?: string;
|
||||
|
||||
// User the token belongs to
|
||||
user: string;
|
||||
|
||||
// Token expiry data
|
||||
expires?: Date;
|
||||
|
||||
// Whether this token is pending activation.
|
||||
pending?: boolean;
|
||||
|
||||
// If true, this token will have unrestricted access to everything.
|
||||
god?: boolean;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
export type DBUserAuditLog = {
|
||||
// Event ID, ULID
|
||||
_id: string;
|
||||
|
||||
// Associated user
|
||||
user: string;
|
||||
|
||||
// Type of event
|
||||
type: UserAuditLogType;
|
||||
|
||||
location?: {
|
||||
// 2 letter ISO-3166-1 country code
|
||||
country?: string;
|
||||
// 2 or 3 letter region code
|
||||
region?: string;
|
||||
// City name
|
||||
city?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export enum UserAuditLogType {
|
||||
LoginAttempt = "login_attempt",
|
||||
LoginAttemptSuccessful = "login_attempt_successful",
|
||||
LoginAttemptFailed = "login_attempt_failed",
|
||||
}
|
|
@ -20,6 +20,9 @@ importers:
|
|||
'@nestjs/swagger':
|
||||
specifier: ^7.0.12
|
||||
version: 7.0.12(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)
|
||||
'@types/geoip-lite':
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
class-transformer:
|
||||
specifier: ^0.5.1
|
||||
version: 0.5.1
|
||||
|
@ -29,6 +32,9 @@ importers:
|
|||
dotenv:
|
||||
specifier: ^16.0.3
|
||||
version: 16.1.3
|
||||
geoip-lite:
|
||||
specifier: ^1.4.7
|
||||
version: 1.4.7
|
||||
lib:
|
||||
specifier: workspace:*
|
||||
version: link:../lib
|
||||
|
@ -1609,6 +1615,10 @@ packages:
|
|||
'@types/serve-static': 1.15.2
|
||||
dev: true
|
||||
|
||||
/@types/geoip-lite@1.4.1:
|
||||
resolution: {integrity: sha512-qHH5eF3rL1wwqpzdsgMdgskfdWXxxQvJb9POJ66NK7/1l3QXsqHLpIheh9OmhtqZ2CF7AmN0sA2R4PgW8JSm7w==}
|
||||
dev: false
|
||||
|
||||
/@types/graceful-fs@4.1.6:
|
||||
resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
|
||||
dependencies:
|
||||
|
@ -2282,6 +2292,12 @@ packages:
|
|||
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
|
||||
dev: true
|
||||
|
||||
/async@2.6.4:
|
||||
resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
dev: false
|
||||
|
||||
/asynckit@0.4.0:
|
||||
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||
dev: true
|
||||
|
@ -2444,7 +2460,6 @@ packages:
|
|||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
concat-map: 0.0.1
|
||||
dev: true
|
||||
|
||||
/brace-expansion@2.0.1:
|
||||
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
|
||||
|
@ -2486,6 +2501,10 @@ packages:
|
|||
resolution: {integrity: sha512-ukmCZMneMlaC5ebPHXIkP8YJzNl5DC41N5MAIvKDqLggdao342t4McltoJBQfQya/nHBWAcSsYRqlXPoQkTJag==}
|
||||
engines: {node: '>=14.20.1'}
|
||||
|
||||
/buffer-crc32@0.2.13:
|
||||
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||
dev: false
|
||||
|
||||
/buffer-from@1.1.2:
|
||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||
|
||||
|
@ -2705,7 +2724,6 @@ packages:
|
|||
|
||||
/concat-map@0.0.1:
|
||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
||||
dev: true
|
||||
|
||||
/concat-stream@1.6.2:
|
||||
resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}
|
||||
|
@ -3345,6 +3363,12 @@ packages:
|
|||
bser: 2.1.1
|
||||
dev: true
|
||||
|
||||
/fd-slicer@1.1.0:
|
||||
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||
dependencies:
|
||||
pend: 1.2.0
|
||||
dev: false
|
||||
|
||||
/figures@3.2.0:
|
||||
resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -3491,7 +3515,6 @@ packages:
|
|||
|
||||
/fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
dev: true
|
||||
|
||||
/fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
|
@ -3528,6 +3551,19 @@ packages:
|
|||
engines: {node: '>=6.9.0'}
|
||||
dev: true
|
||||
|
||||
/geoip-lite@1.4.7:
|
||||
resolution: {integrity: sha512-JQHntlH7B/nR6Ec8ZJTuKsSdRNrR+snrfBNy0y0wVYWyVVi/MoDlXyv7P3wmozdlyshta6rXfbtK7qu/9lvEog==}
|
||||
engines: {node: '>=5.10.0'}
|
||||
dependencies:
|
||||
async: 2.6.4
|
||||
chalk: 4.1.2
|
||||
iconv-lite: 0.4.24
|
||||
ip-address: 5.9.4
|
||||
lazy: 1.0.11
|
||||
rimraf: 2.7.1
|
||||
yauzl: 2.10.0
|
||||
dev: false
|
||||
|
||||
/get-caller-file@2.0.5:
|
||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||
engines: {node: 6.* || 8.* || >= 10.*}
|
||||
|
@ -3593,7 +3629,6 @@ packages:
|
|||
minimatch: 3.1.2
|
||||
once: 1.4.0
|
||||
path-is-absolute: 1.0.1
|
||||
dev: true
|
||||
|
||||
/glob@9.3.5:
|
||||
resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==}
|
||||
|
@ -3802,7 +3837,6 @@ packages:
|
|||
dependencies:
|
||||
once: 1.4.0
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/inherits@2.0.4:
|
||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||
|
@ -3867,6 +3901,15 @@ packages:
|
|||
engines: {node: '>= 0.10'}
|
||||
dev: true
|
||||
|
||||
/ip-address@5.9.4:
|
||||
resolution: {integrity: sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==}
|
||||
engines: {node: '>= 0.10'}
|
||||
dependencies:
|
||||
jsbn: 1.1.0
|
||||
lodash: 4.17.21
|
||||
sprintf-js: 1.1.2
|
||||
dev: false
|
||||
|
||||
/ip@2.0.0:
|
||||
resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
|
||||
|
||||
|
@ -4542,6 +4585,10 @@ packages:
|
|||
dependencies:
|
||||
argparse: 2.0.1
|
||||
|
||||
/jsbn@1.1.0:
|
||||
resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
|
||||
dev: false
|
||||
|
||||
/jsesc@2.5.2:
|
||||
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
|
||||
engines: {node: '>=4'}
|
||||
|
@ -4602,6 +4649,11 @@ packages:
|
|||
resolution: {integrity: sha512-RTSoaUAfLvpR357vWzAz/50Q/BmHfmE6ETSWfutT0AJiw10e6CmcdYRQJlLRd95B53D0Y2aD1jSxD3V3ySF+PA==}
|
||||
dev: true
|
||||
|
||||
/lazy@1.0.11:
|
||||
resolution: {integrity: sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==}
|
||||
engines: {node: '>=0.2.0'}
|
||||
dev: false
|
||||
|
||||
/leven@3.1.0:
|
||||
resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==}
|
||||
engines: {node: '>=6'}
|
||||
|
@ -4811,7 +4863,6 @@ packages:
|
|||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
||||
dependencies:
|
||||
brace-expansion: 1.1.11
|
||||
dev: true
|
||||
|
||||
/minimatch@8.0.4:
|
||||
resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
|
||||
|
@ -4992,7 +5043,6 @@ packages:
|
|||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||
dependencies:
|
||||
wrappy: 1.0.2
|
||||
dev: true
|
||||
|
||||
/onetime@5.1.2:
|
||||
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
|
||||
|
@ -5118,7 +5168,6 @@ packages:
|
|||
/path-is-absolute@1.0.1:
|
||||
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/path-key@3.1.1:
|
||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||
|
@ -5154,6 +5203,10 @@ packages:
|
|||
through: 2.3.8
|
||||
dev: true
|
||||
|
||||
/pend@1.2.0:
|
||||
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
dev: true
|
||||
|
@ -5430,6 +5483,13 @@ packages:
|
|||
- debug
|
||||
dev: false
|
||||
|
||||
/rimraf@2.7.1:
|
||||
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
glob: 7.2.3
|
||||
dev: false
|
||||
|
||||
/rimraf@3.0.2:
|
||||
resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
|
||||
hasBin: true
|
||||
|
@ -5668,6 +5728,10 @@ packages:
|
|||
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
|
||||
dev: true
|
||||
|
||||
/sprintf-js@1.1.2:
|
||||
resolution: {integrity: sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==}
|
||||
dev: false
|
||||
|
||||
/stack-utils@2.0.6:
|
||||
resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -6445,7 +6509,6 @@ packages:
|
|||
|
||||
/wrappy@1.0.2:
|
||||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/write-file-atomic@4.0.2:
|
||||
resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==}
|
||||
|
@ -6506,6 +6569,13 @@ packages:
|
|||
yargs-parser: 21.1.1
|
||||
dev: true
|
||||
|
||||
/yauzl@2.10.0:
|
||||
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
fd-slicer: 1.1.0
|
||||
dev: false
|
||||
|
||||
/yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
Loading…
Reference in New Issue