add totp
This commit is contained in:
parent
7de0962ed6
commit
7724bc460f
@ -35,6 +35,7 @@
|
||||
"@react-email/components": "^0.1.1",
|
||||
"@react-email/html": "^0.0.11",
|
||||
"@react-email/tailwind": "^1.0.5",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"argon2": "^0.43.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
@ -44,9 +45,12 @@
|
||||
"express-session": "^1.18.1",
|
||||
"geoip-lite": "^1.4.10",
|
||||
"graphql": "^16.11.0",
|
||||
"hi-base32": "^0.5.1",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"otpauth": "^9.4.0",
|
||||
"prisma": "^6.10.1",
|
||||
"qrcode": "^1.5.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
|
||||
@ -19,6 +19,8 @@ model User {
|
||||
token Token[]
|
||||
isVerified Boolean @default(false) @map("is_verified")
|
||||
isEmailVerified Boolean @default(false) @map("is_email_verified")
|
||||
isTotpEnabled Boolean @default(false) @map("is_totp_enabled")
|
||||
totpSecret String? @map("totp_secret")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { getGraphQLConfig } from '@/src/core/config/graphql.config'
|
||||
import { AccountModule } from '@/src/module/auth/account/account.module'
|
||||
import { PasswordRecoveryModule } from '@/src/module/auth/password-recovery/password-recovery.module'
|
||||
import { SessionModule } from '@/src/module/auth/session/session.module'
|
||||
import { TotpModule } from '@/src/module/auth/totp/totp.module'
|
||||
import { VerificationModule } from '@/src/module/auth/verification/verification.module'
|
||||
import { MailModule } from '@/src/module/libs/mail/mail.module'
|
||||
import { IS_DEV } from '@/src/shared/util/is-dev.util'
|
||||
@ -31,6 +32,7 @@ import { RedisModule } from './redis/redis.module'
|
||||
SessionModule,
|
||||
VerificationModule,
|
||||
PasswordRecoveryModule,
|
||||
TotpModule,
|
||||
],
|
||||
})
|
||||
export class CoreModule {}
|
||||
|
||||
@ -2,6 +2,11 @@
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
|
||||
# ------------------------------------------------------
|
||||
|
||||
type AuthModel {
|
||||
message: String
|
||||
user: UserModel
|
||||
}
|
||||
|
||||
input CreateUserInput {
|
||||
email: String!
|
||||
name: String!
|
||||
@ -19,6 +24,11 @@ type DeviceModel {
|
||||
type: String!
|
||||
}
|
||||
|
||||
input EnableTotpInput {
|
||||
pin: String!
|
||||
secret: String!
|
||||
}
|
||||
|
||||
type LocationModel {
|
||||
city: String!
|
||||
country: String!
|
||||
@ -29,12 +39,15 @@ type LocationModel {
|
||||
input LoginInput {
|
||||
login: String!
|
||||
password: String!
|
||||
pin: String
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
clearSessionCookie: Boolean!
|
||||
createUser(data: CreateUserInput!): UserModel!
|
||||
loginUser(data: LoginInput!): UserModel!
|
||||
disableTotp: Boolean!
|
||||
enableTotp(data: EnableTotpInput!): Boolean!
|
||||
loginUser(data: LoginInput!): AuthModel!
|
||||
logoutUser: Boolean!
|
||||
removeSession(id: String!): Boolean!
|
||||
resetPassword(data: ResetPasswordInput!): Boolean!
|
||||
@ -52,6 +65,7 @@ type Query {
|
||||
findCurrentSession: SessionModel!
|
||||
findProfile: UserModel!
|
||||
findSessionsByUser: [SessionModel!]!
|
||||
generateTotpSecret: TotpModel!
|
||||
}
|
||||
|
||||
input ResetPasswordInput {
|
||||
@ -71,6 +85,11 @@ type SessionModel {
|
||||
userId: String!
|
||||
}
|
||||
|
||||
type TotpModel {
|
||||
qrcodeUrl: String!
|
||||
secret: String!
|
||||
}
|
||||
|
||||
type UserModel {
|
||||
avatar: String
|
||||
bio: String
|
||||
@ -79,9 +98,11 @@ type UserModel {
|
||||
email: String!
|
||||
id: ID!
|
||||
isEmailVerified: Boolean!
|
||||
isTotpEnabled: Boolean!
|
||||
isVerified: Boolean!
|
||||
name: String!
|
||||
password: String!
|
||||
totpSecret: String
|
||||
updatedAt: DateTime!
|
||||
}
|
||||
|
||||
|
||||
11
backend/src/module/auth/account/models/auth.model.ts
Normal file
11
backend/src/module/auth/account/models/auth.model.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { UserModel } from '@/src/module/auth/account/models/user.model'
|
||||
import { Field, ObjectType } from '@nestjs/graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class AuthModel {
|
||||
@Field(() => UserModel, { nullable: true })
|
||||
public user?: UserModel
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
public message: string
|
||||
}
|
||||
@ -30,6 +30,12 @@ export class UserModel implements User {
|
||||
@Field(() => Boolean)
|
||||
isVerified: boolean
|
||||
|
||||
@Field(() => Boolean)
|
||||
isTotpEnabled: boolean
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
totpSecret: string
|
||||
|
||||
@Field(() => Date)
|
||||
createdAt: Date
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/input/new-password.input'
|
||||
import { ResetPasswordInput } from '@/src/module/auth/password-recovery/input/reset-password.input'
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/inputs/new-password.input'
|
||||
import { ResetPasswordInput } from '@/src/module/auth/password-recovery/inputs/reset-password.input'
|
||||
import { UserAgent } from '@/src/shared/decorators/user-agent.decorator'
|
||||
import { GqlContext } from '@/src/shared/types/gql-context.types'
|
||||
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { PrismaService } from '@/src/core/prisma/prisma.service'
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/input/new-password.input'
|
||||
import { ResetPasswordInput } from '@/src/module/auth/password-recovery/input/reset-password.input'
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/inputs/new-password.input'
|
||||
import { ResetPasswordInput } from '@/src/module/auth/password-recovery/inputs/reset-password.input'
|
||||
import { MailService } from '@/src/module/libs/mail/mail.service'
|
||||
import { generateToken } from '@/src/shared/util/generate-token.util'
|
||||
import { getSessionMetadata } from '@/src/shared/util/session-metadata.util'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Field, InputType } from '@nestjs/graphql'
|
||||
import { IsNotEmpty, IsString, MinLength } from 'class-validator'
|
||||
import { IsNotEmpty, IsOptional, IsString, Length, MinLength } from 'class-validator'
|
||||
|
||||
@InputType()
|
||||
export class LoginInput {
|
||||
@ -13,4 +13,11 @@ export class LoginInput {
|
||||
@IsNotEmpty()
|
||||
@MinLength(8)
|
||||
password: string
|
||||
|
||||
@Field(() => String, { nullable: true })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
@Length(6, 6)
|
||||
pin?: string
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { AuthModel } from '@/src/module/auth/account/models/auth.model';
|
||||
import { UserModel } from '@/src/module/auth/account/models/user.model'
|
||||
import { LoginInput } from '@/src/module/auth/session/inputs/login.input'
|
||||
import { Authorization } from '@/src/shared/decorators/auth.decorator'
|
||||
@ -27,7 +28,7 @@ export class SessionResolver {
|
||||
return this.sessionService.findCurrentSession(req)
|
||||
}
|
||||
|
||||
@Mutation(() => UserModel, { name: 'loginUser' })
|
||||
@Mutation(() => AuthModel, { name: 'loginUser' })
|
||||
public async login(
|
||||
@Context() { req }: GqlContext,
|
||||
@Args('data') input: LoginInput,
|
||||
|
||||
@ -15,6 +15,7 @@ import { ConfigService } from '@nestjs/config'
|
||||
import { verify } from 'argon2'
|
||||
import { Request } from 'express'
|
||||
import { SessionData } from 'express-session'
|
||||
import { TOTP } from 'otpauth'
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
@ -74,7 +75,7 @@ export class SessionService {
|
||||
}
|
||||
|
||||
public async login(req: Request, input: LoginInput, userAgent: string) {
|
||||
const { login, password } = input
|
||||
const { login, password, pin } = input
|
||||
|
||||
const user = await this.prismaService.user.findFirst({ where: {
|
||||
OR: [
|
||||
@ -97,6 +98,28 @@ export class SessionService {
|
||||
throw new BadRequestException('Аккаунт не верифицирован. Проверьте свою почту для подтверждения')
|
||||
}
|
||||
|
||||
if (user.isTotpEnabled) {
|
||||
if (!pin) {
|
||||
return {
|
||||
message: 'Необходимо ввести пин-код для завершения операции',
|
||||
}
|
||||
}
|
||||
|
||||
const totp = new TOTP({
|
||||
issuer: 'TeaStream',
|
||||
label: user.email,
|
||||
algorithm: 'SHA-1',
|
||||
digits: 6,
|
||||
secret: user.totpSecret!,
|
||||
})
|
||||
|
||||
const delta = totp.validate({ token: pin })
|
||||
|
||||
if (delta === null) {
|
||||
throw new BadRequestException('Невереый код')
|
||||
}
|
||||
}
|
||||
|
||||
return saveSession(req, user, getSessionMetadata(req, userAgent))
|
||||
}
|
||||
|
||||
|
||||
16
backend/src/module/auth/totp/inputs/enable-totp.input.ts
Normal file
16
backend/src/module/auth/totp/inputs/enable-totp.input.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Field, InputType } from '@nestjs/graphql'
|
||||
import { IsNotEmpty, IsString, Length } from 'class-validator'
|
||||
|
||||
@InputType()
|
||||
export class EnableTotpInput {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
secret: string
|
||||
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Length(6, 6)
|
||||
pin: string
|
||||
}
|
||||
10
backend/src/module/auth/totp/models/totp.model.ts
Normal file
10
backend/src/module/auth/totp/models/totp.model.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class TotpModel {
|
||||
@Field(() => String)
|
||||
public qrcodeUrl: string
|
||||
|
||||
@Field(() => String)
|
||||
public secret: string
|
||||
}
|
||||
8
backend/src/module/auth/totp/totp.module.ts
Normal file
8
backend/src/module/auth/totp/totp.module.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TotpService } from './totp.service';
|
||||
import { TotpResolver } from './totp.resolver';
|
||||
|
||||
@Module({
|
||||
providers: [TotpResolver, TotpService],
|
||||
})
|
||||
export class TotpModule {}
|
||||
33
backend/src/module/auth/totp/totp.resolver.ts
Normal file
33
backend/src/module/auth/totp/totp.resolver.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { EnableTotpInput } from '@/src/module/auth/totp/inputs/enable-totp.input'
|
||||
import { TotpModel } from '@/src/module/auth/totp/models/totp.model'
|
||||
import { Authorization } from '@/src/shared/decorators/auth.decorator'
|
||||
import { Authorized } from '@/src/shared/decorators/authorized.decorator'
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql'
|
||||
import { User } from '@prisma/generated'
|
||||
import { TotpService } from './totp.service'
|
||||
|
||||
@Resolver('Totp')
|
||||
export class TotpResolver {
|
||||
constructor(private readonly totpService: TotpService) {}
|
||||
|
||||
@Authorization()
|
||||
@Query(() => TotpModel, { name: 'generateTotpSecret' })
|
||||
public async generate(@Authorized() user: User) {
|
||||
return this.totpService.generate(user)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => Boolean, { name: 'enableTotp' })
|
||||
public async enable(
|
||||
@Authorized() user: User,
|
||||
@Args('data') input: EnableTotpInput,
|
||||
) {
|
||||
return this.totpService.enable(user, input)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => Boolean, { name: 'disableTotp' })
|
||||
public async disable(@Authorized() user: User) {
|
||||
return this.totpService.disable(user)
|
||||
}
|
||||
}
|
||||
77
backend/src/module/auth/totp/totp.service.ts
Normal file
77
backend/src/module/auth/totp/totp.service.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import { PrismaService } from '@/src/core/prisma/prisma.service'
|
||||
import { EnableTotpInput } from '@/src/module/auth/totp/inputs/enable-totp.input'
|
||||
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||
import { User } from '@prisma/generated'
|
||||
import { encode } from 'hi-base32'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { TOTP } from 'otpauth'
|
||||
import * as QRCode from 'qrcode'
|
||||
|
||||
@Injectable()
|
||||
export class TotpService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
) {
|
||||
}
|
||||
|
||||
async generate(user: User) {
|
||||
const secret = encode(randomBytes(15))
|
||||
.replace(/=/g, '')
|
||||
.substring(0, 24)
|
||||
|
||||
const totp = new TOTP({
|
||||
issuer: 'TeaStream',
|
||||
label: user.email,
|
||||
algorithm: 'SHA-1',
|
||||
digits: 6,
|
||||
secret,
|
||||
})
|
||||
|
||||
const qrcodeUrl = await QRCode.toDataURL(totp.toString())
|
||||
|
||||
return {
|
||||
qrcodeUrl,
|
||||
secret,
|
||||
}
|
||||
}
|
||||
|
||||
async enable(user: User, input: EnableTotpInput) {
|
||||
const { pin, secret } = input
|
||||
|
||||
const totp = new TOTP({
|
||||
issuer: 'TeaStream',
|
||||
label: user.email,
|
||||
algorithm: 'SHA-1',
|
||||
digits: 6,
|
||||
secret,
|
||||
})
|
||||
|
||||
const delta = totp.validate({ token: pin })
|
||||
|
||||
if (delta === null) {
|
||||
throw new BadRequestException('Невереый код')
|
||||
}
|
||||
|
||||
await this.prismaService.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
isTotpEnabled: true,
|
||||
totpSecret: secret,
|
||||
},
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
async disable(user: User) {
|
||||
await this.prismaService.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
isTotpEnabled: false,
|
||||
totpSecret: null,
|
||||
},
|
||||
})
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/input/new-password.input'
|
||||
import { NewPasswordInput } from '@/src/module/auth/password-recovery/inputs/new-password.input'
|
||||
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from 'class-validator'
|
||||
|
||||
@ValidatorConstraint({ name: 'IsPasswordMatching', async: false })
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { SessionInfo } from '@/src/shared/types/session-metadata.types'
|
||||
import { InternalServerErrorException } from '@nestjs/common'
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { User } from '@prisma/generated'
|
||||
import { Request } from 'express'
|
||||
|
||||
export function saveSession(req: Request, user: User, metadata: SessionInfo) {
|
||||
return new Promise<User>((resolve, reject) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.session.createdAt = new Date()
|
||||
req.session.userId = user.id
|
||||
req.session.metadata = metadata
|
||||
@ -15,7 +15,7 @@ export function saveSession(req: Request, user: User, metadata: SessionInfo) {
|
||||
reject(new InternalServerErrorException('Не удалось сохранить сессию'))
|
||||
}
|
||||
|
||||
resolve(user)
|
||||
resolve({ user })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -1426,6 +1426,11 @@
|
||||
dependencies:
|
||||
tslib "2.8.1"
|
||||
|
||||
"@noble/hashes@1.7.1":
|
||||
version "1.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.7.1.tgz#5738f6d765710921e7a751e00c20ae091ed8db0f"
|
||||
integrity sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==
|
||||
|
||||
"@noble/hashes@^1.1.5":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a"
|
||||
@ -2140,6 +2145,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.10.tgz#52f8dbd6113517aef901db20b4f3fca543b88c1f"
|
||||
integrity sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==
|
||||
|
||||
"@types/qrcode@^1.5.5":
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.5.5.tgz#993ff7c6b584277eee7aac0a20861eab682f9dac"
|
||||
integrity sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/qs@*":
|
||||
version "6.14.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5"
|
||||
@ -3068,7 +3080,7 @@ camel-case@^3.0.0:
|
||||
no-case "^2.2.0"
|
||||
upper-case "^1.1.1"
|
||||
|
||||
camelcase@^5.3.1:
|
||||
camelcase@^5.0.0, camelcase@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
|
||||
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
|
||||
@ -3225,6 +3237,15 @@ cli-width@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-4.1.0.tgz#42daac41d3c254ef38ad8ac037672130173691c5"
|
||||
integrity sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==
|
||||
|
||||
cliui@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
|
||||
integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
|
||||
dependencies:
|
||||
string-width "^4.2.0"
|
||||
strip-ansi "^6.0.0"
|
||||
wrap-ansi "^6.2.0"
|
||||
|
||||
cliui@^8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
|
||||
@ -3525,6 +3546,11 @@ debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3
|
||||
dependencies:
|
||||
ms "^2.1.3"
|
||||
|
||||
decamelize@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||
integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==
|
||||
|
||||
decompress-response@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
|
||||
@ -3632,6 +3658,11 @@ diff@^4.0.1:
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dijkstrajs@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.3.tgz#4c8dbdea1f0f6478bff94d9c49c784d623e4fc23"
|
||||
integrity sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==
|
||||
|
||||
display-notification@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/display-notification/-/display-notification-2.0.0.tgz#49fad2e03289b4f668c296e1855c2cf8ba893d49"
|
||||
@ -4495,7 +4526,7 @@ geoip-lite@^1.4.10:
|
||||
rimraf "2.5.2 - 2.7.1"
|
||||
yauzl "2.9.2 - 2.10.0"
|
||||
|
||||
get-caller-file@^2.0.5:
|
||||
get-caller-file@^2.0.1, get-caller-file@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||
@ -4728,6 +4759,11 @@ he@1.2.0, he@^1.2.0:
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
hi-base32@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/hi-base32/-/hi-base32-0.5.1.tgz#1279f2ddae2673219ea5870c2121d2a33132857e"
|
||||
integrity sha512-EmBBpvdYh/4XxsnUybsPag6VikPYnN30td+vQk+GI3qpahVEG9+gTkG0aXVxTjBqQ5T6ijbWIu77O+C5WFWsnA==
|
||||
|
||||
html-escaper@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
|
||||
@ -6610,6 +6646,13 @@ os-tmpdir@~1.0.2:
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==
|
||||
|
||||
otpauth@^9.4.0:
|
||||
version "9.4.0"
|
||||
resolved "https://registry.yarnpkg.com/otpauth/-/otpauth-9.4.0.tgz#ed52538848cc52138e4dcd75cef45412b6175ea1"
|
||||
integrity sha512-fHIfzIG5RqCkK9cmV8WU+dPQr9/ebR5QOwGZn2JAr1RQF+lmAuLL2YdtdqvmBjNmgJlYk3KZ4a0XokaEhg1Jsw==
|
||||
dependencies:
|
||||
"@noble/hashes" "1.7.1"
|
||||
|
||||
p-cancelable@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-3.0.0.tgz#63826694b54d61ca1c20ebcb6d3ecf5e14cd8050"
|
||||
@ -6841,6 +6884,11 @@ pluralize@8.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||
|
||||
pngjs@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb"
|
||||
integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==
|
||||
|
||||
prelude-ls@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||
@ -7036,6 +7084,15 @@ pure-rand@^6.0.0:
|
||||
resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.1.0.tgz#d173cf23258231976ccbdb05247c9787957604f2"
|
||||
integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
|
||||
|
||||
qrcode@^1.5.4:
|
||||
version "1.5.4"
|
||||
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.4.tgz#5cb81d86eb57c675febb08cf007fff963405da88"
|
||||
integrity sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==
|
||||
dependencies:
|
||||
dijkstrajs "^1.0.1"
|
||||
pngjs "^5.0.0"
|
||||
yargs "^15.3.1"
|
||||
|
||||
qs@6.13.0:
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906"
|
||||
@ -7189,6 +7246,11 @@ require-from-string@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
|
||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||
|
||||
require-main-filename@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
|
||||
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
|
||||
|
||||
resolve-alpn@^1.2.0:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9"
|
||||
@ -7434,6 +7496,11 @@ serve-static@^2.2.0:
|
||||
parseurl "^1.3.3"
|
||||
send "^1.2.0"
|
||||
|
||||
set-blocking@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
|
||||
integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==
|
||||
|
||||
setprototypeof@1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424"
|
||||
@ -8264,6 +8331,11 @@ whatwg-url@^5.0.0:
|
||||
tr46 "~0.0.3"
|
||||
webidl-conversions "^3.0.0"
|
||||
|
||||
which-module@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409"
|
||||
integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==
|
||||
|
||||
which@^1.2.9:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
@ -8370,6 +8442,11 @@ xtend@^4.0.2:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
|
||||
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
|
||||
|
||||
y18n@^4.0.0:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
|
||||
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
|
||||
|
||||
y18n@^5.0.5:
|
||||
version "5.0.8"
|
||||
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
|
||||
@ -8385,6 +8462,31 @@ yargs-parser@21.1.1, yargs-parser@^21.1.1:
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35"
|
||||
integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==
|
||||
|
||||
yargs-parser@^18.1.2:
|
||||
version "18.1.3"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
|
||||
integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
|
||||
dependencies:
|
||||
camelcase "^5.0.0"
|
||||
decamelize "^1.2.0"
|
||||
|
||||
yargs@^15.3.1:
|
||||
version "15.4.1"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8"
|
||||
integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==
|
||||
dependencies:
|
||||
cliui "^6.0.0"
|
||||
decamelize "^1.2.0"
|
||||
find-up "^4.1.0"
|
||||
get-caller-file "^2.0.1"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^2.0.0"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^4.2.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^4.0.0"
|
||||
yargs-parser "^18.1.2"
|
||||
|
||||
yargs@^17.3.1, yargs@^17.7.2:
|
||||
version "17.7.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user