add sessions endpoints
This commit is contained in:
parent
a243549afb
commit
f6ea096842
@ -36,8 +36,11 @@
|
||||
"class-validator": "^0.14.2",
|
||||
"connect-redis": "^7.1.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"device-detector-js": "^3.0.3",
|
||||
"express-session": "^1.18.1",
|
||||
"geoip-lite": "^1.4.10",
|
||||
"graphql": "^16.11.0",
|
||||
"i18n-iso-countries": "^7.14.0",
|
||||
"ioredis": "^5.6.1",
|
||||
"prisma": "^6.10.1",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
@ -55,6 +58,7 @@
|
||||
"@types/cookie-parser": "^1.4.9",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-session": "^1.18.2",
|
||||
"@types/geoip-lite": "^1.4.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/supertest": "^6.0.2",
|
||||
|
||||
@ -13,19 +13,49 @@ A date-time string at UTC, such as 2019-12-03T09:54:33Z, compliant with the date
|
||||
"""
|
||||
scalar DateTime
|
||||
|
||||
type DeviceModel {
|
||||
browser: String!
|
||||
os: String!
|
||||
type: String!
|
||||
}
|
||||
|
||||
type LocationModel {
|
||||
city: String!
|
||||
country: String!
|
||||
latitude: Float!
|
||||
longitude: Float!
|
||||
}
|
||||
|
||||
input LoginInput {
|
||||
login: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
clearSessionCookie: Boolean!
|
||||
createUser(data: CreateUserInput!): UserModel!
|
||||
loginUser(data: LoginInput!): UserModel!
|
||||
logoutUser: Boolean!
|
||||
removeSession(id: String!): Boolean!
|
||||
}
|
||||
|
||||
type Query {
|
||||
findCurrentSession: SessionModel!
|
||||
findProfile: UserModel!
|
||||
findSessionsByUser: [SessionModel!]!
|
||||
}
|
||||
|
||||
type SessionMetadataModel {
|
||||
device: DeviceModel!
|
||||
ip: String!
|
||||
location: LocationModel!
|
||||
}
|
||||
|
||||
type SessionModel {
|
||||
createdAt: String!
|
||||
id: ID!
|
||||
metadata: SessionMetadataModel!
|
||||
userId: String!
|
||||
}
|
||||
|
||||
type UserModel {
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql'
|
||||
import { User } from '@prisma/generated'
|
||||
|
||||
@ObjectType()
|
||||
export class UserModel {
|
||||
export class UserModel implements User {
|
||||
@Field(() => ID)
|
||||
id: string
|
||||
|
||||
|
||||
56
backend/src/module/auth/session/models/session.model.ts
Normal file
56
backend/src/module/auth/session/models/session.model.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { DeviceInfo, LocationInfo, SessionInfo } from '@/src/shared/types/session-metadata.types'
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql'
|
||||
|
||||
@ObjectType()
|
||||
export class LocationModel implements LocationInfo {
|
||||
@Field(() => String)
|
||||
country: string
|
||||
|
||||
@Field(() => String)
|
||||
city: string
|
||||
|
||||
@Field(() => Number)
|
||||
latitude: number
|
||||
|
||||
@Field(() => Number)
|
||||
longitude: number
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class DeviceModel implements DeviceInfo {
|
||||
@Field(() => String)
|
||||
browser: string
|
||||
|
||||
@Field(() => String)
|
||||
os: string
|
||||
|
||||
@Field(() => String)
|
||||
type: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SessionMetadataModel implements SessionInfo {
|
||||
@Field(() => LocationModel)
|
||||
location: LocationModel
|
||||
|
||||
@Field(() => DeviceModel)
|
||||
device: DeviceModel
|
||||
|
||||
@Field(() => String)
|
||||
ip: string
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class SessionModel {
|
||||
@Field(() => ID)
|
||||
id: string
|
||||
|
||||
@Field(() => String)
|
||||
userId: string
|
||||
|
||||
@Field(() => String)
|
||||
createdAt: string
|
||||
|
||||
@Field(() => SessionMetadataModel)
|
||||
metadata: SessionMetadataModel
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SessionService } from './session.service';
|
||||
import { SessionResolver } from './session.resolver';
|
||||
import { Module } from '@nestjs/common'
|
||||
import { SessionService } from './session.service'
|
||||
import { SessionResolver } from './session.resolver'
|
||||
|
||||
@Module({
|
||||
providers: [SessionResolver, SessionService],
|
||||
|
||||
@ -1,23 +1,58 @@
|
||||
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'
|
||||
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'
|
||||
import { Args, Context, Mutation, Query, Resolver } from '@nestjs/graphql'
|
||||
import { SessionService } from './session.service'
|
||||
import { SessionModel } from './models/session.model'
|
||||
|
||||
@Resolver('Session')
|
||||
export class SessionResolver {
|
||||
constructor(private readonly sessionService: SessionService) {}
|
||||
|
||||
@Authorization()
|
||||
@Query(() => [SessionModel], { name: 'findSessionsByUser' })
|
||||
public async findByUser(
|
||||
@Context() { req }: GqlContext,
|
||||
) {
|
||||
return this.sessionService.findByUser(req)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Query(() => SessionModel, { name: 'findCurrentSession' })
|
||||
public async findCurrent(
|
||||
@Context() { req }: GqlContext,
|
||||
) {
|
||||
return this.sessionService.findCurrentSession(req)
|
||||
}
|
||||
|
||||
@Mutation(() => UserModel, { name: 'loginUser' })
|
||||
public async login(
|
||||
@Context() { req }: GqlContext,
|
||||
@Args('data') input: LoginInput,
|
||||
@UserAgent() userAgent: string,
|
||||
) {
|
||||
return this.sessionService.login(req, input)
|
||||
return this.sessionService.login(req, input, userAgent)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => Boolean, { name: 'logoutUser' })
|
||||
public async logout(@Context() { req }: GqlContext) {
|
||||
return this.sessionService.logout(req)
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, { name: 'clearSessionCookie' })
|
||||
public clearSession(@Context() { req }: GqlContext) {
|
||||
return this.sessionService.clearSession(req)
|
||||
}
|
||||
|
||||
@Authorization()
|
||||
@Mutation(() => Boolean, { name: 'removeSession' })
|
||||
public remove(
|
||||
@Context() { req }: GqlContext,
|
||||
@Args('id') id: string,
|
||||
) {
|
||||
return this.sessionService.remove(req, id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
import { PrismaService } from '@/src/core/prisma/prisma.service'
|
||||
import { RedisService } from '@/src/core/redis/redis.service'
|
||||
import { LoginInput } from '@/src/module/auth/session/inputs/login.input'
|
||||
import { getSessionMetadata } from '@/src/shared/util/session-metadata.util'
|
||||
import {
|
||||
ConflictException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
@ -9,15 +12,65 @@ import {
|
||||
import { ConfigService } from '@nestjs/config'
|
||||
import { verify } from 'argon2'
|
||||
import { Request } from 'express'
|
||||
import { SessionData } from 'express-session'
|
||||
|
||||
@Injectable()
|
||||
export class SessionService {
|
||||
constructor(
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly redisService: RedisService,
|
||||
private readonly configService: ConfigService,
|
||||
) {}
|
||||
|
||||
public async login(req: Request, input: LoginInput) {
|
||||
public async findByUser(req: Request) {
|
||||
const userId = req.user?.id
|
||||
|
||||
if (!userId) {
|
||||
throw new NotFoundException('Пользователь не найден')
|
||||
}
|
||||
|
||||
const keys = await this.redisService.keys('*')
|
||||
const userSessions: Request['session'][] = []
|
||||
|
||||
for (const key of keys) {
|
||||
const sessionData = await this.redisService.get(key)
|
||||
if (sessionData) {
|
||||
const session = JSON.parse(sessionData) as Request['session']
|
||||
|
||||
if (session.userId === userId) {
|
||||
userSessions.push({
|
||||
...session,
|
||||
id: key.split(':')[1],
|
||||
} as Request['session'])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment -- todo fixme
|
||||
// @ts-expect-error
|
||||
userSessions.sort((a, b) => b.createdAt - a.createdAt)
|
||||
|
||||
return userSessions.filter(session => session.id !== req.session.id)
|
||||
}
|
||||
|
||||
public async findCurrentSession(req: Request) {
|
||||
const sessionId = req.session.id
|
||||
const key = `${this.configService.getOrThrow<string>('SESSION_FOLDER')}${sessionId}`
|
||||
const sessionData = await this.redisService.get(key)
|
||||
|
||||
if (!sessionData) {
|
||||
throw new NotFoundException('Сессия не найдена')
|
||||
}
|
||||
|
||||
const session = JSON.parse(sessionData) as SessionData
|
||||
|
||||
return {
|
||||
...session,
|
||||
id: sessionId,
|
||||
}
|
||||
}
|
||||
|
||||
public async login(req: Request, input: LoginInput, userAgent: string) {
|
||||
const { login, password } = input
|
||||
|
||||
const user = await this.prismaService.user.findFirst({ where: {
|
||||
@ -39,6 +92,7 @@ export class SessionService {
|
||||
return new Promise((resolve, reject) => {
|
||||
req.session.userId = user.id
|
||||
req.session.createdAt = new Date()
|
||||
req.session.metadata = getSessionMetadata(req, userAgent)
|
||||
|
||||
req.session.save((error) => {
|
||||
if (error) {
|
||||
@ -62,4 +116,21 @@ export class SessionService {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
public clearSession(req: Request) {
|
||||
req.res?.clearCookie(this.configService.getOrThrow('SESSION_NAME'))
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
public async remove(req: Request, id: string) {
|
||||
if (req.session.id === id) {
|
||||
throw new ConflictException('Текущую сессию удалить нельзя')
|
||||
}
|
||||
|
||||
const key = `${this.configService.getOrThrow<string>('SESSION_FOLDER')}${id}`
|
||||
await this.redisService.del(key)
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
14
backend/src/shared/decorators/user-agent.decorator.ts
Normal file
14
backend/src/shared/decorators/user-agent.decorator.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { GqlContext } from '@/src/shared/types/gql-context.types'
|
||||
import { createParamDecorator } from '@nestjs/common'
|
||||
import { GqlExecutionContext } from '@nestjs/graphql'
|
||||
import { Request } from 'express'
|
||||
|
||||
export const UserAgent = createParamDecorator(
|
||||
(data: unknown, ctx) => {
|
||||
const req: Request = ctx.getType() === 'http'
|
||||
? ctx.switchToHttp().getRequest()
|
||||
: GqlExecutionContext.create(ctx).getContext<GqlContext>().req
|
||||
|
||||
return req.headers['user-agent']
|
||||
},
|
||||
)
|
||||
@ -1,8 +1,10 @@
|
||||
import 'express-session'
|
||||
import { SessionInfo } from './session-metadata.types'
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
userId?: string
|
||||
createdAt?: Date | string
|
||||
metadata: SessionInfo
|
||||
}
|
||||
}
|
||||
|
||||
18
backend/src/shared/types/session-metadata.types.ts
Normal file
18
backend/src/shared/types/session-metadata.types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export interface LocationInfo {
|
||||
country: string
|
||||
city: string
|
||||
longitude: number
|
||||
latitude: number
|
||||
}
|
||||
|
||||
export interface DeviceInfo {
|
||||
browser: string
|
||||
os: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface SessionInfo {
|
||||
location: LocationInfo
|
||||
device: DeviceInfo
|
||||
ip: string
|
||||
}
|
||||
43
backend/src/shared/util/session-metadata.util.ts
Normal file
43
backend/src/shared/util/session-metadata.util.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { SessionInfo } from '@/src/shared/types/session-metadata.types'
|
||||
import { IS_DEV } from '@/src/shared/util/is-dev.util'
|
||||
import { Request } from 'express'
|
||||
import { lookup } from 'geoip-lite'
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
import DeviceDetector = require('device-detector-js')
|
||||
import * as countries from 'i18n-iso-countries'
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-unsafe-argument
|
||||
countries.registerLocale(require('i18n-iso-countries/langs/en.json'))
|
||||
|
||||
export function getSessionMetadata(req: Request, userAgent: string): SessionInfo {
|
||||
const ip = IS_DEV
|
||||
? '174.136.85.11'
|
||||
: (
|
||||
Array.isArray(req.headers['cf-connecting-ip'])
|
||||
? req.headers['cf-connecting-ip'][0]
|
||||
: req.headers['cf-connecting-ip']
|
||||
)
|
||||
|| (
|
||||
typeof req.headers['x-forwarded-for'] === 'string'
|
||||
? req.headers['x-forwarded-for'].split(',')[0]
|
||||
: req.ip
|
||||
) as string
|
||||
|
||||
const location = lookup(ip)
|
||||
const device = new DeviceDetector().parse(userAgent)
|
||||
|
||||
return {
|
||||
ip,
|
||||
device: {
|
||||
browser: device.client?.name ?? 'Неизвестно',
|
||||
os: device.os?.name ?? 'Неизвестно',
|
||||
type: device.device?.type ?? 'Неизвестно',
|
||||
},
|
||||
location: {
|
||||
country: countries.getName(location?.country ?? '', 'en') ?? 'Неизвестно',
|
||||
city: location?.city ?? 'Неизвестно',
|
||||
longitude: location?.ll[0] ?? 0,
|
||||
latitude: location?.ll[1] ?? 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -1771,6 +1771,11 @@
|
||||
"@types/qs" "*"
|
||||
"@types/serve-static" "*"
|
||||
|
||||
"@types/geoip-lite@^1.4.4":
|
||||
version "1.4.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/geoip-lite/-/geoip-lite-1.4.4.tgz#6573056a23129f25dcf9d53ca36bc3770192f109"
|
||||
integrity sha512-2uVfn+C6bX/H356H6mjxsWUA5u8LO8dJgSBIRO/NFlpMe4DESzacutD/rKYrTDKm1Ugv78b4Wz1KvpHrlv3jSw==
|
||||
|
||||
"@types/graceful-fs@^4.1.3":
|
||||
version "4.1.9"
|
||||
resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.9.tgz#2a06bc0f68a20ab37b3e36aa238be6abdf49e8b4"
|
||||
@ -2446,6 +2451,13 @@ async-retry@^1.2.1:
|
||||
dependencies:
|
||||
retry "0.13.1"
|
||||
|
||||
"async@2.1 - 2.6.4":
|
||||
version "2.6.4"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-2.6.4.tgz#706b7ff6084664cd7eae713f6f965433b5504221"
|
||||
integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
|
||||
dependencies:
|
||||
lodash "^4.17.14"
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.6"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce"
|
||||
@ -2733,7 +2745,7 @@ caniuse-lite@^1.0.30001718:
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz#c4f3174f02089720736e1887eab345e09bb10944"
|
||||
integrity sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==
|
||||
|
||||
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
|
||||
"chalk@4.1 - 4.1.2", chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@ -3124,6 +3136,11 @@ detect-newline@^3.0.0:
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
|
||||
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
|
||||
|
||||
device-detector-js@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/device-detector-js/-/device-detector-js-3.0.3.tgz#03424a45962bd80294e8004a2557dc7e7b98247e"
|
||||
integrity sha512-jM89LJAvP6uOd84at8OlD9dWP8KeYCCHUde0RT0HQo/stdoRH4b54Xl/fntx2nEXCmqiFhmo+/cJetS2VGUHPw==
|
||||
|
||||
dezalgo@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81"
|
||||
@ -3132,6 +3149,11 @@ dezalgo@^1.0.4:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
diacritics@1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/diacritics/-/diacritics-1.3.0.tgz#3efa87323ebb863e6696cebb0082d48ff3d6f7a1"
|
||||
integrity sha512-wlwEkqcsaxvPJML+rDh/2iS824jbREk6DUMUKkEaSlxdYHeS43cClJtsWglvw2RfeXGm6ohKDqsXteJ5sP5enA==
|
||||
|
||||
diff-sequences@^29.6.3:
|
||||
version "29.6.3"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921"
|
||||
@ -3606,6 +3628,13 @@ fb-watchman@^2.0.0:
|
||||
dependencies:
|
||||
bser "2.1.1"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fflate@^0.8.2:
|
||||
version "0.8.2"
|
||||
resolved "https://registry.yarnpkg.com/fflate/-/fflate-0.8.2.tgz#fc8631f5347812ad6028bbe4a2308b2792aa1dea"
|
||||
@ -3825,6 +3854,19 @@ gensync@^1.0.0-beta.2:
|
||||
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
|
||||
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
|
||||
|
||||
geoip-lite@^1.4.10:
|
||||
version "1.4.10"
|
||||
resolved "https://registry.yarnpkg.com/geoip-lite/-/geoip-lite-1.4.10.tgz#138256ba97f1abee1afdec78df97a78d320c50f6"
|
||||
integrity sha512-4N69uhpS3KFd97m00wiFEefwa+L+HT5xZbzPhwu+sDawStg6UN/dPwWtUfkQuZkGIY1Cj7wDVp80IsqNtGMi2w==
|
||||
dependencies:
|
||||
async "2.1 - 2.6.4"
|
||||
chalk "4.1 - 4.1.2"
|
||||
iconv-lite "0.4.13 - 0.6.3"
|
||||
ip-address "5.8.9 - 5.9.4"
|
||||
lazy "1.0.11"
|
||||
rimraf "2.5.2 - 2.7.1"
|
||||
yauzl "2.9.2 - 2.10.0"
|
||||
|
||||
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"
|
||||
@ -4042,6 +4084,20 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
i18n-iso-countries@^7.14.0:
|
||||
version "7.14.0"
|
||||
resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-7.14.0.tgz#cd5ae098198bce1cc40cadbf0a37ce6c8e9d0edf"
|
||||
integrity sha512-nXHJZYtNrfsi1UQbyRqm3Gou431elgLjKl//CYlnBGt5aTWdRPH1PiS2T/p/n8Q8LnqYqzQJik3Q7mkwvLokeg==
|
||||
dependencies:
|
||||
diacritics "1.3.0"
|
||||
|
||||
"iconv-lite@0.4.13 - 0.6.3", iconv-lite@0.6.3, iconv-lite@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@ -4049,13 +4105,6 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@0.6.3, iconv-lite@^0.6.3:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501"
|
||||
integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3.0.0"
|
||||
|
||||
ieee754@^1.1.13, ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@ -4112,7 +4161,7 @@ inspect-with-kind@^1.0.5:
|
||||
dependencies:
|
||||
kind-of "^6.0.2"
|
||||
|
||||
ioredis@5.6.1:
|
||||
ioredis@^5.6.1:
|
||||
version "5.6.1"
|
||||
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.6.1.tgz#1ed7dc9131081e77342503425afceaf7357ae599"
|
||||
integrity sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==
|
||||
@ -4127,6 +4176,15 @@ ioredis@5.6.1:
|
||||
redis-parser "^3.0.0"
|
||||
standard-as-callback "^2.1.0"
|
||||
|
||||
"ip-address@5.8.9 - 5.9.4":
|
||||
version "5.9.4"
|
||||
resolved "https://registry.yarnpkg.com/ip-address/-/ip-address-5.9.4.tgz#4660ac261ad61bd397a860a007f7e98e4eaee386"
|
||||
integrity sha512-dHkI3/YNJq4b/qQaz+c8LuarD3pY24JqZWfjB8aZx1gtpc2MDILu9L9jpZe1sHpzo/yWFweQVn+U//FhazUxmw==
|
||||
dependencies:
|
||||
jsbn "1.1.0"
|
||||
lodash "^4.17.15"
|
||||
sprintf-js "1.1.2"
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
@ -4678,6 +4736,11 @@ js-yaml@^4.1.0:
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsbn@1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-1.1.0.tgz#b01307cb29b618a1ed26ec79e911f803c4da0040"
|
||||
integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==
|
||||
|
||||
jsesc@^3.0.2:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
|
||||
@ -4744,6 +4807,11 @@ kleur@^3.0.3:
|
||||
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
|
||||
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
|
||||
|
||||
lazy@1.0.11:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690"
|
||||
integrity sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA==
|
||||
|
||||
leven@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
|
||||
@ -4821,7 +4889,7 @@ lodash.sortby@^4.7.0:
|
||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==
|
||||
|
||||
lodash@4.17.21, lodash@^4.17.21:
|
||||
lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
@ -5596,6 +5664,13 @@ reusify@^1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
|
||||
integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
|
||||
|
||||
"rimraf@2.5.2 - 2.7.1":
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||
dependencies:
|
||||
glob "^7.1.3"
|
||||
|
||||
router@^2.2.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/router/-/router-2.2.0.tgz#019be620b711c87641167cc79b99090f00b146ef"
|
||||
@ -5874,6 +5949,11 @@ source-map@^0.6.0, source-map@^0.6.1:
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
sprintf-js@1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673"
|
||||
integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==
|
||||
|
||||
sprintf-js@~1.0.2:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||
@ -6612,6 +6692,14 @@ yargs@^17.3.1:
|
||||
y18n "^5.0.5"
|
||||
yargs-parser "^21.1.1"
|
||||
|
||||
"yauzl@2.9.2 - 2.10.0":
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
yauzl@^3.1.2:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-3.2.0.tgz#7b6cb548f09a48a6177ea0be8ece48deb7da45c0"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user