xiongzhu 2 سال پیش
والد
کامیت
c261642024

+ 2 - 0
package.json

@@ -21,6 +21,7 @@
     "test:e2e": "jest --config ./test/jest-e2e.json"
   },
   "dependencies": {
+    "@alicloud/dysmsapi20170525": "2.0.23",
     "@nestjs/common": "^9.3.3",
     "@nestjs/config": "^2.3.1",
     "@nestjs/core": "^9.3.3",
@@ -38,6 +39,7 @@
     "handlebars": "^4.7.7",
     "mysql2": "^3.1.2",
     "nodemailer": "^6.9.1",
+    "randomstring": "^1.2.3",
     "reflect-metadata": "^0.1.13",
     "rimraf": "^4.1.2",
     "rxjs": "^7.8.0",

+ 14 - 0
src/aliyun/aliyun.module.ts

@@ -0,0 +1,14 @@
+import { Module } from '@nestjs/common'
+import { TypeOrmModule } from '@nestjs/typeorm'
+import { SmsRecord } from '../sms/entities/sms.entity'
+import { AliyunService } from './aliyun.service'
+import { ConfigModule } from '@nestjs/config'
+import aliyunConfig from './config/aliyun.config'
+
+@Module({
+    imports: [TypeOrmModule.forFeature([SmsRecord]), ConfigModule.forFeature(aliyunConfig)],
+    controllers: [],
+    providers: [AliyunService],
+    exports: [AliyunService]
+})
+export class AliyunModule {}

+ 50 - 0
src/aliyun/aliyun.service.ts

@@ -0,0 +1,50 @@
+import { Inject, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
+import aliyunConfig from './config/aliyun.config'
+import { ConfigType } from '@nestjs/config'
+// This file is auto-generated, don't edit it
+import Dysmsapi20170525, * as $Dysmsapi20170525 from '@alicloud/dysmsapi20170525'
+// 依赖的模块可通过下载工程中的模块依赖文件或右上角的获取 SDK 依赖信息查看
+import OpenApi, * as $OpenApi from '@alicloud/openapi-client'
+import Util, * as $Util from '@alicloud/tea-util'
+import * as $tea from '@alicloud/tea-typescript'
+import { SendSmsResponse } from '@alicloud/dysmsapi20170525'
+import * as randomstring from 'randomstring'
+
+@Injectable()
+export class AliyunService {
+    constructor(
+        @Inject(aliyunConfig.KEY)
+        private readonly aliyunConfiguration: ConfigType<typeof aliyunConfig>
+    ) {}
+
+    public async sendCode(phone: string) {
+        if (!/^1[3-9]\d{9}$/.test(phone)) {
+            throw new InternalServerErrorException('手机号码格式不正确')
+        }
+        let config = new $OpenApi.Config({
+            accessKeyId: this.aliyunConfiguration.accessKeyId,
+            accessKeySecret: this.aliyunConfiguration.accessKeySecret
+        })
+        config.endpoint = `dysmsapi.aliyuncs.com`
+        let client = new Dysmsapi20170525(config)
+        const code = randomstring.generate({ length: 4, charset: 'numeric' })
+        let sendSmsRequest = new $Dysmsapi20170525.SendSmsRequest({
+            phoneNumbers: phone,
+            signName: this.aliyunConfiguration.smsSign,
+            templateCode: this.aliyunConfiguration.smsTemplateCode,
+            templateParam: JSON.stringify({ code })
+        })
+        try {
+            const res: SendSmsResponse = await client.sendSmsWithOptions(sendSmsRequest, new $Util.RuntimeOptions({}))
+            Logger.log(JSON.stringify(res, null, 4))
+            if (res.statusCode === 200 && res.body.code == 'OK') {
+                return { phone, code }
+            } else {
+                throw new InternalServerErrorException(res.body.message)
+            }
+        } catch (error) {
+            Logger.error(error.message)
+            throw new InternalServerErrorException(error.message)
+        }
+    }
+}

+ 19 - 0
src/aliyun/config/aliyun.config.ts

@@ -0,0 +1,19 @@
+import { ConfigService, registerAs } from '@nestjs/config'
+import { config } from 'dotenv'
+
+config()
+
+const configService = new ConfigService()
+
+export default registerAs('aliyun', () => {
+    return {
+        accessKeyId: configService.get<string>('ALIYUN_ACCESS_KEY_ID'),
+        accessKeySecret: configService.get<string>('ALIYUN_ACCESS_KEY_SECRET'),
+        ossEndPoint: configService.get<string>('ALIYUN_OSS_ENDPOINT'),
+        ossBucket: configService.get<string>('ALIYUN_OSS_BUCKET'),
+        ossRegion: configService.get<string>('ALIYUN_OSS_REGION'),
+        ossCdn: configService.get<string>('ALIYUN_OSS_CDN'),
+        smsSign: configService.get<string>('ALIYUN_SMS_SIGN'),
+        smsTemplateCode: configService.get<string>('ALIYUN_SMS_TEMPLATE_CODE')
+    }
+})

+ 5 - 1
src/app.module.ts

@@ -1,3 +1,4 @@
+import { AliyunModule } from './aliyun/aliyun.module'
 import { Module } from '@nestjs/common'
 import { ConfigModule, ConfigService } from '@nestjs/config'
 import { TypeOrmModule } from '@nestjs/typeorm'
@@ -7,6 +8,7 @@ import { UsersModule } from './users/users.module'
 import { ThrottlerModule } from '@nestjs/throttler'
 import { IamModule } from './iam/iam.module'
 import * as Yup from 'yup'
+import { SmsModule } from './sms/sms.module'
 
 @Module({
     imports: [
@@ -50,7 +52,9 @@ import * as Yup from 'yup'
             })
         }),
         IamModule,
-        UsersModule
+        UsersModule,
+        AliyunModule,
+        SmsModule
     ],
     controllers: [AppController],
     providers: [AppService]

