commit ffb4a59047a014d6bb050b67a2fc7bc116be4682 Author: Ismaƫl Bouya Date: Tue Feb 12 18:47:53 2019 +0100 Add LDAP authentication diff --git a/config/default.yaml b/config/default.yaml index 3260c62fc..dcce721b9 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -51,6 +51,19 @@ redis: auth: null db: 0 +auth: + local: + enabled: true + ldap: + enabled: true + url: ldap://localhost:389/dc=example,dc=com + insecure_tls: false + bind_dn: cn=admin,dc=example,dc=com + bind_password: adminPass + base: dc=example,dc=com + mail_entry: "mail" + user_filter: "(|(email=%username%)(uid=%username%))" + smtp: hostname: null port: 465 diff --git a/config/production.yaml.example b/config/production.yaml.example index 30cd2ffe0..c56691bf4 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example @@ -51,6 +51,19 @@ redis: auth: null db: 0 +auth: + local: + enabled: true + ldap: + enabled: true + url: ldap://localhost:389/dc=example,dc=com + insecure_tls: false + bind_dn: cn=admin,dc=example,dc=com + bind_password: adminPass + base: dc=example,dc=com + mail_entry: "mail" + user_filter: "(|(email=%username%)(uid=%username%))" + # SMTP server to send emails smtp: hostname: null diff --git a/package.json b/package.json index 49d9faf97..31eccf797 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "iso-639-3": "^1.0.1", "js-yaml": "^3.5.4", "jsonld": "~2.0.1", + "ldapjs": "^1.0.2", "lodash": "^4.17.10", "lru-cache": "^5.1.1", "magnet-uri": "^5.1.4", diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 7fd77f3e8..45a667826 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts @@ -34,6 +34,21 @@ const CONFIG = { AUTH: config.has('redis.auth') ? config.get('redis.auth') : null, DB: config.has('redis.db') ? config.get('redis.db') : null }, + AUTH: { + LOCAL: { + ENABLED: config.has('auth.local.enabled') ? config.get('auth.local.enabled') : true, + }, + LDAP: { + ENABLED: config.has('auth.ldap.enabled') ? config.get('auth.ldap.enabled') : false, + URL: config.has('auth.ldap.url') ? config.get('auth.ldap.url') : null, + INSECURE_TLS: config.has('auth.ldap.insecure_tls') ? config.get('auth.ldap.insecure_tls') : false, + BIND_DN: config.has('auth.ldap.bind_dn') ? config.get('auth.ldap.bind_dn') : null, + BIND_PASSWORD: config.has('auth.ldap.bind_password') ? config.get('auth.ldap.bind_password') : null, + BASE: config.has('auth.ldap.base') ? config.get('auth.ldap.base') : null, + MAIL_ENTRY: config.has('auth.ldap.mail_entry') ? config.get('auth.ldap.mail_entry') : 'mail', + USER_FILTER: config.has('auth.ldap.user_filter') ? config.get('auth.ldap.user_filter') : null + }, + }, SMTP: { HOSTNAME: config.get('smtp.hostname'), PORT: config.get('smtp.port'), diff --git a/server/initializers/migrations/0375-user-ldap-dn.ts b/server/initializers/migrations/0375-user-ldap-dn.ts new file mode 100644 index 000000000..a9d68124b --- /dev/null +++ b/server/initializers/migrations/0375-user-ldap-dn.ts @@ -0,0 +1,26 @@ +import * as Sequelize from 'sequelize' + +async function up (utils: { + transaction: Sequelize.Transaction, + queryInterface: Sequelize.QueryInterface, + sequelize: Sequelize.Sequelize +}): Promise { + + { + const data = { + type: Sequelize.STRING, + allowNull: true, + defaultValue: null + } + await utils.queryInterface.addColumn('user', 'ldapDn', data) + } +} + +function down (options) { + throw new Error('Not implemented.') +} + +export { + up, + down +} diff --git a/server/lib/ldap.ts b/server/lib/ldap.ts new file mode 100644 index 000000000..e6601e5cb --- /dev/null +++ b/server/lib/ldap.ts @@ -0,0 +1,89 @@ +import * as express from 'express' +import { createClient, Client, parseFilter } from 'ldapjs' +import { logger } from '../helpers/logger' +import { CONFIG } from '../initializers/config' + +class Ldap { + + private static instance: Ldap + private initialized = false + private client: Client + private prefix: string + + private constructor () {} + + init () { + // Already initialized + if (this.initialized === true) return + this.initialized = true + + this.client = createClient(Ldap.getLdapClientOptions()) + } + + static getLdapClientOptions () { + return Object.assign({}, { + url: CONFIG.AUTH.LDAP.URL, + reconnect: true, + tlsOptions: { rejectUnauthorized: !CONFIG.AUTH.LDAP.INSECURE_TLS } + }) + } + + getClient () { + this.init() + return this.client + } + + findUser (username: string) { + const filter = parseFilter(CONFIG.AUTH.LDAP.USER_FILTER) + filter.forEach(function (element) { + if (element.value === '%username%') element.value = username + }) + const opts = { + filter, + scope: 'sub', + attributes: [ CONFIG.AUTH.LDAP.MAIL_ENTRY, 'dn' ] + } + + const client = this.getClient() + + return new Promise(function (resolve, reject) { + client.bind(CONFIG.AUTH.LDAP.BIND_DN, CONFIG.AUTH.LDAP.BIND_PASSWORD, function (err) { + if (err) reject(err) + let entries = [] + client.search(CONFIG.AUTH.LDAP.BASE, opts, function (err, search) { + if (err) reject(err) + search.on('searchEntry', function (entry) { + entries.push(entry.object) + }) + search.on('end', function (result) { + if (entries.length === 1) { + resolve(entries[0]) + } else { + reject("No user found corresponding to this username") + } + }) + }) + }) + }) + } + + checkUser (dn: string, password: string) { + const client = this.getClient() + return new Promise(function (resolve, reject) { + client.bind(dn, password, function (err) { + resolve(!err) + }) + }) + } + + + static get Instance () { + return this.instance || (this.instance = new this()) + } +} + +// --------------------------------------------------------------------------- + +export { + Ldap +} diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 086856f41..ab10effd0 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts @@ -9,6 +9,7 @@ import { Transaction } from 'sequelize' import { CONFIG } from '../initializers/config' import * as LRUCache from 'lru-cache' import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' +import { MUserDefault } from '@server/typings/models' type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } @@ -74,7 +75,13 @@ function getRefreshToken (refreshToken: string) { async function getUser (usernameOrEmail: string, password: string) { logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') - const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) + let user : MUserDefault + if (CONFIG.AUTH.LDAP.ENABLED) { + user = await UserModel.findOrCreateLDAPUser(usernameOrEmail) + } + if (!user && CONFIG.AUTH.LOCAL.ENABLED) { + user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) + } if (!user) return null const passwordMatch = await user.isPasswordMatch(password) diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 4c2c5e278..0b38f7cb2 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts @@ -1,4 +1,5 @@ import { FindOptions, literal, Op, QueryTypes, where, fn, col } from 'sequelize' +import { Ldap } from '../../lib/ldap' import { AfterDestroy, AfterUpdate, @@ -50,7 +51,9 @@ import { AccountModel } from './account' import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' import { values } from 'lodash' import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' +import { CONFIG } from '../../initializers/config' import { clearCacheByUserId } from '../../lib/oauth-model' +import { createUserAccountAndChannelAndPlaylist } from '../../lib/user' import { UserNotificationSettingModel } from './user-notification-setting' import { VideoModel } from '../video/video' import { ActorModel } from '../activitypub/actor' @@ -149,6 +152,11 @@ export class UserModel extends Model { @Column(DataType.STRING(400)) pendingEmail: string + @AllowNull(true) + @Default(null) + @Column + ldapDn: string + @AllowNull(true) @Default(null) @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true)) @@ -440,6 +448,48 @@ export class UserModel extends Model { return UserModel.findOne(query) } + static loadByLdapDn (ldapDn: string) { + const query = { + where: { + ldapDn + } + } + + return UserModel.findOne(query) + } + + static async findOrCreateLDAPUser (username: string) { + try { + const userInfos = await Ldap.Instance.findUser(username) + const user = await UserModel.loadByLdapDn(userInfos['dn']) + if (user) { + return user + } else { + return await UserModel.createLDAPUser(username, userInfos) + } + } catch (e) { + return null + } + } + + static async createLDAPUser (username: string, userInfos: {}) { + const userToCreate = new UserModel({ + username, + password: 'SomeInvalidPassword', + email: userInfos[CONFIG.AUTH.LDAP.MAIL_ENTRY], + ldapDn: userInfos['dn'], + nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, + autoPlayVideo: true, + role: UserRole.USER, + videoQuota: CONFIG.USER.VIDEO_QUOTA, + videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, + emailVerified: true, + adminFlags: UserAdminFlag.NONE + }) + const { user } = await createUserAccountAndChannelAndPlaylist({ userToCreate }) + return user + } + static loadForMeAPI (username: string): Bluebird { const query = { where: { @@ -627,7 +677,11 @@ export class UserModel extends Model { } isPasswordMatch (password: string) { - return comparePassword(password, this.password) + if (this.ldapDn === null) { + return comparePassword(password, this.password) + } else { + return Ldap.Instance.checkUser(this.ldapDn, password) + } } toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { diff --git a/yarn.lock b/yarn.lock index 76ce7ed27..f087746df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -616,6 +616,11 @@ arraybuffer.slice@~0.0.7: resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz#3bbc4275dd584cc1b10809b89d4e8b63a69e7675" integrity sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog== +asn1@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -623,6 +628,11 @@ asn1@~0.2.3: dependencies: safer-buffer "~2.1.0" +assert-plus@0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" + integrity sha1-7nQAlBMALYTOxyGcasgRgS5yMWA= + assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" @@ -692,6 +702,13 @@ backo2@1.0.2: resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" integrity sha1-MasayLEpNjRj41s+u2n038+6eUc= +backoff@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/backoff/-/backoff-2.5.0.tgz#f616eda9d3e4b66b8ca7fca79f695722c5f8e26f" + integrity sha1-9hbtqdPktmuMp/ynn2lXIsX44m8= + dependencies: + precond "0.2" + balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -1001,6 +1018,16 @@ bull@^3.4.2: util.promisify "^1.0.0" uuid "^3.3.3" +bunyan@^1.8.3: + version "1.8.12" + resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.12.tgz#f150f0f6748abdd72aeae84f04403be2ef113797" + integrity sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c= + optionalDependencies: + dtrace-provider "~0.8" + moment "^2.10.6" + mv "~2" + safe-json-stringify "~1" + busboy@^0.2.11: version "0.2.14" resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" @@ -1619,7 +1646,7 @@ d@1, d@^1.0.1: es5-ext "^0.10.50" type "^1.0.1" -dashdash@^1.12.0: +dashdash@^1.12.0, dashdash@^1.14.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= @@ -1845,6 +1872,13 @@ double-ended-queue@^2.1.0-0: resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" integrity sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw= +dtrace-provider@~0.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e" + integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg== + dependencies: + nan "^2.14.0" + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" @@ -2228,6 +2262,11 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.2: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extsprintf@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.2.0.tgz#5ad946c22f5b32ba7f8cd7426711c6e8a3fc2529" + integrity sha1-WtlGwi9bMrp/jNdCZxHG6KP8JSk= + extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" @@ -2567,6 +2606,17 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^6.0.1: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= + dependencies: + inflight "^1.0.4" + inherits "2" + minimatch "2 || 3" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.0.3, glob@^7.1.1, glob@^7.1.3: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" @@ -3356,6 +3406,30 @@ latest-version@^3.0.0: dependencies: package-json "^4.0.0" +ldap-filter@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/ldap-filter/-/ldap-filter-0.2.2.tgz#f2b842be0b86da3352798505b31ebcae590d77d0" + integrity sha1-8rhCvguG2jNSeYUFsx68rlkNd9A= + dependencies: + assert-plus "0.1.5" + +ldapjs@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/ldapjs/-/ldapjs-1.0.2.tgz#544ff7032b7b83c68f0701328d9297aa694340f9" + integrity sha1-VE/3Ayt7g8aPBwEyjZKXqmlDQPk= + dependencies: + asn1 "0.2.3" + assert-plus "^1.0.0" + backoff "^2.5.0" + bunyan "^1.8.3" + dashdash "^1.14.0" + ldap-filter "0.2.2" + once "^1.4.0" + vasync "^1.6.4" + verror "^1.8.1" + optionalDependencies: + dtrace-provider "~0.8" + libxmljs@0.19.7: version "0.19.7" resolved "https://registry.yarnpkg.com/libxmljs/-/libxmljs-0.19.7.tgz#96c2151b0b73f33dd29917edec82902587004e5a" @@ -3724,7 +3798,7 @@ mimic-response@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== -minimatch@3.0.4, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -3825,7 +3899,7 @@ moment-timezone@^0.5.21, moment-timezone@^0.5.25: dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@^2.24.0: +"moment@>= 2.9.0", moment@^2.10.6, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -3898,6 +3972,15 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mv@~2: + version "2.1.1" + resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2" + integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI= + dependencies: + mkdirp "~0.5.1" + ncp "~2.0.0" + rimraf "~2.4.0" + nan@2.14.0, nan@^2.14.0, nan@~2.14.0: version "2.14.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c" @@ -3913,6 +3996,11 @@ ncp@1.0.x: resolved "https://registry.yarnpkg.com/ncp/-/ncp-1.0.1.tgz#d15367e5cb87432ba117d2bf80fdf45aecfb4246" integrity sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY= +ncp@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" + integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= + needle@^2.2.1: version "2.4.0" resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" @@ -4597,6 +4685,11 @@ prebuild-install@^5.3.3: tunnel-agent "^0.6.0" which-pm-runs "^1.0.0" +precond@0.2: + version "0.2.3" + resolved "https://registry.yarnpkg.com/precond/-/precond-0.2.3.tgz#aa9591bcaa24923f1e0f4849d240f47efc1075ac" + integrity sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw= + prepend-http@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -5032,6 +5125,13 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +rimraf@~2.4.0: + version "2.4.5" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da" + integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto= + dependencies: + glob "^6.0.1" + run-parallel-limit@^1.0.3: version "1.0.5" resolved "https://registry.yarnpkg.com/run-parallel-limit/-/run-parallel-limit-1.0.5.tgz#c29a4fd17b4df358cb52a8a697811a63c984f1b7" @@ -5069,6 +5169,11 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== +safe-json-stringify@~1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd" + integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg== + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -6337,7 +6442,14 @@ vary@^1, vary@~1.1.2: resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= -verror@1.10.0: +vasync@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/vasync/-/vasync-1.6.4.tgz#dfe93616ad0e7ae801b332a9d88bfc5cdc8e1d1f" + integrity sha1-3+k2Fq0OeugBszKp2Iv8XNyOHR8= + dependencies: + verror "1.6.0" + +verror@1.10.0, verror@^1.8.1: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= @@ -6346,6 +6458,13 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +verror@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.6.0.tgz#7d13b27b1facc2e2da90405eb5ea6e5bdd252ea5" + integrity sha1-fROyex+swuLakEBetepuW90lLqU= + dependencies: + extsprintf "1.2.0" + videostream@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/videostream/-/videostream-3.2.1.tgz#643688ad4bfbf37570d421e3196b7e0ad38eeebc"