1
0
Fork 0

Implement user blocking on server side

This commit is contained in:
Chocobozzz 2018-08-08 14:58:21 +02:00
parent 6b09aba90d
commit e69219184b
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
15 changed files with 287 additions and 59 deletions

View File

@ -55,7 +55,11 @@ export class LoginComponent extends FormReactive implements OnInit {
.subscribe(
() => this.redirectService.redirectToHomepage(),
err => this.error = err.message
err => {
if (err.message.indexOf('credentials are invalid') !== -1) this.error = this.i18n('Incorrect username or password.')
else if (err.message.indexOf('blocked') !== -1) this.error = this.i18n('You account is blocked.')
else this.error = err.message
}
)
}

View File

@ -78,6 +78,7 @@
"@types/bluebird": "3.5.21"
},
"dependencies": {
"@types/oauth2-server": "^3.0.8",
"async": "^2.0.0",
"async-lock": "^1.1.2",
"async-lru": "^1.1.1",
@ -113,6 +114,7 @@
"morgan": "^1.5.3",
"multer": "^1.1.0",
"nodemailer": "^4.4.2",
"oauth2-server": "^3.0.0",
"parse-torrent": "^6.0.0",
"password-generator": "^2.0.2",
"pem": "^1.12.3",

View File

@ -32,6 +32,7 @@ import {
import {
deleteMeValidator,
usersAskResetPasswordValidator,
usersBlockingValidator,
usersResetPasswordValidator,
videoImportsSortValidator,
videosSortValidator
@ -108,6 +109,19 @@ usersRouter.get('/',
asyncMiddleware(listUsers)
)
usersRouter.post('/:id/block',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
asyncMiddleware(usersBlockingValidator),
asyncMiddleware(blockUser)
)
usersRouter.post('/:id/unblock',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
asyncMiddleware(usersBlockingValidator),
asyncMiddleware(unblockUser)
)
usersRouter.get('/:id',
authenticate,
ensureUserHasRight(UserRight.MANAGE_USERS),
@ -278,6 +292,22 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
return res.json(data)
}
async function unblockUser (req: express.Request, res: express.Response, next: express.NextFunction) {
const user: UserModel = res.locals.user
await changeUserBlock(res, user, false)
return res.status(204).end()
}
async function blockUser (req: express.Request, res: express.Response, next: express.NextFunction) {
const user: UserModel = res.locals.user
await changeUserBlock(res, user, true)
return res.status(204).end()
}
function getUser (req: express.Request, res: express.Response, next: express.NextFunction) {
return res.json((res.locals.user as UserModel).toFormattedJSON())
}
@ -423,3 +453,21 @@ async function resetUserPassword (req: express.Request, res: express.Response, n
function success (req: express.Request, res: express.Response, next: express.NextFunction) {
res.end()
}
async function changeUserBlock (res: express.Response, user: UserModel, block: boolean) {
const oldUserAuditView = new UserAuditView(user.toFormattedJSON())
user.blocked = block
await sequelizeTypescript.transaction(async t => {
await OAuthTokenModel.deleteUserToken(user.id, t)
await user.save({ transaction: t })
})
auditLogger.update(
res.locals.oauth.token.User.Account.Actor.getIdentifier(),
new UserAuditView(user.toFormattedJSON()),
oldUserAuditView
)
}

View File

@ -2,7 +2,7 @@ import 'express-validator'
import * as validator from 'validator'
import { UserRole } from '../../../shared'
import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers'
import { exists, isFileValid } from './misc'
import { exists, isFileValid, isBooleanValid } from './misc'
import { values } from 'lodash'
const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS
@ -29,17 +29,17 @@ function isUserDescriptionValid (value: string) {
return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.DESCRIPTION))
}
function isBoolean (value: any) {
return typeof value === 'boolean' || (typeof value === 'string' && validator.isBoolean(value))
}
const nsfwPolicies = values(NSFW_POLICY_TYPES)
function isUserNSFWPolicyValid (value: any) {
return exists(value) && nsfwPolicies.indexOf(value) !== -1
}
function isUserAutoPlayVideoValid (value: any) {
return isBoolean(value)
return isBooleanValid(value)
}
function isUserBlockedValid (value: any) {
return isBooleanValid(value)
}
function isUserRoleValid (value: any) {
@ -57,6 +57,7 @@ function isAvatarFile (files: { [ fieldname: string ]: Express.Multer.File[] } |
// ---------------------------------------------------------------------------
export {
isUserBlockedValid,
isUserPasswordValid,
isUserRoleValid,
isUserVideoQuotaValid,

View File

@ -15,7 +15,7 @@ let config: IConfig = require('config')
// ---------------------------------------------------------------------------
const LAST_MIGRATION_VERSION = 240
const LAST_MIGRATION_VERSION = 245
// ---------------------------------------------------------------------------

View File

@ -0,0 +1,40 @@
import * as Sequelize from 'sequelize'
import { createClient } from 'redis'
import { CONFIG } from '../constants'
import { JobQueue } from '../../lib/job-queue'
import { initDatabaseModels } from '../database'
async function up (utils: {
transaction: Sequelize.Transaction
queryInterface: Sequelize.QueryInterface
sequelize: Sequelize.Sequelize
}): Promise<any> {
{
const data = {
type: Sequelize.BOOLEAN,
allowNull: true,
defaultValue: null
}
await utils.queryInterface.addColumn('user', 'blocked', data)
}
{
const query = 'UPDATE "user" SET "blocked" = false'
await utils.sequelize.query(query)
}
{
const data = {
type: Sequelize.BOOLEAN,
allowNull: false,
defaultValue: null
}
await utils.queryInterface.changeColumn('user', 'blocked', data)
}
}
function down (options) {
throw new Error('Not implemented.')
}
export { up, down }

View File

@ -1,3 +1,4 @@
import { AccessDeniedError} from 'oauth2-server'
import { logger } from '../helpers/logger'
import { UserModel } from '../models/account/user'
import { OAuthClientModel } from '../models/oauth/oauth-client'
@ -34,6 +35,8 @@ async function getUser (usernameOrEmail: string, password: string) {
const passwordMatch = await user.isPasswordMatch(password)
if (passwordMatch === false) return null
if (user.blocked) throw new AccessDeniedError('User is blocked.')
return user
}
@ -67,9 +70,7 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User
}
const tokenCreated = await OAuthTokenModel.create(tokenToCreate)
const tokenToReturn = Object.assign(tokenCreated, { client, user })
return tokenToReturn
return Object.assign(tokenCreated, { client, user })
}
// ---------------------------------------------------------------------------

View File

@ -39,7 +39,7 @@ function token (req: express.Request, res: express.Response, next: express.NextF
if (err) {
return res.status(err.status)
.json({
error: 'Authentication failed.',
error: err.message,
code: err.name
})
.end()

View File

@ -74,6 +74,26 @@ const usersRemoveValidator = [
}
]
const usersBlockingValidator = [
param('id').isInt().not().isEmpty().withMessage('Should have a valid id'),
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
logger.debug('Checking usersRemove parameters', { parameters: req.params })
if (areValidationErrors(req, res)) return
if (!await checkUserIdExist(req.params.id, res)) return
const user = res.locals.user
if (user.username === 'root') {
return res.status(400)
.send({ error: 'Cannot block the root user' })
.end()
}
return next()
}
]
const deleteMeValidator = [
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
const user: UserModel = res.locals.oauth.token.User
@ -230,6 +250,7 @@ export {
usersAddValidator,
deleteMeValidator,
usersRegisterValidator,
usersBlockingValidator,
usersRemoveValidator,
usersUpdateValidator,
usersUpdateMeValidator,

View File

@ -21,6 +21,7 @@ import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared'
import { User, UserRole } from '../../../shared/models/users'
import {
isUserAutoPlayVideoValid,
isUserBlockedValid,
isUserNSFWPolicyValid,
isUserPasswordValid,
isUserRoleValid,
@ -100,6 +101,12 @@ export class UserModel extends Model<UserModel> {
@Column
autoPlayVideo: boolean
@AllowNull(false)
@Default(false)
@Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean'))
@Column
blocked: boolean
@AllowNull(false)
@Is('UserRole', value => throwIfNotValid(value, isUserRoleValid, 'role'))
@Column

View File

@ -3,6 +3,7 @@ import { logger } from '../../helpers/logger'
import { AccountModel } from '../account/account'
import { UserModel } from '../account/user'
import { OAuthClientModel } from './oauth-client'
import { Transaction } from 'sequelize'
export type OAuthTokenInfo = {
refreshToken: string
@ -125,7 +126,7 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
} as OAuthTokenInfo
})
.catch(err => {
logger.info('getRefreshToken error.', { err })
logger.error('getRefreshToken error.', { err })
throw err
})
}
@ -163,11 +164,12 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> {
})
}
static deleteUserToken (userId: number) {
static deleteUserToken (userId: number, t?: Transaction) {
const query = {
where: {
userId
}
},
transaction: t
}
return OAuthTokenModel.destroy(query)

View File

@ -8,7 +8,7 @@ import { UserRole, VideoImport, VideoImportState } from '../../../../shared'
import {
createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest,
makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers,
updateUser, uploadVideo, userLogin, deleteMe
updateUser, uploadVideo, userLogin, deleteMe, unblockUser, blockUser
} from '../../utils'
import { checkBadCountPagination, checkBadSortPagination, checkBadStartPagination } from '../../utils/requests/check-api-params'
import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../utils/videos/video-imports'
@ -455,17 +455,29 @@ describe('Test users API validators', function () {
})
})
describe('When removing an user', function () {
describe('When blocking/unblocking/removing user', function () {
it('Should fail with an incorrect id', async function () {
await removeUser(server.url, 'blabla', server.accessToken, 400)
await blockUser(server.url, 'blabla', server.accessToken, 400)
await unblockUser(server.url, 'blabla', server.accessToken, 400)
})
it('Should fail with the root user', async function () {
await removeUser(server.url, rootId, server.accessToken, 400)
await blockUser(server.url, rootId, server.accessToken, 400)
await unblockUser(server.url, rootId, server.accessToken, 400)
})
it('Should return 404 with a non existing id', async function () {
await removeUser(server.url, 4545454, server.accessToken, 404)
await blockUser(server.url, 4545454, server.accessToken, 404)
await unblockUser(server.url, 4545454, server.accessToken, 404)
})
it('Should fail with a non admin user', async function () {
await removeUser(server.url, userId, userAccessToken, 403)
await blockUser(server.url, userId, userAccessToken, 403)
await unblockUser(server.url, userId, userAccessToken, 403)
})
})

View File

@ -7,7 +7,7 @@ import {
createUser, flushTests, getBlacklistedVideosList, getMyUserInformation, getMyUserVideoQuotaUsed, getMyUserVideoRating,
getUserInformation, getUsersList, getUsersListPaginationAndSort, getVideosList, killallServers, login, makePutBodyRequest, rateVideo,
registerUser, removeUser, removeVideo, runServer, ServerInfo, testImage, updateMyAvatar, updateMyUser, updateUser, uploadVideo, userLogin,
deleteMe
deleteMe, blockUser, unblockUser
} from '../../utils/index'
import { follow } from '../../utils/server/follows'
import { setAccessTokensToServers } from '../../utils/users/login'
@ -45,28 +45,28 @@ describe('Test users', function () {
const client = { id: 'client', secret: server.client.secret }
const res = await login(server.url, client, server.user, 400)
expect(res.body.error).to.equal('Authentication failed.')
expect(res.body.error).to.contain('client is invalid')
})
it('Should not login with an invalid client secret', async function () {
const client = { id: server.client.id, secret: 'coucou' }
const res = await login(server.url, client, server.user, 400)
expect(res.body.error).to.equal('Authentication failed.')
expect(res.body.error).to.contain('client is invalid')
})
it('Should not login with an invalid username', async function () {
const user = { username: 'captain crochet', password: server.user.password }
const res = await login(server.url, server.client, user, 400)
expect(res.body.error).to.equal('Authentication failed.')
expect(res.body.error).to.contain('credentials are invalid')
})
it('Should not login with an invalid password', async function () {
const user = { username: server.user.username, password: 'mew_three' }
const res = await login(server.url, server.client, user, 400)
expect(res.body.error).to.equal('Authentication failed.')
expect(res.body.error).to.contain('credentials are invalid')
})
it('Should not be able to upload a video', async function () {
@ -493,6 +493,27 @@ describe('Test users', function () {
}
})
it('Should block and unblock a user', async function () {
const user16 = {
username: 'user_16',
password: 'my super password'
}
const resUser = await createUser(server.url, server.accessToken, user16.username, user16.password)
const user16Id = resUser.body.user.id
accessToken = await userLogin(server, user16)
await getMyUserInformation(server.url, accessToken, 200)
await blockUser(server.url, user16Id, server.accessToken)
await getMyUserInformation(server.url, accessToken, 401)
await userLogin(server, user16, 400)
await unblockUser(server.url, user16Id, server.accessToken)
accessToken = await userLogin(server, user16)
await getMyUserInformation(server.url, accessToken, 200)
})
after(async function () {
killallServers([ server ])

View File

@ -134,6 +134,26 @@ function removeUser (url: string, userId: number | string, accessToken: string,
.expect(expectedStatus)
}
function blockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) {
const path = '/api/v1/users'
return request(url)
.post(path + '/' + userId + '/block')
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.expect(expectedStatus)
}
function unblockUser (url: string, userId: number | string, accessToken: string, expectedStatus = 204) {
const path = '/api/v1/users'
return request(url)
.post(path + '/' + userId + '/unblock')
.set('Accept', 'application/json')
.set('Authorization', 'Bearer ' + accessToken)
.expect(expectedStatus)
}
function updateMyUser (options: {
url: string
accessToken: string,
@ -234,6 +254,8 @@ export {
updateUser,
updateMyUser,
getUserInformation,
blockUser,
unblockUser,
askResetPassword,
resetPassword,
updateMyAvatar

121
yarn.lock
View File

@ -191,6 +191,12 @@
"@types/events" "*"
"@types/node" "*"
"@types/oauth2-server@^3.0.8":
version "3.0.8"
resolved "https://registry.yarnpkg.com/@types/oauth2-server/-/oauth2-server-3.0.8.tgz#0b7f5083790732ea00bf8c5e0b04b9fa1f22f22c"
dependencies:
"@types/express" "*"
"@types/parse-torrent-file@*":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/parse-torrent-file/-/parse-torrent-file-4.0.1.tgz#056a6c18f3fac0cd7c6c74540f00496a3225976b"
@ -2713,7 +2719,15 @@ fsevents@^1.2.2:
nan "^2.9.2"
node-pre-gyp "^0.10.0"
fstream@^1.0.0, fstream@^1.0.2:
fstream-ignore@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
dependencies:
fstream "^1.0.0"
inherits "2"
minimatch "^3.0.0"
fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
version "1.0.11"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
dependencies:
@ -3227,7 +3241,7 @@ hashish@~0.0.4:
dependencies:
traverse ">=0.2.4"
hawk@~3.1.3:
hawk@3.1.3, hawk@~3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
dependencies:
@ -4115,13 +4129,13 @@ levn@^0.3.0, levn@~0.3.0:
prelude-ls "~1.1.2"
type-check "~0.3.2"
libxmljs@0.19.1:
version "0.19.1"
resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.1.tgz#bc7a62822c4392363feaab49b116b4786b2d5ada"
libxmljs@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.0.tgz#dd0e635ce752af7701492ceb8c565ab74d494473"
dependencies:
bindings "~1.3.0"
nan "~2.10.0"
node-pre-gyp "~0.10.2"
node-pre-gyp "~0.6.37"
lint-staged@^7.1.0:
version "7.2.0"
@ -4701,7 +4715,7 @@ minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4, minimatch@~3.0.2:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@ -4959,7 +4973,7 @@ node-pre-gyp@0.10.2:
semver "^5.3.0"
tar "^4"
node-pre-gyp@^0.10.0, node-pre-gyp@~0.10.2:
node-pre-gyp@^0.10.0:
version "0.10.3"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc"
dependencies:
@ -4974,6 +4988,22 @@ node-pre-gyp@^0.10.0, node-pre-gyp@~0.10.2:
semver "^5.3.0"
tar "^4"
node-pre-gyp@~0.6.37:
version "0.6.39"
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.39.tgz#c00e96860b23c0e1420ac7befc5044e1d78d8649"
dependencies:
detect-libc "^1.0.2"
hawk "3.1.3"
mkdirp "^0.5.1"
nopt "^4.0.1"
npmlog "^4.0.2"
rc "^1.1.7"
request "2.81.0"
rimraf "^2.6.1"
semver "^5.3.0"
tar "^2.2.1"
tar-pack "^3.4.0"
node-sass@^4.9.0:
version "4.9.2"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.9.2.tgz#5e63fe6bd0f2ae3ac9d6c14ede8620e2b8bdb437"
@ -5133,7 +5163,7 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
oauth2-server@3.0.0:
oauth2-server@3.0.0, oauth2-server@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/oauth2-server/-/oauth2-server-3.0.0.tgz#c46276b74c3d28634d59ee981f76b58a6459cc28"
dependencies:
@ -5838,7 +5868,7 @@ raw-body@~1.1.0:
bytes "1"
string_decoder "0.10"
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
rc@^1.0.1, rc@^1.1.6, rc@^1.1.7, rc@^1.2.7:
version "1.2.8"
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
dependencies:
@ -6044,32 +6074,7 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
request@2.87.0, request@^2.81.0, request@^2.83.0:
version "2.87.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.6.0"
caseless "~0.12.0"
combined-stream "~1.0.5"
extend "~3.0.1"
forever-agent "~0.6.1"
form-data "~2.3.1"
har-validator "~5.0.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.17"
oauth-sign "~0.8.2"
performance-now "^2.1.0"
qs "~6.5.1"
safe-buffer "^5.1.1"
tough-cookie "~2.3.3"
tunnel-agent "^0.6.0"
uuid "^3.1.0"
"request@>=2.9.0 <2.82.0":
request@2.81.0, "request@>=2.9.0 <2.82.0":
version "2.81.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
dependencies:
@ -6096,6 +6101,31 @@ request@2.87.0, request@^2.81.0, request@^2.83.0:
tunnel-agent "^0.6.0"
uuid "^3.0.0"
request@2.87.0, request@^2.81.0, request@^2.83.0:
version "2.87.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e"
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.6.0"
caseless "~0.12.0"
combined-stream "~1.0.5"
extend "~3.0.1"
forever-agent "~0.6.1"
form-data "~2.3.1"
har-validator "~5.0.3"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.17"
oauth-sign "~0.8.2"
performance-now "^2.1.0"
qs "~6.5.1"
safe-buffer "^5.1.1"
tough-cookie "~2.3.3"
tunnel-agent "^0.6.0"
uuid "^3.1.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -7081,6 +7111,19 @@ tar-fs@^1.13.0:
pump "^1.0.0"
tar-stream "^1.1.2"
tar-pack@^3.4.0:
version "3.4.1"
resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f"
dependencies:
debug "^2.2.0"
fstream "^1.0.10"
fstream-ignore "^1.0.5"
once "^1.3.3"
readable-stream "^2.1.4"
rimraf "^2.5.1"
tar "^2.2.1"
uid-number "^0.0.6"
tar-stream@^1.1.2:
version "1.6.1"
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
@ -7093,7 +7136,7 @@ tar-stream@^1.1.2:
to-buffer "^1.1.0"
xtend "^4.0.0"
tar@^2.0.0:
tar@^2.0.0, tar@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
dependencies:
@ -7454,6 +7497,10 @@ uglify-to-browserify@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
uid-number@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
uint64be@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/uint64be/-/uint64be-1.0.1.tgz#1f7154202f2a1b8af353871dda651bf34ce93e95"