+ 15 - 0
src/sms/dto/sms.dto.ts

@@ -0,0 +1,15 @@
+import { IsString, MaxLength } from 'class-validator'
+
+export class SendCodeDto {
+    @IsString()
+    @MaxLength(30)
+    readonly phone: string
+}
+
+export class VerifyCodeDto {
+    @IsString()
+    readonly phone: string
+
+    @IsString()
+    readonly code: string
+}

+ 19 - 0
src/sms/entities/sms.entity.ts

@@ -0,0 +1,19 @@
+import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'
+
+@Entity()
+export class SmsRecord {
+    @PrimaryGeneratedColumn()
+    id: number
+
+    @Column()
+    phone: string
+
+    @Column({ length: 10 })
+    code: string
+
+    @Column()
+    expireAt: Date
+
+    @CreateDateColumn()
+    createdAt: Date
+}

+ 25 - 0
src/sms/sms.controller.ts

@@ -0,0 +1,25 @@
+import { Body, Controller, Post } from '@nestjs/common'
+import { ApiTags } from '@nestjs/swagger'
+import { AuthGuard } from 'src/iam/login/decorators/auth-guard.decorator'
+import { AuthType } from 'src/iam/login/enums/auth-type.enum'
+import { SendCodeDto, VerifyCodeDto } from './dto/sms.dto'
+import { SmsService } from './sms.service'
+
+@ApiTags('sms')
+@AuthGuard(AuthType.None)
+@Controller('/sms')
+export class SmsController {
+    constructor(private readonly smsService: SmsService) {}
+
+    @Post('/sendVerify')
+    public async sendVerify(@Body() body: SendCodeDto) {
+        return await this.smsService.sendVerify(body.phone)
+    }
+
+    @Post('/verify')
+    public async verify(@Body() body: VerifyCodeDto) {
+        return {
+            result: await this.smsService.verify(body.phone, body.code)
+        }
+    }
+}

+ 14 - 0
src/sms/sms.module.ts

@@ -0,0 +1,14 @@
+import { Module } from '@nestjs/common'
+import { AliyunModule } from 'src/aliyun/aliyun.module'
+import { SmsController } from './sms.controller'
+import { SmsService } from './sms.service'
+import { TypeOrmModule } from '@nestjs/typeorm'
+import { SmsRecord } from './entities/sms.entity'
+
+@Module({
+    imports: [TypeOrmModule.forFeature([SmsRecord]), AliyunModule],
+    controllers: [SmsController],
+    providers: [SmsService],
+    exports: [SmsService]
+})
+export class SmsModule {}

+ 37 - 0
src/sms/sms.service.ts

@@ -0,0 +1,37 @@
+import { Injectable } from '@nestjs/common'
+import { InjectRepository } from '@nestjs/typeorm'
+import { AliyunService } from 'src/aliyun/aliyun.service'
+import { SmsRecord } from './entities/sms.entity'
+import { Repository } from 'typeorm'
+
+@Injectable()
+export class SmsService {
+    constructor(
+        private readonly aliyunService: AliyunService,
+        @InjectRepository(SmsRecord)
+        private readonly smsRecordRepo: Repository<SmsRecord>
+    ) {}
+
+    public async sendVerify(phone: string) {
+        let res = await this.aliyunService.sendCode(phone)
+        const smsRecord = new SmsRecord()
+        smsRecord.phone = phone
+        smsRecord.code = res.code
+        smsRecord.expireAt = new Date(Date.now() + 5 * 60 * 1000)
+        await this.smsRecordRepo.save(smsRecord)
+    }
+    
+    public async verify(phone: string, code: string) {
+        const smsRecord = await this.smsRecordRepo.findOneBy({
+            phone: phone,
+            code: code
+        })
+        if (!smsRecord) {
+            return false
+        }
+        if (smsRecord.expireAt < new Date()) {
+            return false
+        }
+        return true
+    }
+}

+ 5 - 4
src/users/entities/users.entity.ts

@@ -11,11 +11,12 @@ export class Users {
     @Column()
     username: string
 
-    @Column({
-        unique: true
-    })
+    @Column({ unique: true, nullable: true })
     email: string
 
-    @Column({ length: 60 })
+    @Column({ length: 60, nullable: true })
     password: string
+
+    @Column({ length: 20, unique: true, nullable: true })
+    phone: string
 }

+ 2 - 1
src/users/users.module.ts

@@ -6,9 +6,10 @@ import { UsersController } from './users.controller'
 import { MailerModule } from '../shared/mailer/mailer.module'
 import { BcryptService } from '../shared/hashing/bcrypt.service'
 import { HashingService } from '../shared/hashing/hashing.service'
+import { AliyunModule } from 'src/aliyun/aliyun.module'
 
 @Module({
-    imports: [TypeOrmModule.forFeature([Users]), MailerModule],
+    imports: [TypeOrmModule.forFeature([Users]), MailerModule, AliyunModule],
     controllers: [UsersController],
     providers: [
         {

+ 26 - 2
src/users/users.service.ts

@@ -1,4 +1,11 @@
-import { Injectable, NotFoundException, HttpException, HttpStatus, BadRequestException } from '@nestjs/common'
+import {
+    Injectable,
+    NotFoundException,
+    HttpException,
+    HttpStatus,
+    BadRequestException,
+    InternalServerErrorException
+} from '@nestjs/common'
 import { Repository, UpdateResult } from 'typeorm'
 import { InjectRepository } from '@nestjs/typeorm'
 import { Users } from './entities/users.entity'
@@ -7,13 +14,16 @@ import { UserDto } from './dto/user.dto'
 import { UserProfileDto } from './dto/user-profile.dto'
 import { UserUpdateDto } from './dto/user-update.dto'
 import { HashingService } from '../shared/hashing/hashing.service'
+import { SmsService } from 'src/sms/sms.service'
+import * as randomstring from 'randomstring'
 
 @Injectable()
 export class UsersService {
     constructor(
         @InjectRepository(Users)
         private readonly userRepository: Repository<Users>,
-        private readonly hashingService: HashingService
+        private readonly hashingService: HashingService,
+        private readonly smsService: SmsService
     ) {}
 
     public async findAll(): Promise<Users[]> {
@@ -44,6 +54,20 @@ export class UsersService {
         return user
     }
 
+    public async loginByPhone(phone: string, code: string) {
+        const verified = await this.smsService.verify(phone, code)
+        if (!verified) {
+            throw new InternalServerErrorException('手机号或验证码错误')
+        }
+        let user = await this.userRepository.findOneBy({ phone: phone })
+        if (!user) {
+            user = new Users()
+            user.phone = phone
+            user.name = '0x' + randomstring.generate({ length: 8, charset: 'alphanumeric' })
+            user.username = phone
+        }
+    }
+
     public async create(userDto: UserDto): Promise<IUsers> {
         try {
             return await this.userRepository.save(userDto)

+ 162 - 0
yarn.lock

@@ -2,6 +2,90 @@
 # yarn lockfile v1
 
 
+"@alicloud/credentials@^2":
+  version "2.2.6"
+  resolved "https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz#24b0c188e8ad4b3c5a51a1bc4d75e9c127771975"
+  integrity sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==
+  dependencies:
+    "@alicloud/tea-typescript" "^1.5.3"
+    httpx "^2.2.0"
+    ini "^1.3.5"
+    kitx "^2.0.0"
+
+"@alicloud/dysmsapi20170525@2.0.23":
+  version "2.0.23"
+  resolved "https://registry.npmmirror.com/@alicloud/dysmsapi20170525/-/dysmsapi20170525-2.0.23.tgz#fd8ce10600464cf3dd94ebcc76d64654b696bfde"
+  integrity sha512-C02xj9S2ZPL13SciChlIY3s5+PiOM13jEGZSn+L92aiWYCBqTlpx9UMwNKBNWImMSOlG71IOSYfsQggaoIY+4Q==
+  dependencies:
+    "@alicloud/endpoint-util" "^0.0.1"
+    "@alicloud/openapi-client" "^0.4.4"
+    "@alicloud/openapi-util" "^0.3.0"
+    "@alicloud/tea-typescript" "^1.7.1"
+    "@alicloud/tea-util" "^1.4.5"
+
+"@alicloud/endpoint-util@^0.0.1":
+  version "0.0.1"
+  resolved "https://registry.npmmirror.com/@alicloud/endpoint-util/-/endpoint-util-0.0.1.tgz#b237f5e04e373abb54c42119377b30bd6afb1a7c"
+  integrity sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==
+  dependencies:
+    "@alicloud/tea-typescript" "^1.5.1"
+    kitx "^2.0.0"
+
+"@alicloud/gateway-spi@^0.0.8":
+  version "0.0.8"
+  resolved "https://registry.npmmirror.com/@alicloud/gateway-spi/-/gateway-spi-0.0.8.tgz#1d251986ed40d8b98690dcac8128fec0c56f0f53"
+  integrity sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==
+  dependencies:
+    "@alicloud/credentials" "^2"
+    "@alicloud/tea-typescript" "^1.7.1"
+
+"@alicloud/openapi-client@^0.4.4":
+  version "0.4.5"
+  resolved "https://registry.npmmirror.com/@alicloud/openapi-client/-/openapi-client-0.4.5.tgz#94beb9ea68d0b34d802518fdba9cced45154fa48"
+  integrity sha512-x1blwhfPOVkH/JCLWFssFRWDL0C75RToun9AwhNV+84gqJB2/GUipm3quHGLon8JiQ0DQ9YBUho2rukSoAvhJQ==
+  dependencies:
+    "@alicloud/credentials" "^2"
+    "@alicloud/gateway-spi" "^0.0.8"
+    "@alicloud/openapi-util" "^0.3.1"
+    "@alicloud/tea-typescript" "^1.7.1"
+    "@alicloud/tea-util" "^1.4.5"
+    "@alicloud/tea-xml" "0.0.2"
+
+"@alicloud/openapi-util@^0.3.0", "@alicloud/openapi-util@^0.3.1":
+  version "0.3.1"
+  resolved "https://registry.npmmirror.com/@alicloud/openapi-util/-/openapi-util-0.3.1.tgz#104f79d91e13a347115c8a416cc2c8f56580af5d"
+  integrity sha512-6mGT+hs+SXismZi/CEkjPhhbn2U3qTT/Qv/RXAYFA1DC3Jk4/YaX3N7RtpgdzOhdD7uI8XtNkaULKHZY3BrtxQ==
+  dependencies:
+    "@alicloud/tea-typescript" "^1.7.1"
+    "@alicloud/tea-util" "^1.3.0"
+    kitx "^2.1.0"
+    sm3 "^1.0.3"
+
+"@alicloud/tea-typescript@^1", "@alicloud/tea-typescript@^1.5.1", "@alicloud/tea-typescript@^1.5.3", "@alicloud/tea-typescript@^1.7.1":
+  version "1.8.0"
+  resolved "https://registry.npmmirror.com/@alicloud/tea-typescript/-/tea-typescript-1.8.0.tgz#aa9b04b6ee53e1b22aa51e224a950ea5bcd966e9"
+  integrity sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==
+  dependencies:
+    "@types/node" "^12.0.2"
+    httpx "^2.2.6"
+
+"@alicloud/tea-util@^1.3.0", "@alicloud/tea-util@^1.4.5":
+  version "1.4.5"
+  resolved "https://registry.npmmirror.com/@alicloud/tea-util/-/tea-util-1.4.5.tgz#ba434dc725883c2f36a9eb9a75a7f82698c317fa"
+  integrity sha512-7NuThYUi90/ivT/ORKusm0NVKlc1khPTtlzTR77xEqSBt7d24Ee/Lo70hx9PWP28nHpIZ1gM0NKYBtpq7HUDlg==
+  dependencies:
+    "@alicloud/tea-typescript" "^1.5.1"
+    kitx "^2.0.0"
+
+"@alicloud/tea-xml@0.0.2":
+  version "0.0.2"
+  resolved "https://registry.npmmirror.com/@alicloud/tea-xml/-/tea-xml-0.0.2.tgz#7c97a38255d5e4f009c437facd3a2afc0ef17f45"
+  integrity sha512-Xs7v5y7YSNSDDYmiDWAC0/013VWPjS3dQU4KezSLva9VGiTVPaL3S7Nk4NrTmAYCG6MKcrRj/nGEDIWL5KRoPg==
+  dependencies:
+    "@alicloud/tea-typescript" "^1"
+    "@types/xml2js" "^0.4.5"
+    xml2js "^0.4.22"
+
 "@ampproject/remapping@^2.2.0":
   version "2.2.1"
   resolved "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
@@ -1040,6 +1124,16 @@
   resolved "https://registry.npmmirror.com/@types/node/-/node-18.15.11.tgz#b3b790f09cb1696cffcec605de025b088fa4225f"
   integrity sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==
 
+"@types/node@^12.0.2":
+  version "12.20.55"
+  resolved "https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240"
+  integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==
+
+"@types/node@^14":
+  version "14.18.42"
+  resolved "https://registry.npmmirror.com/@types/node/-/node-14.18.42.tgz#fa39b2dc8e0eba61bdf51c66502f84e23b66e114"
+  integrity sha512-xefu+RBie4xWlK8hwAzGh3npDz/4VhF6icY/shU+zv/1fNn+ZVG7T7CRwe9LId9sAYRPxI+59QBPuKL3WpyGRg==
+
 "@types/nodemailer-express-handlebars@^4.0.2":
   version "4.0.2"
   resolved "https://registry.npmmirror.com/@types/nodemailer-express-handlebars/-/nodemailer-express-handlebars-4.0.2.tgz#7bc3ac6d4c1fd0ff7550dbf6132a67a611ae15cf"
@@ -1108,6 +1202,13 @@
   dependencies:
     "@types/superagent" "*"
 
+"@types/xml2js@^0.4.5":
+  version "0.4.11"
+  resolved "https://registry.npmmirror.com/@types/xml2js/-/xml2js-0.4.11.tgz#bf46a84ecc12c41159a7bd9cf51ae84129af0e79"
+  integrity sha512-JdigeAKmCyoJUiQljjr7tQG3if9NkqGUgwEUqBvV0N7LM4HyQk7UXCnusRa1lnvXAEYJ8mw8GtZWioagNztOwA==
+  dependencies:
+    "@types/node" "*"
+
 "@types/yargs-parser@*":
   version "21.0.0"
   resolved "https://registry.npmmirror.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
@@ -1506,6 +1607,11 @@ array-union@^2.1.0:
   resolved "https://registry.npmmirror.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
   integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
 
+array-uniq@1.0.2:
+  version "1.0.2"
+  resolved "https://registry.npmmirror.com/array-uniq/-/array-uniq-1.0.2.tgz#5fcc373920775723cfd64d65c64bef53bf9eba6d"
+  integrity sha512-GVYjmpL05al4dNlKJm53mKE4w9OOLiuVHWorsIA3YVz+Hu0hcn6PtE3Ydl0EqU7v+7ABC4mjjWsnLUxbpno+CA==
+
 asap@^2.0.0:
   version "2.0.6"
   resolved "https://registry.npmmirror.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46"
@@ -2930,6 +3036,14 @@ https-proxy-agent@^5.0.0:
     agent-base "6"
     debug "4"
 
+httpx@^2.2.0, httpx@^2.2.6:
+  version "2.2.7"
+  resolved "https://registry.npmmirror.com/httpx/-/httpx-2.2.7.tgz#1e34198146e32ca3305a66c11209559e1cbeba09"
+  integrity sha512-Wjh2JOAah0pdczfqL8NC5378G7jMt0Zcpn8U+yyxAiejjlagzSTQgJHuVvka2VNPQlKfoGehYRc79WKq9E4gDw==
+  dependencies:
+    "@types/node" "^14"
+    debug "^4.1.1"
+
 human-signals@^1.1.1:
   version "1.1.1"
   resolved "https://registry.npmmirror.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@@ -2998,6 +3112,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i
   resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
+ini@^1.3.5:
+  version "1.3.8"
+  resolved "https://registry.npmmirror.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+  integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+
 inquirer@8.2.4:
   version "8.2.4"
   resolved "https://registry.npmmirror.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4"
@@ -3649,6 +3768,13 @@ jws@^3.2.2:
     jwa "^1.4.1"
     safe-buffer "^5.0.1"
 
+kitx@^2.0.0, kitx@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmmirror.com/kitx/-/kitx-2.1.0.tgz#fc7fbf78eb6ed7a5a3fd2d7afb3011e29d0e44c8"
+  integrity sha512-C/5v9MtIX7aHGOjwn5BmrrbNkJSf7i0R5mRzmh13GSAdRqQ7bYQo/Su2pTYNylFicqKNTVX3HML9k1u8k51+pQ==
+  dependencies:
+    "@types/node" "^12.0.2"
+
 kleur@^3.0.3:
   version "3.0.3"
   resolved "https://registry.npmmirror.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
@@ -4388,6 +4514,11 @@ queue-microtask@^1.2.2:
   resolved "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
   integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
 
+randombytes@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec"
+  integrity sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==
+
 randombytes@^2.1.0:
   version "2.1.0"
   resolved "https://registry.npmmirror.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
@@ -4395,6 +4526,14 @@ randombytes@^2.1.0:
   dependencies:
     safe-buffer "^5.1.0"
 
+randomstring@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.npmmirror.com/randomstring/-/randomstring-1.2.3.tgz#49d2bc34ff6bc2bd0f6bb8e7d876e1d4433564c8"
+  integrity sha512-3dEFySepTzp2CvH6W/ASYGguPPveBuz5MpZ7MuoUkoVehmyNl9+F9c9GFVrz2QPbM9NXTIHGcmJDY/3j4677kQ==
+  dependencies:
+    array-uniq "1.0.2"
+    randombytes "2.0.3"
+
 range-parser@~1.2.1:
   version "1.2.1"
   resolved "https://registry.npmmirror.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
@@ -4582,6 +4721,11 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0:
   resolved "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
+sax@>=0.6.0:
+  version "1.2.4"
+  resolved "https://registry.npmmirror.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+  integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+
 schema-utils@^3.1.0, schema-utils@^3.1.1:
   version "3.1.1"
   resolved "https://registry.npmmirror.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281"
@@ -4707,6 +4851,11 @@ slash@^3.0.0:
   resolved "https://registry.npmmirror.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
+sm3@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.npmmirror.com/sm3/-/sm3-1.0.3.tgz#0051f0cc948c983944843136e7baa244eec5cd49"
+  integrity sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==
+
 source-map-support@0.5.13:
   version "0.5.13"
   resolved "https://registry.npmmirror.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932"
@@ -5359,6 +5508,19 @@ write-file-atomic@^4.0.2:
     imurmurhash "^0.1.4"
     signal-exit "^3.0.7"
 
+xml2js@^0.4.22:
+  version "0.4.23"
+  resolved "https://registry.npmmirror.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
+  integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==
+  dependencies:
+    sax ">=0.6.0"
+    xmlbuilder "~11.0.0"
+
+xmlbuilder@~11.0.0:
+  version "11.0.1"
+  resolved "https://registry.npmmirror.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
+  integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
+
 xtend@^4.0.0:
   version "4.0.2"
   resolved "https://registry.npmmirror.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"