diff --git a/package.json b/package.json index 07bfd65431..6e2b362a66 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "unleash-server", "description": "Unleash is an enterprise ready feature toggles service. It provides different strategies for handling feature toggles.", - "version": "4.5.1", + "version": "4.6.0-beta.1", "keywords": [ "unleash", "feature toggle", @@ -111,7 +111,7 @@ "response-time": "^2.3.2", "serve-favicon": "^2.5.0", "stoppable": "^1.1.0", - "unleash-frontend": "4.4.1", + "unleash-frontend": "4.6.0-beta.1", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap index 6ddd68a167..5b90fbfcfc 100644 --- a/src/lib/__snapshots__/create-config.test.ts.snap +++ b/src/lib/__snapshots__/create-config.test.ts.snap @@ -30,13 +30,14 @@ Object { "user": "unleash", "version": undefined, }, + "disableLegacyFeaturesApi": false, "email": Object { - "host": undefined, + "host": "smtp.ethereal.email", "port": 587, "secure": false, "sender": "noreply@unleash-hosted.com", - "smtppass": undefined, - "smtpuser": undefined, + "smtppass": "DtBAy8kzwhMjzbY5UJ", + "smtpuser": "maureen.heaney@ethereal.email", }, "enableOAS": false, "enterpriseVersion": undefined, diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts index 2e4ddcc203..0c558ab9e4 100644 --- a/src/lib/create-config.ts +++ b/src/lib/create-config.ts @@ -287,6 +287,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { const enableOAS = options.enableOAS || safeBoolean(process.env.ENABLE_OAS, false); + const disableLegacyFeaturesApi = + options.disableLegacyFeaturesApi || + safeBoolean(process.env.DISABLE_LEGACY_FEATURES_API, false); + return { db, session, @@ -301,6 +305,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig { email, secureHeaders, enableOAS, + disableLegacyFeaturesApi, preHook: options.preHook, preRouterHook: options.preRouterHook, eventHook: options.eventHook, diff --git a/src/lib/db/access-store.ts b/src/lib/db/access-store.ts index cee7c16fb9..b60ed3982f 100644 --- a/src/lib/db/access-store.ts +++ b/src/lib/db/access-store.ts @@ -7,15 +7,32 @@ import { IAccessStore, IRole, IUserPermission, - IUserRole, } from '../types/stores/access-store'; +import { IPermission } from '../types/model'; +import NotFoundError from '../error/notfound-error'; +import { + ENVIRONMENT_PERMISSION_TYPE, + ROOT_PERMISSION_TYPE, +} from '../util/constants'; const T = { ROLE_USER: 'role_user', ROLES: 'roles', ROLE_PERMISSION: 'role_permission', + PERMISSIONS: 'permissions', + PERMISSION_TYPES: 'permission_types', }; +interface IPermissionRow { + id: number; + permission: string; + display_name: string; + environment?: string; + type: string; + project?: string; + role_id: number; +} + export class AccessStore implements IAccessStore { private logger: Logger; @@ -53,75 +70,141 @@ export class AccessStore implements IAccessStore { } async get(key: number): Promise { - return this.db + const role = await this.db .select(['id', 'name', 'type', 'description']) .where('id', key) .first() .from(T.ROLES); + + if (!role) { + throw new NotFoundError(`Could not find role with id: ${key}`); + } + + return role; } async getAll(): Promise { return Promise.resolve([]); } + async getAvailablePermissions(): Promise { + const rows = await this.db + .select(['id', 'permission', 'type', 'display_name']) + .where('type', 'project') + .orWhere('type', 'environment') + .from(`${T.PERMISSIONS} as p`); + return rows.map(this.mapPermission); + } + + mapPermission(permission: IPermissionRow): IPermission { + return { + id: permission.id, + name: permission.permission, + displayName: permission.display_name, + type: permission.type, + }; + } + async getPermissionsForUser(userId: number): Promise { const stopTimer = this.timer('getPermissionsForUser'); const rows = await this.db - .select('project', 'permission') - .from(`${T.ROLE_PERMISSION} AS rp`) - .leftJoin(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id') + .select( + 'project', + 'permission', + 'environment', + 'type', + 'ur.role_id', + ) + .from(`${T.ROLE_PERMISSION} AS rp`) + .join(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id') + .join(`${T.PERMISSIONS} AS p`, 'p.id', 'rp.permission_id') .where('ur.user_id', '=', userId); stopTimer(); - return rows; + return rows.map(this.mapUserPermission); } - async getPermissionsForRole(roleId: number): Promise { + mapUserPermission(row: IPermissionRow): IUserPermission { + let project: string = undefined; + // Since the editor should have access to the default project, + // we map the project to the project and environment specific + // permissions that are connected to the editor role. + if (row.type !== ROOT_PERMISSION_TYPE) { + project = row.project; + } + + const environment = + row.type === ENVIRONMENT_PERMISSION_TYPE + ? row.environment + : undefined; + + const result = { + project, + environment, + permission: row.permission, + }; + return result; + } + + async getPermissionsForRole(roleId: number): Promise { const stopTimer = this.timer('getPermissionsForRole'); const rows = await this.db - .select('project', 'permission') - .from(`${T.ROLE_PERMISSION}`) - .where('role_id', '=', roleId); + .select( + 'p.id', + 'p.permission', + 'rp.environment', + 'p.display_name', + 'p.type', + ) + .from(`${T.ROLE_PERMISSION} as rp`) + .join(`${T.PERMISSIONS} as p`, 'p.id', 'rp.permission_id') + .where('rp.role_id', '=', roleId); stopTimer(); - return rows; + return rows.map((permission) => { + return { + id: permission.id, + name: permission.permission, + environment: permission.environment, + displayName: permission.display_name, + type: permission.type, + }; + }); } - async getRoles(): Promise { - return this.db - .select(['id', 'name', 'type', 'description']) - .from(T.ROLES); + async addEnvironmentPermissionsToRole( + role_id: number, + permissions: IPermission[], + ): Promise { + const rows = permissions.map((permission) => { + return { + role_id, + permission_id: permission.id, + environment: permission.environment, + }; + }); + this.db.batchInsert(T.ROLE_PERMISSION, rows); } - async getRoleWithId(id: number): Promise { - return this.db - .select(['id', 'name', 'type', 'description']) - .where('id', id) - .first() - .from(T.ROLES); - } - - async getRolesForProject(projectId: string): Promise { - return this.db - .select(['id', 'name', 'type', 'project', 'description']) - .from(T.ROLES) - .where('project', projectId) - .andWhere('type', 'project'); - } - - async getRootRoles(): Promise { - return this.db - .select(['id', 'name', 'type', 'project', 'description']) - .from(T.ROLES) - .andWhere('type', 'root'); - } - - async removeRolesForProject(projectId: string): Promise { - return this.db(T.ROLES) + async unlinkUserRoles(userId: number): Promise { + return this.db(T.ROLE_USER) .where({ - project: projectId, + user_id: userId, }) .delete(); } + async getProjectUserIdsForRole( + roleId: number, + projectId?: string, + ): Promise { + const rows = await this.db + .select(['user_id']) + .from(`${T.ROLE_USER} AS ru`) + .join(`${T.ROLES} as r`, 'ru.role_id', 'id') + .where('r.id', roleId) + .andWhere('ru.project', projectId); + return rows.map((r) => r.user_id); + } + async getRolesForUserId(userId: number): Promise { return this.db .select(['id', 'name', 'type', 'project', 'description']) @@ -138,18 +221,28 @@ export class AccessStore implements IAccessStore { return rows.map((r) => r.user_id); } - async addUserToRole(userId: number, roleId: number): Promise { + async addUserToRole( + userId: number, + roleId: number, + projecId?: string, + ): Promise { return this.db(T.ROLE_USER).insert({ user_id: userId, role_id: roleId, + project: projecId, }); } - async removeUserFromRole(userId: number, roleId: number): Promise { + async removeUserFromRole( + userId: number, + roleId: number, + projectId?: string, + ): Promise { return this.db(T.ROLE_USER) .where({ user_id: userId, role_id: roleId, + project: projectId, }) .delete(); } @@ -168,67 +261,51 @@ export class AccessStore implements IAccessStore { .delete(); } - async createRole( - name: string, - type: string, - project?: string, - description?: string, - ): Promise { - const [id] = await this.db(T.ROLES) - .insert({ - name, - description, - type, - project, - }) - .returning('id'); - return { - id, - name, - description, - type, - project, - }; - } - async addPermissionsToRole( role_id: number, permissions: string[], - projectId?: string, + environment?: string, ): Promise { - const rows = permissions.map((permission) => ({ + const rows = await this.db + .select('id as permissionId') + .from(T.PERMISSIONS) + .whereIn('permission', permissions); + + const newRoles = rows.map((row) => ({ role_id, - project: projectId, - permission, + environment, + permission_id: row.permissionId, })); - return this.db.batchInsert(T.ROLE_PERMISSION, rows); + + return this.db.batchInsert(T.ROLE_PERMISSION, newRoles); } async removePermissionFromRole( - roleId: number, + role_id: number, permission: string, - projectId?: string, + environment?: string, ): Promise { + const rows = await this.db + .select('id as permissionId') + .from(T.PERMISSIONS) + .where('permission', permission); + + const permissionId = rows[0].permissionId; + return this.db(T.ROLE_PERMISSION) .where({ - role_id: roleId, - permission, - project: projectId, + role_id, + permission_id: permissionId, + environment, }) .delete(); } - async getRootRoleForAllUsers(): Promise { - const rows = await this.db - .select('id', 'user_id') - .distinctOn('user_id') - .from(`${T.ROLES} AS r`) - .leftJoin(`${T.ROLE_USER} AS ru`, 'r.id', 'ru.role_id') - .where('r.type', '=', 'root'); - - return rows.map((row) => ({ - roleId: +row.id, - userId: +row.user_id, - })); + async wipePermissionsFromRole(role_id: number): Promise { + return this.db(T.ROLE_PERMISSION) + .where({ + role_id, + }) + .delete(); } } diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 40d36623fb..04dab0866f 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -27,6 +27,7 @@ import FeatureTagStore from './feature-tag-store'; import { FeatureEnvironmentStore } from './feature-environment-store'; import { ClientMetricsStoreV2 } from './client-metrics-store-v2'; import UserSplashStore from './user-splash-store'; +import RoleStore from './role-store'; export const createStores = ( config: IUnleashConfig, @@ -77,6 +78,7 @@ export const createStores = ( getLogger, ), userSplashStore: new UserSplashStore(db, eventBus, getLogger), + roleStore: new RoleStore(db, eventBus, getLogger), }; }; diff --git a/src/lib/db/project-store.ts b/src/lib/db/project-store.ts index 4d19552c44..44b622a156 100644 --- a/src/lib/db/project-store.ts +++ b/src/lib/db/project-store.ts @@ -88,7 +88,7 @@ class ProjectStore implements IProjectStore { const row = await this.db(TABLE) .insert(this.fieldToRow(project)) .returning('*'); - return this.mapRow(row); + return this.mapRow(row[0]); } // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types diff --git a/src/lib/db/role-store.ts b/src/lib/db/role-store.ts new file mode 100644 index 0000000000..a0a71852b9 --- /dev/null +++ b/src/lib/db/role-store.ts @@ -0,0 +1,185 @@ +import EventEmitter from 'events'; +import { Knex } from 'knex'; +import { Logger, LogProvider } from '../logger'; +import NotFoundError from '../error/notfound-error'; +import { ICustomRole } from 'lib/types/model'; +import { + ICustomRoleInsert, + ICustomRoleUpdate, + IRoleStore, +} from 'lib/types/stores/role-store'; +import { IRole, IUserRole } from 'lib/types/stores/access-store'; + +const T = { + ROLE_USER: 'role_user', + ROLES: 'roles', +}; + +const COLUMNS = ['id', 'name', 'description', 'type']; + +interface IRoleRow { + id: number; + name: string; + description: string; + type: string; +} + +export default class RoleStore implements IRoleStore { + private logger: Logger; + + private eventBus: EventEmitter; + + private db: Knex; + + constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) { + this.db = db; + this.eventBus = eventBus; + this.logger = getLogger('lib/db/role-store.ts'); + } + + async getAll(): Promise { + const rows = await this.db + .select(COLUMNS) + .from(T.ROLES) + .orderBy('name', 'asc'); + + return rows.map(this.mapRow); + } + + async create(role: ICustomRoleInsert): Promise { + const row = await this.db(T.ROLES) + .insert({ + name: role.name, + description: role.description, + type: role.roleType, + }) + .returning('*'); + return this.mapRow(row[0]); + } + + async delete(id: number): Promise { + return this.db(T.ROLES).where({ id }).del(); + } + + async get(id: number): Promise { + const rows = await this.db.select(COLUMNS).from(T.ROLES).where({ id }); + if (rows.length === 0) { + throw new NotFoundError(`Could not find role with id: ${id}`); + } + return this.mapRow(rows[0]); + } + + async update(role: ICustomRoleUpdate): Promise { + const rows = await this.db(T.ROLES) + .where({ + id: role.id, + }) + .update({ + id: role.id, + name: role.name, + description: role.description, + }) + .returning('*'); + return this.mapRow(rows[0]); + } + + async exists(id: number): Promise { + const result = await this.db.raw( + `SELECT EXISTS (SELECT 1 FROM ${T.ROLES} WHERE id = ?) AS present`, + [id], + ); + const { present } = result.rows[0]; + return present; + } + + async nameInUse(name: string, existingId?: number): Promise { + let query = this.db(T.ROLES).where({ name }).returning('id'); + if (existingId) { + query = query.andWhereNot({ id: existingId }); + } + const result = await query; + return result.length > 0; + } + + async deleteAll(): Promise { + return this.db(T.ROLES).del(); + } + + mapRow(row: IRoleRow): ICustomRole { + if (!row) { + throw new NotFoundError('No row'); + } + + return { + id: row.id, + name: row.name, + description: row.description, + type: row.type, + }; + } + + async getRoles(): Promise { + return this.db + .select(['id', 'name', 'type', 'description']) + .from(T.ROLES); + } + + async getRoleWithId(id: number): Promise { + return this.db + .select(['id', 'name', 'type', 'description']) + .where('id', id) + .first() + .from(T.ROLES); + } + + async getProjectRoles(): Promise { + return this.db + .select(['id', 'name', 'type', 'description']) + .from(T.ROLES) + .where('type', 'custom') + .orWhere('type', 'project'); + } + + async getRolesForProject(projectId: string): Promise { + return this.db + .select(['r.id', 'r.name', 'r.type', 'ru.project', 'r.description']) + .from(`${T.ROLE_USER} as ru`) + .innerJoin(`${T.ROLES} as r`, 'ru.role_id', 'r.id') + .where('project', projectId); + } + + async getRootRoles(): Promise { + return this.db + .select(['id', 'name', 'type', 'description']) + .from(T.ROLES) + .where('type', 'root'); + } + + async removeRolesForProject(projectId: string): Promise { + return this.db(T.ROLE_USER) + .where({ + project: projectId, + }) + .delete(); + } + + async getRootRoleForAllUsers(): Promise { + const rows = await this.db + .select('id', 'user_id') + .distinctOn('user_id') + .from(`${T.ROLES} AS r`) + .leftJoin(`${T.ROLE_USER} AS ru`, 'r.id', 'ru.role_id') + .where('r.type', '=', 'root'); + + return rows.map((row) => ({ + roleId: Number(row.id), + userId: Number(row.user_id), + })); + } + + async getRoleByName(name: string): Promise { + return this.db(T.ROLES).where({ name }).first(); + } + + destroy(): void {} +} diff --git a/src/lib/error/role-in-use-error.ts b/src/lib/error/role-in-use-error.ts new file mode 100644 index 0000000000..b32ba6b522 --- /dev/null +++ b/src/lib/error/role-in-use-error.ts @@ -0,0 +1,23 @@ +class RoleInUseError extends Error { + constructor(message: string) { + super(); + Error.captureStackTrace(this, this.constructor); + + this.name = this.constructor.name; + this.message = message; + } + + toJSON(): object { + return { + isJoi: true, + name: this.constructor.name, + details: [ + { + message: this.message, + }, + ], + }; + } +} + +export default RoleInUseError; diff --git a/src/lib/middleware/rbac-middleware.test.ts b/src/lib/middleware/rbac-middleware.test.ts index 5615efed5c..66c5916384 100644 --- a/src/lib/middleware/rbac-middleware.test.ts +++ b/src/lib/middleware/rbac-middleware.test.ts @@ -152,6 +152,7 @@ test('should verify permission for root resource', async () => { req.user, perms.ADMIN, undefined, + undefined, ); }); @@ -181,6 +182,7 @@ test('should lookup projectId from params', async () => { req.user, perms.UPDATE_PROJECT, req.params.projectId, + undefined, ); }); @@ -215,6 +217,7 @@ test('should lookup projectId from feature toggle', async () => { req.user, perms.UPDATE_FEATURE, projectId, + undefined, ); }); @@ -249,6 +252,7 @@ test('should lookup projectId from data', async () => { req.user, perms.CREATE_FEATURE, projectId, + undefined, ); }); @@ -275,6 +279,7 @@ test('Does not double check permission if not changing project when updating tog req.user, perms.UPDATE_FEATURE, oldProjectId, + undefined, ); }); @@ -298,6 +303,7 @@ test('UPDATE_TAG_TYPE does not need projectId', async () => { req.user, perms.UPDATE_TAG_TYPE, undefined, + undefined, ); }); @@ -321,5 +327,6 @@ test('DELETE_TAG_TYPE does not need projectId', async () => { req.user, perms.DELETE_TAG_TYPE, undefined, + undefined, ); }); diff --git a/src/lib/middleware/rbac-middleware.ts b/src/lib/middleware/rbac-middleware.ts index 9d74647b7d..117d104886 100644 --- a/src/lib/middleware/rbac-middleware.ts +++ b/src/lib/middleware/rbac-middleware.ts @@ -14,6 +14,7 @@ interface PermissionChecker { user: User, permission: string, projectId?: string, + environment?: string, ): Promise; } @@ -44,7 +45,7 @@ const rbacMiddleware = ( } // For /api/admin/projects/:projectId we will find it as part of params - let { projectId } = params; + let { projectId, environment } = params; // Temporary workaround to figure out projectId for feature toggle updates. // will be removed in Unleash v5.0 @@ -55,7 +56,12 @@ const rbacMiddleware = ( projectId = req.body.project || 'default'; } - return accessService.hasPermission(user, permission, projectId); + return accessService.hasPermission( + user, + permission, + projectId, + environment, + ); }; return next(); }; diff --git a/src/lib/routes/admin-api/index.ts b/src/lib/routes/admin-api/index.ts index f1aa75c7fa..7c29cfb2c9 100644 --- a/src/lib/routes/admin-api/index.ts +++ b/src/lib/routes/admin-api/index.ts @@ -30,10 +30,14 @@ class AdminApi extends Controller { super(config); this.app.get('/', this.index); - this.app.use( - '/features', - new FeatureController(config, services).router, - ); + + if (!config.disableLegacyFeaturesApi) { + this.app.use( + '/features', + new FeatureController(config, services).router, + ); + } + this.app.use( '/feature-types', new FeatureTypeController(config, services).router, diff --git a/src/lib/routes/admin-api/project/features.ts b/src/lib/routes/admin-api/project/features.ts index 33ad91e10a..0c88683d1c 100644 --- a/src/lib/routes/admin-api/project/features.ts +++ b/src/lib/routes/admin-api/project/features.ts @@ -7,8 +7,12 @@ import FeatureToggleService from '../../../services/feature-toggle-service'; import { Logger } from '../../../logger'; import { CREATE_FEATURE, + CREATE_FEATURE_STRATEGY, DELETE_FEATURE, + DELETE_FEATURE_STRATEGY, UPDATE_FEATURE, + UPDATE_FEATURE_ENVIRONMENT, + UPDATE_FEATURE_STRATEGY, } from '../../../types/permissions'; import { FeatureToggleDTO, @@ -70,16 +74,40 @@ export default class ProjectFeaturesController extends Controller { // Environments this.get(`${PATH_ENV}`, this.getEnvironment); - this.post(`${PATH_ENV}/on`, this.toggleEnvironmentOn, UPDATE_FEATURE); - this.post(`${PATH_ENV}/off`, this.toggleEnvironmentOff, UPDATE_FEATURE); + this.post( + `${PATH_ENV}/on`, + this.toggleEnvironmentOn, + UPDATE_FEATURE_ENVIRONMENT, + ); + this.post( + `${PATH_ENV}/off`, + this.toggleEnvironmentOff, + UPDATE_FEATURE_ENVIRONMENT, + ); // activation strategies this.get(`${PATH_STRATEGIES}`, this.getStrategies); - this.post(`${PATH_STRATEGIES}`, this.addStrategy, UPDATE_FEATURE); + this.post( + `${PATH_STRATEGIES}`, + this.addStrategy, + CREATE_FEATURE_STRATEGY, + ); this.get(`${PATH_STRATEGY}`, this.getStrategy); - this.put(`${PATH_STRATEGY}`, this.updateStrategy, UPDATE_FEATURE); - this.patch(`${PATH_STRATEGY}`, this.patchStrategy, UPDATE_FEATURE); - this.delete(`${PATH_STRATEGY}`, this.deleteStrategy, UPDATE_FEATURE); + this.put( + `${PATH_STRATEGY}`, + this.updateStrategy, + UPDATE_FEATURE_STRATEGY, + ); + this.patch( + `${PATH_STRATEGY}`, + this.patchStrategy, + UPDATE_FEATURE_STRATEGY, + ); + this.delete( + `${PATH_STRATEGY}`, + this.deleteStrategy, + DELETE_FEATURE_STRATEGY, + ); // feature toggles this.get(PATH, this.getFeatures); diff --git a/src/lib/routes/admin-api/project/variants.ts b/src/lib/routes/admin-api/project/variants.ts index 415731f0c5..a704ed8e86 100644 --- a/src/lib/routes/admin-api/project/variants.ts +++ b/src/lib/routes/admin-api/project/variants.ts @@ -5,7 +5,7 @@ import { IUnleashConfig } from '../../../types/option'; import { IUnleashServices } from '../../../types'; import { Request, Response } from 'express'; import { Operation } from 'fast-json-patch'; -import { UPDATE_FEATURE } from '../../../types/permissions'; +import { UPDATE_FEATURE_VARIANTS } from '../../../types/permissions'; import { IVariant } from '../../../types/model'; import { extractUsername } from '../../../util/extract-user'; import { IAuthRequest } from '../../unleash-types'; @@ -35,8 +35,8 @@ export default class VariantsController extends Controller { this.logger = config.getLogger('admin-api/project/variants.ts'); this.featureService = featureToggleService; this.get(PREFIX, this.getVariants); - this.patch(PREFIX, this.patchVariants, UPDATE_FEATURE); - this.put(PREFIX, this.overwriteVariants, UPDATE_FEATURE); + this.patch(PREFIX, this.patchVariants, UPDATE_FEATURE_VARIANTS); + this.put(PREFIX, this.overwriteVariants, UPDATE_FEATURE_VARIANTS); } async getVariants( diff --git a/src/lib/routes/util.ts b/src/lib/routes/util.ts index f8b5051328..df4ea5de37 100644 --- a/src/lib/routes/util.ts +++ b/src/lib/routes/util.ts @@ -58,6 +58,8 @@ export const handleErrors: ( return res.status(409).json(error).end(); case 'FeatureHasTagError': return res.status(409).json(error).end(); + case 'RoleInUseError': + return res.status(400).json(error).end(); default: logger.error('Server failed executing request', error); return res.status(500).end(); diff --git a/src/lib/schema/role-schema.test.ts b/src/lib/schema/role-schema.test.ts new file mode 100644 index 0000000000..05fb65b6d0 --- /dev/null +++ b/src/lib/schema/role-schema.test.ts @@ -0,0 +1,83 @@ +import { roleSchema } from './role-schema'; + +test('role schema rejects a role without a name', async () => { + expect.assertions(1); + const role = { + permissions: [], + }; + + try { + await roleSchema.validateAsync(role); + } catch (error) { + expect(error.details[0].message).toBe('"name" is required'); + } +}); + +test('role schema allows a role with an empty description', async () => { + const role = { + name: 'Brønsted', + description: '', + }; + + const value = await roleSchema.validateAsync(role); + expect(value.description).toEqual(''); +}); + +test('role schema rejects a role with a broken permission list', async () => { + expect.assertions(1); + const role = { + name: 'Mendeleev', + permissions: [ + { + aPropertyThatIsAproposToNothing: true, + }, + ], + }; + + try { + await roleSchema.validateAsync(role); + } catch (error) { + expect(error.details[0].message).toBe( + '"permissions[0].id" is required', + ); + } +}); + +test('role schema allows a role with an empty permission list', async () => { + const role = { + name: 'Avogadro', + permissions: [], + }; + + const value = await roleSchema.validateAsync(role); + expect(value.permissions).toEqual([]); +}); + +test('role schema allows a role with a null list', async () => { + const role = { + name: 'Curie', + permissions: null, + }; + + const value = await roleSchema.validateAsync(role); + expect(value.permissions).toEqual(null); +}); + +test('role schema allows an undefined with a null list', async () => { + const role = { + name: 'Fischer', + }; + + const value = await roleSchema.validateAsync(role); + expect(value.permissions).toEqual(undefined); +}); + +test('role schema strips roleType if present', async () => { + const role = { + name: 'Grignard', + roleType: 'Organic Chemistry', + }; + + const value = await roleSchema.validateAsync(role); + expect(value.roleType).toEqual(undefined); +}); diff --git a/src/lib/schema/role-schema.ts b/src/lib/schema/role-schema.ts new file mode 100644 index 0000000000..67fc1d5ee7 --- /dev/null +++ b/src/lib/schema/role-schema.ts @@ -0,0 +1,22 @@ +import joi from 'joi'; + +export const permissionRoleSchema = joi + .object() + .keys({ + id: joi.number().required(), + environment: joi.string().optional().allow('').allow(null).default(''), + }) + .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); + +export const roleSchema = joi + .object() + .keys({ + name: joi.string().required(), + description: joi.string().optional().allow('').allow(null).default(''), + permissions: joi + .array() + .allow(null) + .optional() + .items(permissionRoleSchema), + }) + .options({ stripUnknown: true, allowUnknown: false, abortEarly: false }); diff --git a/src/lib/services/access-service.ts b/src/lib/services/access-service.ts index e5011a63e4..de08fbceef 100644 --- a/src/lib/services/access-service.ts +++ b/src/lib/services/access-service.ts @@ -3,6 +3,7 @@ import User, { IUser } from '../types/user'; import { IAccessStore, IRole, + IRoleWithPermissions, IUserPermission, IUserRole, } from '../types/stores/access-store'; @@ -10,20 +11,25 @@ import { IUserStore } from '../types/stores/user-store'; import { Logger } from '../logger'; import { IUnleashStores } from '../types/stores'; import { + IAvailablePermissions, + ICustomRole, IPermission, IRoleData, IUserWithRole, - PermissionType, RoleName, RoleType, } from '../types/model'; +import { IRoleStore } from 'lib/types/stores/role-store'; +import NameExistsError from '../error/name-exists-error'; +import { IEnvironmentStore } from 'lib/types/stores/environment-store'; +import RoleInUseError from '../error/role-in-use-error'; +import { roleSchema } from '../schema/role-schema'; +import { CUSTOM_ROLE_TYPE } from '../util/constants'; +import { DEFAULT_PROJECT } from '../types/project'; +import InvalidOperationError from '../error/invalid-operation-error'; export const ALL_PROJECTS = '*'; - -const PROJECT_DESCRIPTION = { - OWNER: 'Users with this role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.', - MEMBER: 'Users with this role within a project are allowed to view, create and update feature toggles, but have limited permissions in regards to managing the projects user access and can not archive or delete the project.', -}; +export const ALL_ENVS = '*'; const { ADMIN } = permissions; @@ -35,11 +41,18 @@ const PROJECT_ADMIN = [ permissions.DELETE_FEATURE, ]; -const PROJECT_REGULAR = [ - permissions.CREATE_FEATURE, - permissions.UPDATE_FEATURE, - permissions.DELETE_FEATURE, -]; +interface IRoleCreation { + name: string; + description: string; + permissions?: IPermission[]; +} + +interface IRoleUpdate { + id: number; + name: string; + description: string; + permissions?: IPermission[]; +} const isProjectPermission = (permission) => PROJECT_ADMIN.includes(permission); @@ -48,26 +61,29 @@ export class AccessService { private userStore: IUserStore; - private logger: Logger; + private roleStore: IRoleStore; - private permissions: IPermission[]; + private environmentStore: IEnvironmentStore; + + private logger: Logger; constructor( { accessStore, userStore, - }: Pick, + roleStore, + environmentStore, + }: Pick< + IUnleashStores, + 'accessStore' | 'userStore' | 'roleStore' | 'environmentStore' + >, { getLogger }: { getLogger: Function }, ) { this.store = accessStore; this.userStore = userStore; + this.roleStore = roleStore; + this.environmentStore = environmentStore; this.logger = getLogger('/services/access-service.ts'); - this.permissions = Object.values(permissions).map((p) => ({ - name: p, - type: isProjectPermission(p) - ? PermissionType.project - : PermissionType.root, - })); } /** @@ -81,9 +97,10 @@ export class AccessService { user: User, permission: string, projectId?: string, + environment?: string, ): Promise { this.logger.info( - `Checking permission=${permission}, userId=${user.id} projectId=${projectId}`, + `Checking permission=${permission}, userId=${user.id}, projectId=${projectId}, environment=${environment}`, ); try { @@ -96,6 +113,12 @@ export class AccessService { p.project === projectId || p.project === ALL_PROJECTS, ) + .filter( + (p) => + !p.environment || + p.environment === environment || + p.environment === ALL_ENVS, + ) .some( (p) => p.permission === permission || p.permission === ADMIN, @@ -118,12 +141,43 @@ export class AccessService { return this.store.getPermissionsForUser(user.id); } - getPermissions(): IPermission[] { - return this.permissions; + async getPermissions(): Promise { + const bindablePermissions = await this.store.getAvailablePermissions(); + const environments = await this.environmentStore.getAll(); + + const projectPermissions = bindablePermissions.filter((x) => { + return x.type === 'project'; + }); + + const environmentPermissions = bindablePermissions.filter((perm) => { + return perm.type === 'environment'; + }); + + const allEnvironmentPermissions = environments.map((env) => { + return { + name: env.name, + permissions: environmentPermissions.map((permission) => { + return { environment: env.name, ...permission }; + }), + }; + }); + + return { + project: projectPermissions, + environments: allEnvironmentPermissions, + }; } - async addUserToRole(userId: number, roleId: number): Promise { - return this.store.addUserToRole(userId, roleId); + async addUserToRole( + userId: number, + roleId: number, + projectId: string, + ): Promise { + return this.store.addUserToRole(userId, roleId, projectId); + } + + async getRoleByName(roleName: string): Promise { + return this.roleStore.getRoleByName(roleName); } async setUserRootRole( @@ -131,14 +185,18 @@ export class AccessService { role: number | RoleName, ): Promise { const newRootRole = await this.resolveRootRole(role); - if (newRootRole) { try { await this.store.removeRolesOfTypeForUser( userId, RoleType.ROOT, ); - await this.store.addUserToRole(userId, newRootRole.id); + + await this.store.addUserToRole( + userId, + newRootRole.id, + DEFAULT_PROJECT, + ); } catch (error) { throw new Error( `Could not add role=${newRootRole.name} to userId=${userId}`, @@ -154,29 +212,39 @@ export class AccessService { return userRoles.filter((r) => r.type === RoleType.ROOT); } - async removeUserFromRole(userId: number, roleId: number): Promise { - return this.store.removeUserFromRole(userId, roleId); + async removeUserFromRole( + userId: number, + roleId: number, + projectId: string, + ): Promise { + return this.store.removeUserFromRole(userId, roleId, projectId); } + //This actually only exists for testing purposes async addPermissionToRole( roleId: number, permission: string, - projectId?: string, + environment?: string, ): Promise { - if (isProjectPermission(permission) && !projectId) { + if (isProjectPermission(permission) && !environment) { throw new Error( `ProjectId cannot be empty for permission=${permission}`, ); } - return this.store.addPermissionsToRole(roleId, [permission], projectId); + return this.store.addPermissionsToRole( + roleId, + [permission], + environment, + ); } + //This actually only exists for testing purposes async removePermissionFromRole( roleId: number, permission: string, - projectId?: string, + environment?: string, ): Promise { - if (isProjectPermission(permission) && !projectId) { + if (isProjectPermission(permission) && !environment) { throw new Error( `ProjectId cannot be empty for permission=${permission}`, ); @@ -184,15 +252,24 @@ export class AccessService { return this.store.removePermissionFromRole( roleId, permission, - projectId, + environment, ); } async getRoles(): Promise { - return this.store.getRoles(); + return this.roleStore.getRoles(); } - async getRole(roleId: number): Promise { + async getRole(id: number): Promise { + const role = await this.store.get(id); + const rolePermissions = await this.store.getPermissionsForRole(role.id); + return { + ...role, + permissions: rolePermissions, + }; + } + + async getRoleData(roleId: number): Promise { const [role, rolePerms, users] = await Promise.all([ this.store.get(roleId), this.store.getPermissionsForRole(roleId), @@ -201,14 +278,22 @@ export class AccessService { return { role, permissions: rolePerms, users }; } + async getProjectRoles(): Promise { + return this.roleStore.getProjectRoles(); + } + async getRolesForProject(projectId: string): Promise { - return this.store.getRolesForProject(projectId); + return this.roleStore.getRolesForProject(projectId); } async getRolesForUser(userId: number): Promise { return this.store.getRolesForUserId(userId); } + async unlinkUserRoles(userId: number): Promise { + return this.store.unlinkUserRoles(userId); + } + async getUsersForRole(roleId: number): Promise { const userIdList = await this.store.getUserIdsForRole(roleId); if (userIdList.length > 0) { @@ -217,16 +302,33 @@ export class AccessService { return []; } + async getProjectUsersForRole( + roleId: number, + projectId?: string, + ): Promise { + const userIdList = await this.store.getProjectUserIdsForRole( + roleId, + projectId, + ); + if (userIdList.length > 0) { + return this.userStore.getAllWithId(userIdList); + } + return []; + } + // Move to project-service? async getProjectRoleUsers( projectId: string, ): Promise<[IRole[], IUserWithRole[]]> { - const roles = await this.store.getRolesForProject(projectId); + const roles = await this.roleStore.getProjectRoles(); const users = await Promise.all( roles.map(async (role) => { - const usrs = await this.getUsersForRole(role.id); - return usrs.map((u) => ({ ...u, roleId: role.id })); + const projectUsers = await this.getProjectUsersForRole( + role.id, + projectId, + ); + return projectUsers.map((u) => ({ ...u, roleId: role.id })); }), ); return [roles, users.flat()]; @@ -240,36 +342,15 @@ export class AccessService { throw new Error('ProjectId cannot be empty'); } - const ownerRole = await this.store.createRole( - RoleName.OWNER, - RoleType.PROJECT, - projectId, - PROJECT_DESCRIPTION.OWNER, - ); - await this.store.addPermissionsToRole( - ownerRole.id, - PROJECT_ADMIN, - projectId, - ); + const ownerRole = await this.roleStore.getRoleByName(RoleName.OWNER); // TODO: remove this when all users is guaranteed to have a unique id. if (owner.id) { this.logger.info( `Making ${owner.id} admin of ${projectId} via roleId=${ownerRole.id}`, ); - await this.store.addUserToRole(owner.id, ownerRole.id); + await this.store.addUserToRole(owner.id, ownerRole.id, projectId); } - const memberRole = await this.store.createRole( - RoleName.MEMBER, - RoleType.PROJECT, - projectId, - PROJECT_DESCRIPTION.MEMBER, - ); - await this.store.addPermissionsToRole( - memberRole.id, - PROJECT_REGULAR, - projectId, - ); } async removeDefaultProjectRoles( @@ -277,15 +358,15 @@ export class AccessService { projectId: string, ): Promise { this.logger.info(`Removing project roles for ${projectId}`); - return this.store.removeRolesForProject(projectId); + return this.roleStore.removeRolesForProject(projectId); } async getRootRoleForAllUsers(): Promise { - return this.store.getRootRoleForAllUsers(); + return this.roleStore.getRootRoleForAllUsers(); } async getRootRoles(): Promise { - return this.store.getRootRoles(); + return this.roleStore.getRootRoles(); } public async resolveRootRole(rootRole: number | RoleName): Promise { @@ -300,7 +381,94 @@ export class AccessService { } async getRootRole(roleName: RoleName): Promise { - const roles = await this.store.getRootRoles(); + const roles = await this.roleStore.getRootRoles(); return roles.find((r) => r.name === roleName); } + + async getAllRoles(): Promise { + return this.roleStore.getAll(); + } + + async createRole(role: IRoleCreation): Promise { + const baseRole = { + ...(await this.validateRole(role)), + roleType: CUSTOM_ROLE_TYPE, + }; + + const rolePermissions = role.permissions; + const newRole = await this.roleStore.create(baseRole); + if (rolePermissions) { + this.store.addEnvironmentPermissionsToRole( + newRole.id, + rolePermissions, + ); + } + return newRole; + } + + async updateRole(role: IRoleUpdate): Promise { + await this.validateRole(role, role.id); + const baseRole = { + id: role.id, + name: role.name, + description: role.description, + roleType: CUSTOM_ROLE_TYPE, + }; + const rolePermissions = role.permissions; + const newRole = await this.roleStore.update(baseRole); + if (rolePermissions) { + this.store.wipePermissionsFromRole(newRole.id); + this.store.addEnvironmentPermissionsToRole( + newRole.id, + rolePermissions, + ); + } + return newRole; + } + + async deleteRole(id: number): Promise { + const roleUsers = await this.getUsersForRole(id); + + if (roleUsers.length > 0) { + throw new RoleInUseError( + 'Role is in use by more than one user. You cannot delete a role that is in use without first removing the role from the users.', + ); + } + + return this.roleStore.delete(id); + } + + async validateRoleIsUnique( + roleName: string, + existingId?: number, + ): Promise { + const exists = await this.roleStore.nameInUse(roleName, existingId); + if (exists) { + throw new NameExistsError( + `There already exists a role with the name ${roleName}`, + ); + } + return Promise.resolve(); + } + + async validateRoleIsNotBuiltIn(roleId: number): Promise { + const role = await this.store.get(roleId); + if (role.type !== CUSTOM_ROLE_TYPE) { + throw new InvalidOperationError( + 'You can not change built in roles.', + ); + } + } + + async validateRole( + role: IRoleCreation, + existingId?: number, + ): Promise { + const cleanedRole = await roleSchema.validateAsync(role); + if (existingId) { + await this.validateRoleIsNotBuiltIn(existingId); + } + await this.validateRoleIsUnique(role.name, existingId); + return cleanedRole; + } } diff --git a/src/lib/services/project-service.ts b/src/lib/services/project-service.ts index 29cd4ff9e6..d7dfb1c200 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/services/project-service.ts @@ -25,20 +25,20 @@ import { IFeatureTypeStore } from '../types/stores/feature-type-store'; import { IFeatureToggleStore } from '../types/stores/feature-toggle-store'; import { IFeatureEnvironmentStore } from '../types/stores/feature-environment-store'; import { IProjectQuery, IProjectStore } from '../types/stores/project-store'; -import { IRole } from '../types/stores/access-store'; +import { IRoleDescriptor } from '../types/stores/access-store'; import { IEventStore } from '../types/stores/event-store'; import FeatureToggleService from './feature-toggle-service'; -import { CREATE_FEATURE, UPDATE_FEATURE } from '../types/permissions'; +import { MOVE_FEATURE_TOGGLE } from '../types/permissions'; import NoAccessError from '../error/no-access-error'; import IncompatibleProjectError from '../error/incompatible-project-error'; +import { DEFAULT_PROJECT } from '../types/project'; +import { IFeatureTagStore } from 'lib/types/stores/feature-tag-store'; const getCreatedBy = (user: User) => user.email || user.username; -const DEFAULT_PROJECT = 'default'; - export interface UsersWithRoles { users: IUserWithRole[]; - roles: IRole[]; + roles: IRoleDescriptor[]; } export default class ProjectService { @@ -60,6 +60,8 @@ export default class ProjectService { private featureToggleService: FeatureToggleService; + private tagStore: IFeatureTagStore; + constructor( { projectStore, @@ -68,6 +70,7 @@ export default class ProjectService { featureTypeStore, environmentStore, featureEnvironmentStore, + featureTagStore, }: Pick< IUnleashStores, | 'projectStore' @@ -76,6 +79,7 @@ export default class ProjectService { | 'featureTypeStore' | 'environmentStore' | 'featureEnvironmentStore' + | 'featureTagStore' >, config: IUnleashConfig, accessService: AccessService, @@ -89,6 +93,7 @@ export default class ProjectService { this.featureToggleStore = featureToggleStore; this.featureTypeStore = featureTypeStore; this.featureToggleService = featureToggleService; + this.tagStore = featureTagStore; this.logger = config.getLogger('services/project-service.js'); } @@ -188,7 +193,7 @@ export default class ProjectService { const feature = await this.featureToggleStore.get(featureName); if (feature.project !== currentProjectId) { - throw new NoAccessError(UPDATE_FEATURE); + throw new NoAccessError(MOVE_FEATURE_TOGGLE); } const project = await this.getProject(newProjectId); @@ -198,12 +203,12 @@ export default class ProjectService { const authorized = await this.accessService.hasPermission( user, - CREATE_FEATURE, + MOVE_FEATURE_TOGGLE, newProjectId, ); if (!authorized) { - throw new NoAccessError(CREATE_FEATURE); + throw new NoAccessError(MOVE_FEATURE_TOGGLE); } const isCompatibleWithTargetProject = @@ -297,10 +302,10 @@ export default class ProjectService { const alreadyHasAccess = users.some((u) => u.id === userId); if (alreadyHasAccess) { - throw new Error(`User already have access to project=${projectId}`); + throw new Error(`User already has access to project=${projectId}`); } - await this.accessService.addUserToRole(userId, role.id); + await this.accessService.addUserToRole(userId, role.id, projectId); } // TODO: should be an event too @@ -318,13 +323,16 @@ export default class ProjectService { } if (role.name === RoleName.OWNER) { - const users = await this.accessService.getUsersForRole(role.id); + const users = await this.accessService.getProjectUsersForRole( + role.id, + projectId, + ); if (users.length < 2) { throw new Error('A project must have at least one owner'); } } - await this.accessService.removeUserFromRole(userId, role.id); + await this.accessService.removeUserFromRole(userId, role.id, projectId); } async getMembers(projectId: string): Promise { diff --git a/src/lib/services/user-service.ts b/src/lib/services/user-service.ts index 129e16830a..03d7d86e30 100644 --- a/src/lib/services/user-service.ts +++ b/src/lib/services/user-service.ts @@ -135,7 +135,6 @@ class UserService { }); const passwordHash = await bcrypt.hash(pwd, saltRounds); await this.store.setPasswordHash(user.id, passwordHash); - await this.accessService.setUserRootRole( user.id, RoleName.ADMIN, @@ -257,12 +256,7 @@ class UserService { async deleteUser(userId: number, updatedBy?: User): Promise { const user = await this.store.get(userId); - const roles = await this.accessService.getRolesForUser(userId); - await Promise.all( - roles.map((role) => - this.accessService.removeUserFromRole(userId, role.id), - ), - ); + await this.accessService.unlinkUserRoles(userId); await this.sessionService.deleteSessionsForUser(userId); await this.store.delete(userId); @@ -355,7 +349,7 @@ class UserService { token, ); const user = await this.getUser(userId); - const role = await this.accessService.getRole(user.rootRole); + const role = await this.accessService.getRoleData(user.rootRole); return { token, createdBy, diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index b80ffd80ce..0f0e3c2060 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -1,6 +1,6 @@ import { ITagType } from './stores/tag-type-store'; import { LogProvider } from '../logger'; -import { IRole, IUserPermission } from './stores/access-store'; +import { IRole } from './stores/access-store'; import { IUser } from './user'; export interface IConstraint { @@ -212,12 +212,25 @@ export interface IUserWithRole { export interface IRoleData { role: IRole; users: IUser[]; - permissions: IUserPermission[]; + permissions: IPermission[]; +} + +export interface IAvailablePermissions { + project: IPermission[]; + environments: IEnvironmentPermission[]; } export interface IPermission { + id: number; name: string; - type: PermissionType; + displayName: string; + type: string; + environment?: string; +} + +export interface IEnvironmentPermission { + name: string; + permissions: IPermission[]; } export enum PermissionType { @@ -313,6 +326,13 @@ export interface IProject { updatedAt?: Date; } +export interface ICustomRole { + id: number; + name: string; + description: string; + type: string; +} + export interface IProjectWithCount extends IProject { featureCount: number; memberCount: number; diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts index d0e60e24c4..f60413cd4a 100644 --- a/src/lib/types/option.ts +++ b/src/lib/types/option.ts @@ -101,6 +101,7 @@ export interface IUnleashOptions { preRouterHook?: Function; eventHook?: EventHook; enterpriseVersion?: string; + disableLegacyFeaturesApi?: boolean; } export interface IEmailOption { @@ -156,4 +157,5 @@ export interface IUnleashConfig { eventHook?: EventHook; enterpriseVersion?: string; eventBus: EventEmitter; + disableLegacyFeaturesApi?: boolean; } diff --git a/src/lib/types/permissions.ts b/src/lib/types/permissions.ts index 0e0efe92f7..58276a3dab 100644 --- a/src/lib/types/permissions.ts +++ b/src/lib/types/permissions.ts @@ -1,4 +1,4 @@ -// Special +//Special export const ADMIN = 'ADMIN'; export const CLIENT = 'CLIENT'; export const NONE = 'NONE'; @@ -6,6 +6,10 @@ export const NONE = 'NONE'; export const CREATE_FEATURE = 'CREATE_FEATURE'; export const UPDATE_FEATURE = 'UPDATE_FEATURE'; export const DELETE_FEATURE = 'DELETE_FEATURE'; +export const CREATE_FEATURE_STRATEGY = 'CREATE_FEATURE_STRATEGY'; +export const UPDATE_FEATURE_STRATEGY = 'UPDATE_FEATURE_STRATEGY'; +export const DELETE_FEATURE_STRATEGY = 'DELETE_FEATURE_STRATEGY'; +export const UPDATE_FEATURE_ENVIRONMENT = 'UPDATE_FEATURE_ENVIRONMENT'; export const CREATE_STRATEGY = 'CREATE_STRATEGY'; export const UPDATE_STRATEGY = 'UPDATE_STRATEGY'; export const DELETE_STRATEGY = 'DELETE_STRATEGY'; @@ -26,3 +30,5 @@ export const CREATE_API_TOKEN = 'CREATE_API_TOKEN'; export const DELETE_API_TOKEN = 'DELETE_API_TOKEN'; export const UPDATE_TAG_TYPE = 'UPDATE_TAG_TYPE'; export const DELETE_TAG_TYPE = 'DELETE_TAG_TYPE'; +export const UPDATE_FEATURE_VARIANTS = 'UPDATE_FEATURE_VARIANTS'; +export const MOVE_FEATURE_TOGGLE = 'MOVE_FEATURE_TOGGLE'; diff --git a/src/lib/types/project.ts b/src/lib/types/project.ts new file mode 100644 index 0000000000..0b4d3ce913 --- /dev/null +++ b/src/lib/types/project.ts @@ -0,0 +1 @@ +export const DEFAULT_PROJECT = 'default'; diff --git a/src/lib/types/stores.ts b/src/lib/types/stores.ts index d0d9778f26..ea9bac57c7 100644 --- a/src/lib/types/stores.ts +++ b/src/lib/types/stores.ts @@ -23,6 +23,7 @@ import { IEnvironmentStore } from './stores/environment-store'; import { IFeatureToggleClientStore } from './stores/feature-toggle-client-store'; import { IClientMetricsStoreV2 } from './stores/client-metrics-store-v2'; import { IUserSplashStore } from './stores/user-splash-store'; +import { IRoleStore } from './stores/role-store'; export interface IUnleashStores { accessStore: IAccessStore; @@ -50,4 +51,5 @@ export interface IUnleashStores { userFeedbackStore: IUserFeedbackStore; userStore: IUserStore; userSplashStore: IUserSplashStore; + roleStore: IRoleStore; } diff --git a/src/lib/types/stores/access-store.ts b/src/lib/types/stores/access-store.ts index 9adf5c1a2a..bac68b07c0 100644 --- a/src/lib/types/stores/access-store.ts +++ b/src/lib/types/stores/access-store.ts @@ -1,7 +1,9 @@ +import { IPermission } from '../model'; import { Store } from './store'; export interface IUserPermission { project?: string; + environment?: string; permission: string; } @@ -10,7 +12,16 @@ export interface IRole { name: string; description?: string; type: string; - project?: string; +} + +export interface IRoleWithPermissions extends IRole { + permissions: IPermission[]; +} + +export interface IRoleDescriptor { + name: string; + description?: string; + type: string; } export interface IUserRole { @@ -18,32 +29,37 @@ export interface IUserRole { userId: number; } export interface IAccessStore extends Store { + getAvailablePermissions(): Promise; getPermissionsForUser(userId: number): Promise; - getPermissionsForRole(roleId: number): Promise; - getRoles(): Promise; - getRolesForProject(projectId: string): Promise; - getRootRoles(): Promise; - removeRolesForProject(projectId: string): Promise; + getPermissionsForRole(roleId: number): Promise; + unlinkUserRoles(userId: number): Promise; getRolesForUserId(userId: number): Promise; - getUserIdsForRole(roleId: number): Promise; - addUserToRole(userId: number, roleId: number): Promise; - removeUserFromRole(userId: number, roleId: number): Promise; + getProjectUserIdsForRole(roleId: number, projectId?: string); + getUserIdsForRole(roleId: number, projectId?: string): Promise; + wipePermissionsFromRole(role_id: number): Promise; + addEnvironmentPermissionsToRole( + role_id: number, + permissions: IPermission[], + ): Promise; + addUserToRole( + userId: number, + roleId: number, + projectId?: string, + ): Promise; + removeUserFromRole( + userId: number, + roleId: number, + projectId?: string, + ): Promise; removeRolesOfTypeForUser(userId: number, roleType: string): Promise; - createRole( - name: string, - type: string, - project?: string, - description?: string, - ): Promise; addPermissionsToRole( role_id: number, permissions: string[], - projectId?: string, + environment?: string, ): Promise; removePermissionFromRole( roleId: number, permission: string, projectId?: string, ): Promise; - getRootRoleForAllUsers(): Promise; } diff --git a/src/lib/types/stores/environment-store.ts b/src/lib/types/stores/environment-store.ts index c975ad28a4..c6fe2600f2 100644 --- a/src/lib/types/stores/environment-store.ts +++ b/src/lib/types/stores/environment-store.ts @@ -15,4 +15,5 @@ export interface IEnvironmentStore extends Store { ): Promise; updateSortOrder(id: string, value: number): Promise; importEnvironments(environments: IEnvironment[]): Promise; + delete(name: string): Promise; } diff --git a/src/lib/types/stores/role-store.ts b/src/lib/types/stores/role-store.ts new file mode 100644 index 0000000000..ffa508269a --- /dev/null +++ b/src/lib/types/stores/role-store.ts @@ -0,0 +1,31 @@ +import { ICustomRole } from '../model'; +import { IRole, IUserRole } from './access-store'; +import { Store } from './store'; + +export interface ICustomRoleInsert { + name: string; + description: string; + roleType: string; +} + +export interface ICustomRoleUpdate { + id: number; + name: string; + description: string; + roleType: string; +} + +export interface IRoleStore extends Store { + getAll(): Promise; + create(role: ICustomRoleInsert): Promise; + update(role: ICustomRoleUpdate): Promise; + delete(id: number): Promise; + getRoles(): Promise; + getRoleByName(name: string): Promise; + getRolesForProject(projectId: string): Promise; + removeRolesForProject(projectId: string): Promise; + getProjectRoles(): Promise; + getRootRoles(): Promise; + getRootRoleForAllUsers(): Promise; + nameInUse(name: string, existingId: number): Promise; +} diff --git a/src/lib/util/constants.ts b/src/lib/util/constants.ts index c46e2644b8..45b0ab23b8 100644 --- a/src/lib/util/constants.ts +++ b/src/lib/util/constants.ts @@ -1 +1,7 @@ export const DEFAULT_ENV = 'default'; + +export const ROOT_PERMISSION_TYPE = 'root'; +export const ENVIRONMENT_PERMISSION_TYPE = 'environment'; +export const PROJECT_PERMISSION_TYPE = 'project'; + +export const CUSTOM_ROLE_TYPE = 'custom'; diff --git a/src/migrations/20211202120808-add-custom-roles.js b/src/migrations/20211202120808-add-custom-roles.js new file mode 100644 index 0000000000..2da5387350 --- /dev/null +++ b/src/migrations/20211202120808-add-custom-roles.js @@ -0,0 +1,205 @@ +exports.up = function (db, cb) { + db.runSql( + ` + CREATE TABLE IF NOT EXISTS permissions + ( + id SERIAL PRIMARY KEY, + permission VARCHAR(255) NOT NULL, + display_name TEXT, + type VARCHAR(255), + created_at TIMESTAMP WITH TIME ZONE DEFAULT now() + ); + + INSERT INTO permissions (permission, display_name, type) VALUES ('ADMIN', 'Admin', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_FEATURE', 'Create Feature Toggles', 'project'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_STRATEGY','Create Strategies', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_ADDON', 'Create Addons', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_ADDON', 'Delete Addons', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_ADDON', 'Update Addons', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_FEATURE', 'Update Feature Toggles', 'project'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_FEATURE', 'Delete Feature Toggles', 'project'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_APPLICATION', 'Update Applications', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_TAG_TYPE', 'Update Tag Types', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_TAG_TYPE', 'Delete Tag Types', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_PROJECT', 'Create Projects', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_PROJECT', 'Update Projects', 'project'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_PROJECT', 'Delete Projects', 'project'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_STRATEGY', 'Update Strategies', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_STRATEGY', 'Delete Strategies', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_CONTEXT_FIELD', 'Update Context Fields', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_CONTEXT_FIELD', 'Create Context Fields', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_CONTEXT_FIELD', 'Delete Context Fields', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('READ_ROLE', 'Read Roles', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_ROLE', 'Update Roles', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_API_TOKEN', 'Update API Tokens', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_API_TOKEN', 'Create API Tokens', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_API_TOKEN', 'Delete API Tokens', 'root'); + INSERT INTO permissions (permission, display_name, type) VALUES ('CREATE_FEATURE_STRATEGY', 'Create Feature Strategies', 'environment'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_FEATURE_STRATEGY', 'Update Feature Strategies', 'environment'); + INSERT INTO permissions (permission, display_name, type) VALUES ('DELETE_FEATURE_STRATEGY', 'Delete Feature Strategies', 'environment'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_FEATURE_ENVIRONMENT', 'Enable/disable Toggles in Environment', 'environment'); + INSERT INTO permissions (permission, display_name, type) VALUES ('UPDATE_FEATURE_VARIANTS', 'Create/Edit variants', 'project'); + + ALTER TABLE role_user ADD COLUMN + project VARCHAR(255); + + ALTER TABLE roles + ADD COLUMN + updated_at TIMESTAMP WITH TIME ZONE; + + ALTER TABLE role_permission + ADD COLUMN + permission_id INTEGER, + ADD COLUMN + environment VARCHAR (100); + + CREATE TEMPORARY TABLE temp_primary_roles + ( + id INTEGER, + name TEXT, + description TEXT, + type TEXT, + project TEXT, + created_at DATE + ) + ON COMMIT DROP; + + CREATE TEMPORARY TABLE temp_discard_roles + ( + id INTEGER, + name TEXT, + description TEXT, + type TEXT, + project TEXT, + created_at DATE + ) + ON COMMIT DROP; + + INSERT INTO temp_primary_roles select distinct on (name) id, name ,description, type, project, created_at from roles order by name, id; + + INSERT INTO temp_discard_roles SELECT r.id, r.name, r.description, r.type, r.project, r.created_at FROM roles r + LEFT JOIN temp_primary_roles tpr ON r.id = tpr.id + WHERE tpr.id IS NULL; + + UPDATE role_user + SET project = tpr.project + FROM temp_primary_roles tpr + WHERE tpr.id = role_user.role_id; + + ALTER TABLE role_user DROP CONSTRAINT role_user_pkey; + + WITH rtu as ( + SELECT tdr.id as old_role_id, tpr.id as new_role_id, tdr.project as project FROM temp_discard_roles tdr + JOIN temp_primary_roles tpr ON tdr.name = tpr.name + ) + UPDATE role_user + SET project = rtu.project, role_id = rtu.new_role_id + FROM rtu + WHERE rtu.old_role_id = role_user.role_id; + + UPDATE role_user SET project = '*' WHERE project IS NULL; + ALTER TABLE role_user ADD PRIMARY KEY (role_id, user_id, project); + + DELETE FROM roles WHERE EXISTS + ( + SELECT 1 FROM temp_discard_roles tdr WHERE tdr.id = roles.id + ); + + DELETE FROM role_permission; + + ALTER TABLE roles DROP COLUMN project; + ALTER TABLE role_permission + DROP COLUMN project, + DROP COLUMN permission; + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Editor' LIMIT 1), + p.id as permission_id, + '*' as environment + FROM permissions p + WHERE p.permission IN + ('CREATE_STRATEGY', + 'UPDATE_STRATEGY', + 'DELETE_STRATEGY', + 'UPDATE_APPLICATION', + 'CREATE_CONTEXT_FIELD', + 'UPDATE_CONTEXT_FIELD', + 'DELETE_CONTEXT_FIELD', + 'CREATE_PROJECT', + 'CREATE_ADDON', + 'UPDATE_ADDON', + 'DELETE_ADDON', + 'UPDATE_PROJECT', + 'DELETE_PROJECT', + 'CREATE_FEATURE', + 'UPDATE_FEATURE', + 'DELETE_FEATURE', + 'UPDATE_TAG_TYPE', + 'DELETE_TAG_TYPE', + 'UPDATE_FEATURE_VARIANTS'); + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Owner' LIMIT 1), + p.id as permission_id, + null as environment + FROM permissions p + WHERE p.permission IN + ('UPDATE_PROJECT', + 'DELETE_PROJECT', + 'CREATE_FEATURE', + 'UPDATE_FEATURE', + 'DELETE_FEATURE', + 'UPDATE_FEATURE_VARIANTS'); + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Member' LIMIT 1), + p.id as permission_id, + null as environment + FROM permissions p + WHERE p.permission IN + ('CREATE_FEATURE', + 'UPDATE_FEATURE', + 'DELETE_FEATURE', + 'UPDATE_FEATURE_VARIANTS'); + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Admin' LIMIT 1), + p.id as permission_id, + '*' environment + FROM permissions p + WHERE p.permission = 'ADMIN'; + + ALTER TABLE role_permission + ADD CONSTRAINT fk_role_permission + FOREIGN KEY(role_id) + REFERENCES roles(id) ON DELETE CASCADE; + + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + ALTER TABLE role_user DROP COLUMN project; + + ALTER TABLE roles DROP COLUMN updated_at; + + ALTER TABLE role_permission + DROP COLUMN + permission_id, + DROP COLUMN + environment; + + ALTER TABLE role_permission + ADD COLUMN project TEXT, + ADD COLUMN permission TEXT; + `, + cb, + ); +}; diff --git a/src/migrations/20220103134659-add-permissions-to-project-roles.js b/src/migrations/20220103134659-add-permissions-to-project-roles.js new file mode 100644 index 0000000000..1789cb5aa2 --- /dev/null +++ b/src/migrations/20220103134659-add-permissions-to-project-roles.js @@ -0,0 +1,40 @@ +exports.up = function (db, cb) { + db.runSql( + ` + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Owner' LIMIT 1), + p.id as permission_id, + e.name as environment + FROM permissions p + CROSS JOIN environments e + WHERE p.permission IN + ('CREATE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_STRATEGY', + 'DELETE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_ENVIRONMENT'); + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Member' LIMIT 1), + p.id as permission_id, + e.name as environment + FROM permissions p + CROSS JOIN environments e + WHERE p.permission IN + ('CREATE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_STRATEGY', + 'DELETE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_ENVIRONMENT'); + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + `, + cb, + ); +}; diff --git a/src/migrations/20220103143843-add-permissions-to-editor-role.js b/src/migrations/20220103143843-add-permissions-to-editor-role.js new file mode 100644 index 0000000000..acb0fc2443 --- /dev/null +++ b/src/migrations/20220103143843-add-permissions-to-editor-role.js @@ -0,0 +1,27 @@ +exports.up = function (db, cb) { + db.runSql( + ` + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Editor' LIMIT 1), + p.id as permission_id, + e.name as environment + FROM permissions p + CROSS JOIN environments e + WHERE p.permission IN + ('CREATE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_STRATEGY', + 'DELETE_FEATURE_STRATEGY', + 'UPDATE_FEATURE_ENVIRONMENT'); + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + `, + cb, + ); +}; diff --git a/src/migrations/20220111112804-update-permission-descriptions.js b/src/migrations/20220111112804-update-permission-descriptions.js new file mode 100644 index 0000000000..d962578728 --- /dev/null +++ b/src/migrations/20220111112804-update-permission-descriptions.js @@ -0,0 +1,45 @@ +exports.up = function (db, cb) { + db.runSql( + ` + UPDATE permissions SET display_name = 'Admin' WHERE permission = 'ADMIN'; + UPDATE permissions SET display_name = 'Create feature toggles' WHERE permission = 'CREATE_FEATURE'; + UPDATE permissions SET display_name = 'Create activation strategies' WHERE permission = 'CREATE_STRATEGY'; + UPDATE permissions SET display_name = 'Create addons' WHERE permission = 'CREATE_ADDON'; + UPDATE permissions SET display_name = 'Delete addons' WHERE permission = 'DELETE_ADDON'; + UPDATE permissions SET display_name = 'Update addons' WHERE permission = 'UPDATE_ADDON'; + UPDATE permissions SET display_name = 'Update feature toggles' WHERE permission = 'UPDATE_FEATURE'; + UPDATE permissions SET display_name = 'Delete feature toggles' WHERE permission = 'DELETE_FEATURE'; + UPDATE permissions SET display_name = 'Update applications' WHERE permission = 'UPDATE_APPLICATION'; + UPDATE permissions SET display_name = 'Update tag types' WHERE permission = 'UPDATE_TAG_TYPE'; + UPDATE permissions SET display_name = 'Delete tag types' WHERE permission = 'DELETE_TAG_TYPE'; + UPDATE permissions SET display_name = 'Create projects' WHERE permission = 'CREATE_PROJECT'; + UPDATE permissions SET display_name = 'Update project' WHERE permission = 'UPDATE_PROJECT'; + UPDATE permissions SET display_name = 'Delete project' WHERE permission = 'DELETE_PROJECT'; + UPDATE permissions SET display_name = 'Update strategies' WHERE permission = 'UPDATE_STRATEGY'; + UPDATE permissions SET display_name = 'Delete strategies' WHERE permission = 'DELETE_STRATEGY'; + UPDATE permissions SET display_name = 'Update context fields' WHERE permission = 'UPDATE_CONTEXT_FIELD'; + UPDATE permissions SET display_name = 'Create context fields' WHERE permission = 'CREATE_CONTEXT_FIELD'; + UPDATE permissions SET display_name = 'Delete context fields' WHERE permission = 'DELETE_CONTEXT_FIELD'; + UPDATE permissions SET display_name = 'Read roles' WHERE permission = 'READ_ROLE'; + UPDATE permissions SET display_name = 'Update roles' WHERE permission = 'UPDATE_ROLE'; + UPDATE permissions SET display_name = 'Update API tokens' WHERE permission = 'UPDATE_API_TOKEN'; + UPDATE permissions SET display_name = 'Create API tokens' WHERE permission = 'CREATE_API_TOKEN'; + UPDATE permissions SET display_name = 'Delete API tokens' WHERE permission = 'DELETE_API_TOKEN'; + UPDATE permissions SET display_name = 'Create activation strategies' WHERE permission = 'CREATE_FEATURE_STRATEGY'; + UPDATE permissions SET display_name = 'Update activation strategies' WHERE permission = 'UPDATE_FEATURE_STRATEGY'; + UPDATE permissions SET display_name = 'Delete activation strategies' WHERE permission = 'DELETE_FEATURE_STRATEGY'; + UPDATE permissions SET display_name = 'Enable/disable toggles in this environment' WHERE permission = 'UPDATE_FEATURE_ENVIRONMENT'; + UPDATE permissions SET display_name = 'Create/edit variants' WHERE permission = 'UPDATE_FEATURE_VARIANTS'; + + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` +`, + cb, + ); +}; diff --git a/src/migrations/20220111115613-move-feature-toggle-permission.js b/src/migrations/20220111115613-move-feature-toggle-permission.js new file mode 100644 index 0000000000..0ae4f57c04 --- /dev/null +++ b/src/migrations/20220111115613-move-feature-toggle-permission.js @@ -0,0 +1,35 @@ +exports.up = function (db, cb) { + db.runSql( + ` + INSERT INTO permissions (permission, display_name, type) VALUES ('MOVE_FEATURE_TOGGLE', 'Change feature toggle project', 'project'); + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Editor' LIMIT 1), + p.id as permission_id, + '*' as environment + FROM permissions p + WHERE p.permission IN + ('MOVE_FEATURE_TOGGLE'); + + + INSERT INTO role_permission (role_id, permission_id, environment) + SELECT + (SELECT id as role_id from roles WHERE name = 'Owner' LIMIT 1), + p.id as permission_id, + '*' as environment + FROM permissions p + WHERE p.permission IN + ('MOVE_FEATURE_TOGGLE'); + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + DELETE FROM permissions WHERE permission = 'MOVE_FEATURE_TOGGLE'; + `, + cb, + ); +}; diff --git a/src/migrations/20220111120346-roles-unique-name.js b/src/migrations/20220111120346-roles-unique-name.js new file mode 100644 index 0000000000..43b2d1ac55 --- /dev/null +++ b/src/migrations/20220111120346-roles-unique-name.js @@ -0,0 +1,17 @@ +exports.up = function (db, cb) { + db.runSql( + ` + ALTER TABLE roles ADD CONSTRAINT unique_name UNIQUE (name); + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + ALTER TABLE roles DROP CONSTRAINT unique_name; +`, + cb, + ); +}; diff --git a/src/migrations/20220111121010-update-project-for-editor-role.js b/src/migrations/20220111121010-update-project-for-editor-role.js new file mode 100644 index 0000000000..1825bda4d0 --- /dev/null +++ b/src/migrations/20220111121010-update-project-for-editor-role.js @@ -0,0 +1,19 @@ +exports.up = function (db, cb) { + db.runSql( + ` + UPDATE role_user set project = 'default' where role_id + IN (SELECT id as role_id from roles WHERE name in ('Admin', 'Editor', 'Viewer') LIMIT 3) + `, + cb, + ); +}; + +exports.down = function (db, cb) { + db.runSql( + ` + UPDATE role_user set project = '*' where role_id + IN (SELECT id as role_id from roles WHERE name in ('Admin', 'Editor', 'Viewer') LIMIT 3) +`, + cb, + ); +}; diff --git a/src/migrations/20220111125620-role-permission-empty-string-for-non-environment-type.js b/src/migrations/20220111125620-role-permission-empty-string-for-non-environment-type.js new file mode 100644 index 0000000000..0a01106e6d --- /dev/null +++ b/src/migrations/20220111125620-role-permission-empty-string-for-non-environment-type.js @@ -0,0 +1,13 @@ +exports.up = function (db, cb) { + db.runSql( + ` + UPDATE role_permission SET environment = '' where permission_id NOT IN + (select id from permissions WHERE type = 'environment'); +`, + cb, + ); +}; + +exports.down = function (db, cb) { + cb(); +}; diff --git a/src/test/e2e/api/admin/feature.e2e.test.ts b/src/test/e2e/api/admin/feature.e2e.test.ts index d509741fed..a8a2182130 100644 --- a/src/test/e2e/api/admin/feature.e2e.test.ts +++ b/src/test/e2e/api/admin/feature.e2e.test.ts @@ -1,7 +1,11 @@ import faker from 'faker'; import { FeatureToggleDTO, IStrategyConfig, IVariant } from 'lib/types/model'; import dbInit, { ITestDb } from '../../helpers/database-init'; -import { IUnleashTest, setupApp } from '../../helpers/test-helper'; +import { + IUnleashTest, + setupApp, + setupAppWithCustomConfig, +} from '../../helpers/test-helper'; import getLogger from '../../../fixtures/no-logger'; import { DEFAULT_ENV } from '../../../../lib/util/constants'; @@ -680,3 +684,21 @@ test('marks feature toggle as stale', async () => { expect(res.body.stale).toBe(true); }); }); + +test('should not hit endpoints if disable configuration is set', async () => { + const appWithDisabledLegacyFeatures = await setupAppWithCustomConfig( + db.stores, + { + disableLegacyFeaturesApi: true, + }, + ); + + await appWithDisabledLegacyFeatures.request + .get('/api/admin/features') + .expect(404); + + return appWithDisabledLegacyFeatures.request + .get('/api/admin/features/featureX') + .expect('Content-Type', /json/) + .expect(404); +}); diff --git a/src/test/e2e/api/admin/user-admin.e2e.test.ts b/src/test/e2e/api/admin/user-admin.e2e.test.ts index db9478cfa1..eb0ed45b3c 100644 --- a/src/test/e2e/api/admin/user-admin.e2e.test.ts +++ b/src/test/e2e/api/admin/user-admin.e2e.test.ts @@ -6,10 +6,11 @@ import { USER_DELETED, USER_UPDATED, } from '../../../../lib/types/events'; -import { IAccessStore, IRole } from '../../../../lib/types/stores/access-store'; +import { IRole } from '../../../../lib/types/stores/access-store'; import { IEventStore } from '../../../../lib/types/stores/event-store'; import { IUserStore } from '../../../../lib/types/stores/user-store'; import { RoleName } from '../../../../lib/types/model'; +import { IRoleStore } from 'lib/types/stores/role-store'; let stores; let db; @@ -17,7 +18,7 @@ let app; let userStore: IUserStore; let eventStore: IEventStore; -let accessStore: IAccessStore; +let roleStore: IRoleStore; let editorRole: IRole; let adminRole: IRole; @@ -27,9 +28,9 @@ beforeAll(async () => { app = await setupApp(stores); userStore = stores.userStore; - accessStore = stores.accessStore; eventStore = stores.eventStore; - const roles = await accessStore.getRootRoles(); + roleStore = stores.roleStore; + const roles = await roleStore.getRootRoles(); editorRole = roles.find((r) => r.name === RoleName.EDITOR); adminRole = roles.find((r) => r.name === RoleName.ADMIN); }); diff --git a/src/test/e2e/services/access-service.e2e.test.ts b/src/test/e2e/services/access-service.e2e.test.ts index be473647a9..4d7c0401d0 100644 --- a/src/test/e2e/services/access-service.e2e.test.ts +++ b/src/test/e2e/services/access-service.e2e.test.ts @@ -1,4 +1,4 @@ -import dbInit from '../helpers/database-init'; +import dbInit, { ITestDb } from '../helpers/database-init'; import getLogger from '../../fixtures/no-logger'; // eslint-disable-next-line import/no-unresolved @@ -9,11 +9,16 @@ import { import * as permissions from '../../../lib/types/permissions'; import { RoleName } from '../../../lib/types/model'; +import { IUnleashStores } from '../../../lib/types'; +import FeatureToggleService from '../../../lib/services/feature-toggle-service'; +import ProjectService from '../../../lib/services/project-service'; +import { createTestConfig } from '../../config/test-config'; -let db; -let stores; +let db: ITestDb; +let stores: IUnleashStores; let accessService; - +let featureToggleService; +let projectService; let editorUser; let superUser; let editorRole; @@ -23,17 +28,172 @@ let readRole; const createUserEditorAccess = async (name, email) => { const { userStore } = stores; const user = await userStore.insert({ name, email }); - await accessService.addUserToRole(user.id, editorRole.id); + await accessService.addUserToRole(user.id, editorRole.id, 'default'); return user; }; +const createUserViewerAccess = async (name, email) => { + const { userStore } = stores; + const user = await userStore.insert({ name, email }); + await accessService.addUserToRole(user.id, readRole.id, ALL_PROJECTS); + return user; +}; + +const hasCommonProjectAccess = async (user, projectName, condition) => { + const defaultEnv = 'default'; + const developmentEnv = 'development'; + const productionEnv = 'production'; + + const { + CREATE_FEATURE, + UPDATE_FEATURE, + DELETE_FEATURE, + CREATE_FEATURE_STRATEGY, + UPDATE_FEATURE_STRATEGY, + DELETE_FEATURE_STRATEGY, + UPDATE_FEATURE_ENVIRONMENT, + UPDATE_FEATURE_VARIANTS, + } = permissions; + expect( + await accessService.hasPermission(user, CREATE_FEATURE, projectName), + ).toBe(condition); + expect( + await accessService.hasPermission(user, UPDATE_FEATURE, projectName), + ).toBe(condition); + expect( + await accessService.hasPermission(user, DELETE_FEATURE, projectName), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_VARIANTS, + projectName, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + projectName, + defaultEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_STRATEGY, + projectName, + defaultEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + DELETE_FEATURE_STRATEGY, + projectName, + defaultEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_ENVIRONMENT, + projectName, + defaultEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + projectName, + developmentEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_STRATEGY, + projectName, + developmentEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + DELETE_FEATURE_STRATEGY, + projectName, + developmentEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_ENVIRONMENT, + projectName, + developmentEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + projectName, + productionEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_STRATEGY, + projectName, + productionEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + DELETE_FEATURE_STRATEGY, + projectName, + productionEnv, + ), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_ENVIRONMENT, + projectName, + productionEnv, + ), + ).toBe(condition); +}; + +const hasFullProjectAccess = async (user, projectName, condition) => { + const { DELETE_PROJECT, UPDATE_PROJECT, MOVE_FEATURE_TOGGLE } = permissions; + + expect( + await accessService.hasPermission(user, DELETE_PROJECT, projectName), + ).toBe(condition); + expect( + await accessService.hasPermission(user, UPDATE_PROJECT, projectName), + ).toBe(condition); + expect( + await accessService.hasPermission( + user, + MOVE_FEATURE_TOGGLE, + projectName, + ), + ); + hasCommonProjectAccess(user, projectName, condition); +}; + const createSuperUser = async () => { const { userStore } = stores; const user = await userStore.insert({ name: 'Alice Admin', email: 'admin@getunleash.io', }); - await accessService.addUserToRole(user.id, adminRole.id); + await accessService.addUserToRole(user.id, adminRole.id, ALL_PROJECTS); return user; }; @@ -41,11 +201,23 @@ beforeAll(async () => { db = await dbInit('access_service_serial', getLogger); stores = db.stores; // projectStore = stores.projectStore; + const config = createTestConfig({ + getLogger, + // @ts-ignore + experimental: { environments: { enabled: true } }, + }); accessService = new AccessService(stores, { getLogger }); const roles = await accessService.getRootRoles(); editorRole = roles.find((r) => r.name === RoleName.EDITOR); adminRole = roles.find((r) => r.name === RoleName.ADMIN); readRole = roles.find((r) => r.name === RoleName.VIEWER); + featureToggleService = new FeatureToggleService(stores, config); + projectService = new ProjectService( + stores, + config, + accessService, + featureToggleService, + ); editorUser = await createUserEditorAccess('Bob Test', 'bob@getunleash.io'); superUser = await createSuperUser(); @@ -106,45 +278,17 @@ test('should not have admin permission', async () => { expect(await accessService.hasPermission(user, ADMIN)).toBe(false); }); -test('should have project admin to default project', async () => { - const { - DELETE_PROJECT, - UPDATE_PROJECT, - CREATE_FEATURE, - UPDATE_FEATURE, - DELETE_FEATURE, - } = permissions; +test('should have project admin to default project as editor', async () => { + const projectName = 'default'; + const user = editorUser; - expect( - await accessService.hasPermission(user, DELETE_PROJECT, 'default'), - ).toBe(true); - expect( - await accessService.hasPermission(user, UPDATE_PROJECT, 'default'), - ).toBe(true); - expect( - await accessService.hasPermission(user, CREATE_FEATURE, 'default'), - ).toBe(true); - expect( - await accessService.hasPermission(user, UPDATE_FEATURE, 'default'), - ).toBe(true); - expect( - await accessService.hasPermission(user, DELETE_FEATURE, 'default'), - ).toBe(true); + hasFullProjectAccess(user, projectName, true); }); -test('should grant member CREATE_FEATURE on all projects', async () => { - const { CREATE_FEATURE } = permissions; +test('should not have project admin to other projects as editor', async () => { + const projectName = 'unusedprojectname'; const user = editorUser; - - await accessService.addPermissionToRole( - editorRole.id, - permissions.CREATE_FEATURE, - ALL_PROJECTS, - ); - - expect( - await accessService.hasPermission(user, CREATE_FEATURE, 'some-project'), - ).toBe(true); + hasFullProjectAccess(user, projectName, false); }); test('cannot add CREATE_FEATURE without defining project', async () => { @@ -169,20 +313,21 @@ test('cannot remove CREATE_FEATURE without defining project', async () => { ); }); -test('should remove CREATE_FEATURE on all projects', async () => { +test('should remove CREATE_FEATURE on default environment', async () => { const { CREATE_FEATURE } = permissions; const user = editorUser; + const editRole = await accessService.getRoleByName(RoleName.EDITOR); await accessService.addPermissionToRole( - editorRole.id, + editRole.id, permissions.CREATE_FEATURE, - ALL_PROJECTS, + '*', ); await accessService.removePermissionFromRole( - editorRole.id, + editRole.id, permissions.CREATE_FEATURE, - ALL_PROJECTS, + '*', ); expect( @@ -219,31 +364,10 @@ test('admin should be admin', async () => { }); test('should create default roles to project', async () => { - const { - DELETE_PROJECT, - UPDATE_PROJECT, - CREATE_FEATURE, - UPDATE_FEATURE, - DELETE_FEATURE, - } = permissions; const project = 'some-project'; const user = editorUser; await accessService.createDefaultProjectRoles(user, project); - expect( - await accessService.hasPermission(user, UPDATE_PROJECT, project), - ).toBe(true); - expect( - await accessService.hasPermission(user, DELETE_PROJECT, project), - ).toBe(true); - expect( - await accessService.hasPermission(user, CREATE_FEATURE, project), - ).toBe(true); - expect( - await accessService.hasPermission(user, UPDATE_FEATURE, project), - ).toBe(true); - expect( - await accessService.hasPermission(user, DELETE_FEATURE, project), - ).toBe(true); + hasFullProjectAccess(user, project, true); }); test('should require name when create default roles to project', async () => { @@ -253,38 +377,20 @@ test('should require name when create default roles to project', async () => { }); test('should grant user access to project', async () => { - const { - DELETE_PROJECT, - UPDATE_PROJECT, - CREATE_FEATURE, - UPDATE_FEATURE, - DELETE_FEATURE, - } = permissions; + const { DELETE_PROJECT, UPDATE_PROJECT } = permissions; const project = 'another-project'; const user = editorUser; - const sUser = await createUserEditorAccess( + const sUser = await createUserViewerAccess( 'Some Random', 'random@getunleash.io', ); await accessService.createDefaultProjectRoles(user, project); - const roles = await accessService.getRolesForProject(project); + const projectRole = await accessService.getRoleByName(RoleName.MEMBER); + await accessService.addUserToRole(sUser.id, projectRole.id, project); - const projectRole = roles.find( - (r) => r.name === 'Member' && r.project === project, - ); - await accessService.addUserToRole(sUser.id, projectRole.id); - - // Should be able to update feature toggles inside the project - expect( - await accessService.hasPermission(sUser, CREATE_FEATURE, project), - ).toBe(true); - expect( - await accessService.hasPermission(sUser, UPDATE_FEATURE, project), - ).toBe(true); - expect( - await accessService.hasPermission(sUser, DELETE_FEATURE, project), - ).toBe(true); + // // Should be able to update feature toggles inside the project + hasCommonProjectAccess(sUser, project, true); // Should not be able to admin the project itself. expect( @@ -296,32 +402,20 @@ test('should grant user access to project', async () => { }); test('should not get access if not specifying project', async () => { - const { CREATE_FEATURE, UPDATE_FEATURE, DELETE_FEATURE } = permissions; const project = 'another-project-2'; const user = editorUser; - const sUser = await createUserEditorAccess( + const sUser = await createUserViewerAccess( 'Some Random', 'random22@getunleash.io', ); await accessService.createDefaultProjectRoles(user, project); - const roles = await accessService.getRolesForProject(project); + const projectRole = await accessService.getRoleByName(RoleName.MEMBER); - const projectRole = roles.find( - (r) => r.name === 'Member' && r.project === project, - ); - await accessService.addUserToRole(sUser.id, projectRole.id); + await accessService.addUserToRole(sUser.id, projectRole.id, project); // Should not be able to update feature toggles outside project - expect(await accessService.hasPermission(sUser, CREATE_FEATURE)).toBe( - false, - ); - expect(await accessService.hasPermission(sUser, UPDATE_FEATURE)).toBe( - false, - ); - expect(await accessService.hasPermission(sUser, DELETE_FEATURE)).toBe( - false, - ); + hasCommonProjectAccess(sUser, undefined, false); }); test('should remove user from role', async () => { @@ -331,14 +425,14 @@ test('should remove user from role', async () => { email: 'random123@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id); + await accessService.addUserToRole(user.id, editorRole.id, 'default'); // check user has one role const userRoles = await accessService.getRolesForUser(user.id); expect(userRoles.length).toBe(1); expect(userRoles[0].name).toBe(RoleName.EDITOR); - await accessService.removeUserFromRole(user.id, editorRole.id); + await accessService.removeUserFromRole(user.id, editorRole.id, 'default'); const userRolesAfterRemove = await accessService.getRolesForUser(user.id); expect(userRolesAfterRemove.length).toBe(0); }); @@ -350,12 +444,11 @@ test('should return role with users', async () => { email: 'random2223@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id); - - const roleWithUsers = await accessService.getRole(editorRole.id); + await accessService.addUserToRole(user.id, editorRole.id, 'default'); + const roleWithUsers = await accessService.getRoleData(editorRole.id); expect(roleWithUsers.role.name).toBe(RoleName.EDITOR); - expect(roleWithUsers.users.length > 2).toBe(true); + expect(roleWithUsers.users.length >= 2).toBe(true); expect(roleWithUsers.users.find((u) => u.id === user.id)).toBeTruthy(); expect( roleWithUsers.users.find((u) => u.email === user.email), @@ -369,39 +462,20 @@ test('should return role with permissions and users', async () => { email: 'random2244@getunleash.io', }); - await accessService.addUserToRole(user.id, editorRole.id); + await accessService.addUserToRole(user.id, editorRole.id, 'default'); - const roleWithPermission = await accessService.getRole(editorRole.id); + const roleWithPermission = await accessService.getRoleData(editorRole.id); expect(roleWithPermission.role.name).toBe(RoleName.EDITOR); expect(roleWithPermission.permissions.length > 2).toBe(true); expect( roleWithPermission.permissions.find( - (p) => p.permission === permissions.CREATE_PROJECT, + (p) => p.name === permissions.CREATE_PROJECT, ), ).toBeTruthy(); - expect(roleWithPermission.users.length > 2).toBe(true); -}); - -test('should return list of permissions', async () => { - const p = await accessService.getPermissions(); - - const findPerm = (perm) => p.find((_) => _.name === perm); - - const { - DELETE_FEATURE, - UPDATE_FEATURE, - CREATE_FEATURE, - UPDATE_PROJECT, - CREATE_PROJECT, - } = permissions; - - expect(p.length > 2).toBe(true); - expect(findPerm(CREATE_PROJECT).type).toBe('root'); - expect(findPerm(UPDATE_PROJECT).type).toBe('project'); - expect(findPerm(CREATE_FEATURE).type).toBe('project'); - expect(findPerm(UPDATE_FEATURE).type).toBe('project'); - expect(findPerm(DELETE_FEATURE).type).toBe('project'); + //This assert requires other tests to have run in this pack before length > 2 resolves to true + // I've set this to be > 1, which allows us to run the test alone and should still satisfy the logic requirement + expect(roleWithPermission.users.length > 1).toBe(true); }); test('should set root role for user', async () => { @@ -414,9 +488,10 @@ test('should set root role for user', async () => { await accessService.setUserRootRole(user.id, editorRole.id); const roles = await accessService.getRolesForUser(user.id); + //To have duplicated roles like this may not may not be a hack. Needs some thought + expect(roles[0].name).toBe(RoleName.EDITOR); expect(roles.length).toBe(1); - expect(roles[0].name).toBe(RoleName.EDITOR); }); test('should switch root role for user', async () => { @@ -453,3 +528,250 @@ test('should not crash if user does not have permission', async () => { expect(hasAccess).toBe(false); }); + +test('should support permission with "ALL" environment requirement', async () => { + const { userStore, roleStore, accessStore } = stores; + + const user = await userStore.insert({ + name: 'Some User', + email: 'randomEnv1@getunleash.io', + }); + + await accessService.setUserRootRole(user.id, readRole.id); + + const customRole = await roleStore.create({ + name: 'Power user', + roleType: 'custom', + description: 'Grants access to modify all environments', + }); + + const { CREATE_FEATURE_STRATEGY } = permissions; + await accessStore.addPermissionsToRole( + customRole.id, + [CREATE_FEATURE_STRATEGY], + 'production', + ); + await accessStore.addUserToRole(user.id, customRole.id, ALL_PROJECTS); + + const hasAccess = await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + 'default', + 'production', + ); + + expect(hasAccess).toBe(true); + + const hasNotAccess = await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + 'default', + 'development', + ); + expect(hasNotAccess).toBe(false); +}); + +test('Should have access to create a strategy in an environment', async () => { + const { CREATE_FEATURE_STRATEGY } = permissions; + const user = editorUser; + expect( + await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + 'default', + 'development', + ), + ).toBe(true); +}); + +test('Should be denied access to create a strategy in an environment the user does not have access to', async () => { + const { CREATE_FEATURE_STRATEGY } = permissions; + const user = editorUser; + expect( + await accessService.hasPermission( + user, + CREATE_FEATURE_STRATEGY, + 'default', + 'noaccess', + ), + ).toBe(false); +}); + +test('Should have access to edit a strategy in an environment', async () => { + const { UPDATE_FEATURE_STRATEGY } = permissions; + const user = editorUser; + expect( + await accessService.hasPermission( + user, + UPDATE_FEATURE_STRATEGY, + 'default', + 'development', + ), + ).toBe(true); +}); + +test('Should have access to delete a strategy in an environment', async () => { + const { DELETE_FEATURE_STRATEGY } = permissions; + const user = editorUser; + expect( + await accessService.hasPermission( + user, + DELETE_FEATURE_STRATEGY, + 'default', + 'development', + ), + ).toBe(true); +}); + +test('Should be denied access to delete a strategy in an environment the user does not have access to', async () => { + const { DELETE_FEATURE_STRATEGY } = permissions; + const user = editorUser; + expect( + await accessService.hasPermission( + user, + DELETE_FEATURE_STRATEGY, + 'default', + 'noaccess', + ), + ).toBe(false); +}); + +test('Should be denied access to delete a role that is in use', async () => { + const user = editorUser; + + const project = { + id: 'projectToUseRole', + name: 'New project', + description: 'Blah', + }; + await projectService.createProject(project, user.id); + + const projectMember = await stores.userStore.insert({ + name: 'CustomProjectMember', + email: 'custom@getunleash.io', + }); + + const customRole = await accessService.createRole({ + name: 'RoleInUse', + description: '', + permissions: [ + { + id: 2, + name: 'CREATE_FEATURE', + environment: null, + displayName: 'Create Feature Toggles', + type: 'project', + }, + { + id: 8, + name: 'DELETE_FEATURE', + environment: null, + displayName: 'Delete Feature Toggles', + type: 'project', + }, + ], + }); + + await projectService.addUser(project.id, customRole.id, projectMember.id); + + try { + await accessService.deleteRole(customRole.id); + } catch (e) { + expect(e.toString()).toBe( + 'RoleInUseError: Role is in use by more than one user. You cannot delete a role that is in use without first removing the role from the users.', + ); + } +}); + +test('Should be denied move feature toggle to project where the user does not have access', async () => { + const user = editorUser; + const editorUser2 = await createUserEditorAccess( + 'seconduser', + 'bob2@gmail.com', + ); + + const projectOrigin = { + id: 'projectOrigin', + name: 'New project', + description: 'Blah', + }; + const projectDest = { + id: 'projectDest', + name: 'New project', + description: 'Blah', + }; + await projectService.createProject(projectOrigin, user.id); + await projectService.createProject(projectDest, editorUser2.id); + + const featureToggle = { name: 'moveableToggle' }; + + await featureToggleService.createFeatureToggle( + projectOrigin.id, + featureToggle, + user.username, + ); + + try { + await projectService.changeProject( + projectDest.id, + featureToggle.name, + user, + projectOrigin.id, + ); + } catch (e) { + expect(e.toString()).toBe( + 'NoAccessError: You need permission=MOVE_FEATURE_TOGGLE to perform this action', + ); + } +}); + +test('Should be allowed move feature toggle to project when the user has access', async () => { + const user = editorUser; + + const projectOrigin = { + id: 'projectOrigin1', + name: 'New project', + description: 'Blah', + }; + const projectDest = { + id: 'projectDest2', + name: 'New project', + description: 'Blah', + }; + await projectService.createProject(projectOrigin, user); + await projectService.createProject(projectDest, user); + + const featureToggle = { name: 'moveableToggle2' }; + + await featureToggleService.createFeatureToggle( + projectOrigin.id, + featureToggle, + user.username, + ); + + await projectService.changeProject( + projectDest.id, + featureToggle.name, + user, + projectOrigin.id, + ); +}); + +test('Should not be allowed to edit a built in role', async () => { + expect.assertions(1); + + const editRole = await accessService.getRoleByName(RoleName.EDITOR); + const roleUpdate = { + id: editRole.id, + name: 'NoLongerTheEditor', + description: 'Ha!', + }; + + try { + await accessService.updateRole(roleUpdate); + } catch (e) { + expect(e.toString()).toBe( + 'InvalidOperationError: You can not change built in roles.', + ); + } +}); diff --git a/src/test/e2e/services/project-service.e2e.test.ts b/src/test/e2e/services/project-service.e2e.test.ts index 05e26f7f05..fac909c19e 100644 --- a/src/test/e2e/services/project-service.e2e.test.ts +++ b/src/test/e2e/services/project-service.e2e.test.ts @@ -3,12 +3,7 @@ import getLogger from '../../fixtures/no-logger'; import FeatureToggleService from '../../../lib/services/feature-toggle-service'; import ProjectService from '../../../lib/services/project-service'; import { AccessService } from '../../../lib/services/access-service'; -import { - CREATE_FEATURE, - UPDATE_FEATURE, - UPDATE_PROJECT, -} from '../../../lib/types/permissions'; -import NotFoundError from '../../../lib/error/notfound-error'; +import { MOVE_FEATURE_TOGGLE } from '../../../lib/types/permissions'; import { createTestConfig } from '../../config/test-config'; import { RoleName } from '../../../lib/types/model'; @@ -53,7 +48,12 @@ afterEach(async () => { .map(async (env) => { await stores.environmentStore.delete(env.name); }); + const users = await stores.userStore.getAll(); + const wipeUserPermissions = users.map(async (u) => { + await stores.accessStore.unlinkUserRoles(u.id); + }); await Promise.allSettled(deleteEnvs); + await Promise.allSettled(wipeUserPermissions); }); test('should have default project', async () => { @@ -202,37 +202,6 @@ test('should give error when getting unknown project', async () => { } }); -test('(TODO: v4): should create roles for new project if userId is missing', async () => { - const project = { - id: 'test-roles-no-id', - name: 'New project', - description: 'Blah', - }; - await projectService.createProject(project, { - username: 'random-user', - }); - const roles = await stores.accessStore.getRolesForProject(project.id); - - expect(roles).toHaveLength(2); - expect( - await accessService.hasPermission(user, UPDATE_PROJECT, project.id), - ).toBe(false); -}); - -test('should create roles when project is created', async () => { - const project = { - id: 'test-roles', - name: 'New project', - description: 'Blah', - }; - await projectService.createProject(project, user); - const roles = await stores.accessStore.getRolesForProject(project.id); - expect(roles).toHaveLength(2); - expect( - await accessService.hasPermission(user, UPDATE_PROJECT, project.id), - ).toBe(true); -}); - test('should get list of users with access to project', async () => { const project = { id: 'test-roles-access', @@ -240,13 +209,10 @@ test('should get list of users with access to project', async () => { description: 'Blah', }; await projectService.createProject(project, user); - const { roles, users } = await projectService.getUsersWithAccess( - project.id, - user, - ); + const { users } = await projectService.getUsersWithAccess(project.id, user); - const owner = roles.find((role) => role.name === RoleName.OWNER); - const member = roles.find((role) => role.name === RoleName.MEMBER); + const member = await stores.roleStore.getRoleByName(RoleName.MEMBER); + const owner = await stores.roleStore.getRoleByName(RoleName.OWNER); expect(users).toHaveLength(1); expect(users[0].id).toBe(user.id); @@ -272,8 +238,7 @@ test('should add a member user to the project', async () => { email: 'member2@getunleash.io', }); - const roles = await stores.accessStore.getRolesForProject(project.id); - const memberRole = roles.find((r) => r.name === RoleName.MEMBER); + const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER); await projectService.addUser(project.id, memberRole.id, projectMember1.id); await projectService.addUser(project.id, memberRole.id, projectMember2.id); @@ -305,10 +270,7 @@ test('should add admin users to the project', async () => { email: 'admin2@getunleash.io', }); - const projectRoles = await stores.accessStore.getRolesForProject( - project.id, - ); - const ownerRole = projectRoles.find((r) => r.name === RoleName.OWNER); + const ownerRole = await stores.roleStore.getRoleByName(RoleName.OWNER); await projectService.addUser(project.id, ownerRole.id, projectAdmin1.id); await projectService.addUser(project.id, ownerRole.id, projectAdmin2.id); @@ -324,15 +286,6 @@ test('should add admin users to the project', async () => { expect(adminUsers[2].name).toBe(projectAdmin2.name); }); -test('add user only accept to add users to project roles', async () => { - const roles = await accessService.getRoles(); - const memberRole = roles.find((r) => r.name === RoleName.MEMBER); - - await expect(async () => { - await projectService.addUser('some-id', memberRole.id, user.id); - }).rejects.toThrowError(NotFoundError); -}); - test('add user should fail if user already have access', async () => { const project = { id: 'add-users-twice', @@ -346,15 +299,14 @@ test('add user should fail if user already have access', async () => { email: 'member42@getunleash.io', }); - const roles = await stores.accessStore.getRolesForProject(project.id); - const memberRole = roles.find((r) => r.name === RoleName.MEMBER); + const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER); await projectService.addUser(project.id, memberRole.id, projectMember1.id); await expect(async () => projectService.addUser(project.id, memberRole.id, projectMember1.id), ).rejects.toThrow( - new Error('User already have access to project=add-users-twice'), + new Error('User already has access to project=add-users-twice'), ); }); @@ -371,8 +323,7 @@ test('should remove user from the project', async () => { email: 'member99@getunleash.io', }); - const roles = await stores.accessStore.getRolesForProject(project.id); - const memberRole = roles.find((r) => r.name === RoleName.MEMBER); + const memberRole = await stores.roleStore.getRoleByName(RoleName.MEMBER); await projectService.addUser(project.id, memberRole.id, projectMember1.id); await projectService.removeUser( @@ -395,7 +346,7 @@ test('should not remove user from the project', async () => { }; await projectService.createProject(project, user); - const roles = await stores.accessStore.getRolesForProject(project.id); + const roles = await stores.roleStore.getRolesForProject(project.id); const ownerRole = roles.find((r) => r.name === RoleName.OWNER); await expect(async () => { @@ -426,7 +377,7 @@ test('should not change project if feature toggle project does not match current ); } catch (err) { expect(err.message).toBe( - `You need permission=${UPDATE_FEATURE} to perform this action`, + `You need permission=${MOVE_FEATURE_TOGGLE} to perform this action`, ); } }); @@ -487,7 +438,7 @@ test('should fail if user is not authorized', async () => { ); } catch (err) { expect(err.message).toBe( - `You need permission=${CREATE_FEATURE} to perform this action`, + `You need permission=${MOVE_FEATURE_TOGGLE} to perform this action`, ); } }); @@ -548,3 +499,156 @@ test('A newly created project only gets connected to enabled environments', asyn expect(connectedEnvs.some((e) => e === enabledEnv)).toBeTruthy(); expect(connectedEnvs.some((e) => e === disabledEnv)).toBeFalsy(); }); + +test('should add a user to the project with a custom role', async () => { + const project = { + id: 'add-users-custom-role', + name: 'New project', + description: 'Blah', + }; + await projectService.createProject(project, user); + + const projectMember1 = await stores.userStore.insert({ + name: 'Custom', + email: 'custom@getunleash.io', + }); + + const customRole = await accessService.createRole({ + name: 'Service Engineer2', + description: '', + permissions: [ + { + id: 2, + name: 'CREATE_FEATURE', + environment: null, + displayName: 'Create Feature Toggles', + type: 'project', + }, + { + id: 8, + name: 'DELETE_FEATURE', + environment: null, + displayName: 'Delete Feature Toggles', + type: 'project', + }, + ], + }); + + await projectService.addUser(project.id, customRole.id, projectMember1.id); + + const { users } = await projectService.getUsersWithAccess(project.id, user); + + const customRoleMember = users.filter((u) => u.roleId === customRole.id); + + expect(customRoleMember).toHaveLength(1); + expect(customRoleMember[0].id).toBe(projectMember1.id); + expect(customRoleMember[0].name).toBe(projectMember1.name); +}); + +test('should delete role entries when deleting project', async () => { + const project = { + id: 'test-delete-users-1', + name: 'New project', + description: 'Blah', + }; + + await projectService.createProject(project, user); + + const user1 = await stores.userStore.insert({ + name: 'Projectuser1', + email: 'project1@getunleash.io', + }); + + const user2 = await stores.userStore.insert({ + name: 'Projectuser2', + email: 'project2@getunleash.io', + }); + + const customRole = await accessService.createRole({ + name: 'Service Engineer', + description: '', + permissions: [ + { + id: 2, + name: 'CREATE_FEATURE', + environment: null, + displayName: 'Create Feature Toggles', + type: 'project', + }, + { + id: 8, + name: 'DELETE_FEATURE', + environment: null, + displayName: 'Delete Feature Toggles', + type: 'project', + }, + ], + }); + + await projectService.addUser(project.id, customRole.id, user1.id); + await projectService.addUser(project.id, customRole.id, user2.id); + + let usersForRole = await accessService.getUsersForRole(customRole.id); + expect(usersForRole.length).toBe(2); + + await projectService.deleteProject(project.id, user); + usersForRole = await accessService.getUsersForRole(customRole.id); + expect(usersForRole.length).toBe(0); +}); + +test('should change a users role in the project', async () => { + const project = { + id: 'test-change-user-role', + name: 'New project', + description: 'Blah', + }; + + await projectService.createProject(project, user); + + const projectUser = await stores.userStore.insert({ + name: 'Projectuser3', + email: 'project3@getunleash.io', + }); + + const customRole = await accessService.createRole({ + name: 'Service Engineer3', + description: '', + permissions: [ + { + id: 2, + name: 'CREATE_FEATURE', + environment: null, + displayName: 'Create Feature Toggles', + type: 'project', + }, + { + id: 8, + name: 'DELETE_FEATURE', + environment: null, + displayName: 'Delete Feature Toggles', + type: 'project', + }, + ], + }); + const member = await stores.roleStore.getRoleByName(RoleName.MEMBER); + + await projectService.addUser(project.id, member.id, projectUser.id); + const { users } = await projectService.getUsersWithAccess(project.id, user); + let memberUser = users.filter((u) => u.roleId === member.id); + + expect(memberUser).toHaveLength(1); + expect(memberUser[0].id).toBe(projectUser.id); + expect(memberUser[0].name).toBe(projectUser.name); + await projectService.removeUser(project.id, member.id, projectUser.id); + await projectService.addUser(project.id, customRole.id, projectUser.id); + + let { users: updatedUsers } = await projectService.getUsersWithAccess( + project.id, + user, + ); + const customUser = updatedUsers.filter((u) => u.roleId === customRole.id); + + expect(customUser).toHaveLength(1); + expect(customUser[0].id).toBe(projectUser.id); + expect(customUser[0].name).toBe(projectUser.name); +}); diff --git a/src/test/fixtures/access-service-mock.ts b/src/test/fixtures/access-service-mock.ts index c8c5089bbd..d11c945b5f 100644 --- a/src/test/fixtures/access-service-mock.ts +++ b/src/test/fixtures/access-service-mock.ts @@ -4,12 +4,21 @@ import { AccessService } from '../../lib/services/access-service'; import User from '../../lib/types/user'; import noLoggerProvider from './no-logger'; import { IRole } from '../../lib/types/stores/access-store'; -import { IPermission, IRoleData, IUserWithRole } from '../../lib/types/model'; +import { + IAvailablePermissions, + IRoleData, + IUserWithRole, +} from '../../lib/types/model'; class AccessServiceMock extends AccessService { constructor() { super( - { accessStore: undefined, userStore: undefined }, + { + accessStore: undefined, + userStore: undefined, + roleStore: undefined, + environmentStore: undefined, + }, { getLogger: noLoggerProvider }, ); } @@ -22,7 +31,7 @@ class AccessServiceMock extends AccessService { throw new Error('Method not implemented.'); } - getPermissions(): IPermission[] { + getPermissions(): Promise { throw new Error('Method not implemented.'); } @@ -34,10 +43,6 @@ class AccessServiceMock extends AccessService { return Promise.resolve(); } - removeUserFromRole(userId: number, roleId: number): Promise { - throw new Error('Method not implemented.'); - } - addPermissionToRole( roleId: number, permission: string, @@ -58,10 +63,6 @@ class AccessServiceMock extends AccessService { throw new Error('Method not implemented.'); } - getRole(roleId: number): Promise { - throw new Error('Method not implemented.'); - } - getRolesForProject(projectId: string): Promise { throw new Error('Method not implemented.'); } diff --git a/src/test/fixtures/fake-access-store.ts b/src/test/fixtures/fake-access-store.ts index 0e637d03dd..cc50d1dd18 100644 --- a/src/test/fixtures/fake-access-store.ts +++ b/src/test/fixtures/fake-access-store.ts @@ -6,17 +6,60 @@ import { IUserPermission, IUserRole, } from '../../lib/types/stores/access-store'; +import { IAvailablePermissions, IPermission } from 'lib/types/model'; class AccessStoreMock implements IAccessStore { + removeUserFromRole( + userId: number, + roleId: number, + projectId: string, + ): Promise { + throw new Error('Method not implemented.'); + } + + wipePermissionsFromRole(role_id: number): Promise { + throw new Error('Method not implemented.'); + } + + unlinkUserRoles(userId: number): Promise { + throw new Error('Method not implemented.'); + } + + getRoleByName(name: string): Promise { + throw new Error('Method not implemented.'); + } + + getProjectUserIdsForRole( + roleId: number, + projectId?: string, + ): Promise { + throw new Error('Method not implemented.'); + } + + getProjectRoles(): Promise { + throw new Error('Method not implemented.'); + } + + addEnvironmentPermissionsToRole( + role_id: number, + permissions: IPermission[], + ): Promise { + throw new Error('Method not implemented.'); + } + userPermissions: IUserPermission[] = []; roles: IRole[] = []; + getAvailablePermissions(): Promise { + throw new Error('Method not implemented.'); + } + getPermissionsForUser(userId: Number): Promise { return Promise.resolve([]); } - getPermissionsForRole(roleId: number): Promise { + getPermissionsForRole(roleId: number): Promise { throw new Error('Method not implemented.'); } @@ -40,7 +83,7 @@ class AccessStoreMock implements IAccessStore { return Promise.resolve([]); } - getUserIdsForRole(roleId: number): Promise { + getUserIdsForRole(roleId: number, projectId: string): Promise { throw new Error('Method not implemented.'); } @@ -48,19 +91,6 @@ class AccessStoreMock implements IAccessStore { throw new Error('Method not implemented.'); } - removeUserFromRole(userId: number, roleId: number): Promise { - throw new Error('Method not implemented.'); - } - - createRole( - name: string, - type: string, - project?: string, - description?: string, - ): Promise { - throw new Error('Method not implemented.'); - } - addPermissionsToRole( role_id: number, permissions: string[], diff --git a/src/test/fixtures/fake-role-store.ts b/src/test/fixtures/fake-role-store.ts new file mode 100644 index 0000000000..0efc5881c3 --- /dev/null +++ b/src/test/fixtures/fake-role-store.ts @@ -0,0 +1,74 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { ICustomRole } from 'lib/types/model'; +import { IRole, IUserRole } from 'lib/types/stores/access-store'; +import { + ICustomRoleInsert, + ICustomRoleUpdate, + IRoleStore, +} from 'lib/types/stores/role-store'; + +export default class FakeRoleStore implements IRoleStore { + nameInUse(name: string, existingId: number): Promise { + throw new Error('Method not implemented.'); + } + + getAll(): Promise { + throw new Error('Method not implemented.'); + } + + create(role: ICustomRoleInsert): Promise { + throw new Error('Method not implemented.'); + } + + update(role: ICustomRoleUpdate): Promise { + throw new Error('Method not implemented.'); + } + + delete(id: number): Promise { + throw new Error('Method not implemented.'); + } + + getRoles(): Promise { + throw new Error('Method not implemented.'); + } + + getRoleByName(name: string): Promise { + throw new Error('Method not implemented.'); + } + + getRolesForProject(projectId: string): Promise { + throw new Error('Method not implemented.'); + } + + removeRolesForProject(projectId: string): Promise { + throw new Error('Method not implemented.'); + } + + getProjectRoles(): Promise { + throw new Error('Method not implemented.'); + } + + getRootRoles(): Promise { + throw new Error('Method not implemented.'); + } + + getRootRoleForAllUsers(): Promise { + throw new Error('Method not implemented.'); + } + + get(key: number): Promise { + throw new Error('Method not implemented.'); + } + + exists(key: number): Promise { + throw new Error('Method not implemented.'); + } + + deleteAll(): Promise { + throw new Error('Method not implemented.'); + } + + destroy(): void { + throw new Error('Method not implemented.'); + } +} diff --git a/src/test/fixtures/store.ts b/src/test/fixtures/store.ts index 2e5cf67dfc..d2724626ba 100644 --- a/src/test/fixtures/store.ts +++ b/src/test/fixtures/store.ts @@ -24,6 +24,7 @@ import FakeResetTokenStore from './fake-reset-token-store'; import FakeFeatureToggleClientStore from './fake-feature-toggle-client-store'; import FakeClientMetricsStoreV2 from './fake-client-metrics-store-v2'; import FakeUserSplashStore from './fake-user-splash-store'; +import FakeRoleStore from './fake-role-store'; const createStores: () => IUnleashStores = () => { const db = { @@ -59,6 +60,7 @@ const createStores: () => IUnleashStores = () => { resetTokenStore: new FakeResetTokenStore(), sessionStore: new FakeSessionStore(), userSplashStore: new FakeUserSplashStore(), + roleStore: new FakeRoleStore(), }; }; diff --git a/yarn.lock b/yarn.lock index 27303d3932..1fde6e9035 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,7 +18,7 @@ "@babel/code-frame@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789" + resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz" integrity sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg== dependencies: "@babel/highlight" "^7.16.7" @@ -30,12 +30,12 @@ "@babel/compat-data@^7.16.4": version "7.16.4" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz" integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== -"@babel/core@7.16.7", "@babel/core@^7.12.3", "@babel/core@^7.8.0": +"@babel/core@7.16.7", "@babel/core@^7.0.0", "@babel/core@^7.12.3", "@babel/core@^7.8.0": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.7.tgz#db990f931f6d40cb9b87a0dc7d2adc749f1dcbcf" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.16.7.tgz" integrity sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA== dependencies: "@babel/code-frame" "^7.16.7" @@ -54,7 +54,7 @@ semver "^6.3.0" source-map "^0.5.0" -"@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.7.2": +"@babel/core@^7.1.0", "@babel/core@^7.7.2": version "7.15.0" resolved "https://registry.npmjs.org/@babel/core/-/core-7.15.0.tgz" integrity sha512-tXtmTminrze5HEUPn/a0JtOzzfp0nk+UEXQ/tqIJo3WDGypl/2OFQEMll/zSFU8f/lfmfLXvTaORHF3cfXIQMw== @@ -86,7 +86,7 @@ "@babel/generator@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.16.7.tgz#b42bf46a3079fa65e1544135f32e7958f048adbb" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz" integrity sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg== dependencies: "@babel/types" "^7.16.7" @@ -105,7 +105,7 @@ "@babel/helper-compilation-targets@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz#06e66c5f299601e6c7da350049315e83209d551b" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.7.tgz" integrity sha512-mGojBwIWcwGD6rfqgRXVlVYmPAv7eOpIemUG3dGnDdCY4Pae70ROij3XmfrH6Fa1h1aiDylpglbZyktfzyo/hA== dependencies: "@babel/compat-data" "^7.16.4" @@ -115,7 +115,7 @@ "@babel/helper-environment-visitor@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + resolved "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz" integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== dependencies: "@babel/types" "^7.16.7" @@ -131,7 +131,7 @@ "@babel/helper-function-name@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz#f1ec51551fb1c8956bc8dd95f38523b6cf375f8f" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz" integrity sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA== dependencies: "@babel/helper-get-function-arity" "^7.16.7" @@ -147,7 +147,7 @@ "@babel/helper-get-function-arity@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" + resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz" integrity sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw== dependencies: "@babel/types" "^7.16.7" @@ -161,7 +161,7 @@ "@babel/helper-hoist-variables@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz#86bcb19a77a509c7b77d0e22323ef588fa58c246" + resolved "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz" integrity sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg== dependencies: "@babel/types" "^7.16.7" @@ -182,7 +182,7 @@ "@babel/helper-module-imports@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz" integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== dependencies: "@babel/types" "^7.16.7" @@ -203,7 +203,7 @@ "@babel/helper-module-transforms@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz#7665faeb721a01ca5327ddc6bba15a5cb34b6a41" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz" integrity sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng== dependencies: "@babel/helper-environment-visitor" "^7.16.7" @@ -251,7 +251,7 @@ "@babel/helper-simple-access@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz#d656654b9ea08dbb9659b69d61063ccd343ff0f7" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.7.tgz" integrity sha512-ZIzHVyoeLMvXMN/vok/a4LWRy8G2v205mNP0XOuf9XRLyX5/u9CnVulUtDgUTama3lT+bf/UqucuZjqiGuTS1g== dependencies: "@babel/types" "^7.16.7" @@ -265,7 +265,7 @@ "@babel/helper-split-export-declaration@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz#0b648c0c42da9d3920d85ad585f2778620b8726b" + resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz" integrity sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw== dependencies: "@babel/types" "^7.16.7" @@ -277,7 +277,7 @@ "@babel/helper-validator-identifier@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz" integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== "@babel/helper-validator-option@^7.14.5": @@ -287,7 +287,7 @@ "@babel/helper-validator-option@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz#b203ce62ce5fe153899b617c08957de860de4d23" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz" integrity sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ== "@babel/helpers@^7.14.8": @@ -301,7 +301,7 @@ "@babel/helpers@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.16.7.tgz#7e3504d708d50344112767c3542fc5e357fffefc" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.7.tgz" integrity sha512-9ZDoqtfY7AuEOt3cxchfii6C7GDyyMBffktR5B2jvWv8u2+efwvpnVKXMWzNehqy68tKgAfSwfdw/lWpthS2bw== dependencies: "@babel/template" "^7.16.7" @@ -319,7 +319,7 @@ "@babel/highlight@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.16.7.tgz#81a01d7d675046f0d96f82450d9d9578bdfd6b0b" + resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz" integrity sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw== dependencies: "@babel/helper-validator-identifier" "^7.16.7" @@ -333,7 +333,7 @@ "@babel/parser@^7.14.7", "@babel/parser@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.7.tgz#d372dda9c89fcec340a82630a9f533f2fe15877e" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz" integrity sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA== "@babel/plugin-syntax-async-generators@^7.8.4": @@ -445,7 +445,7 @@ "@babel/template@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz" integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== dependencies: "@babel/code-frame" "^7.16.7" @@ -469,7 +469,7 @@ "@babel/traverse@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.16.7.tgz#dac01236a72c2560073658dd1a285fe4e0865d76" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz" integrity sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ== dependencies: "@babel/code-frame" "^7.16.7" @@ -493,7 +493,7 @@ "@babel/types@^7.16.7": version "7.16.7" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.7.tgz#4ed19d51f840ed4bd5645be6ce40775fecf03159" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz" integrity sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg== dependencies: "@babel/helper-validator-identifier" "^7.16.7" @@ -511,14 +511,14 @@ "@cspotcode/source-map-support@0.7.0": version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" + resolved "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz" integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== dependencies: "@cspotcode/source-map-consumer" "0.8.0" "@eslint/eslintrc@^1.0.5": version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" + resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.0.5.tgz" integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== dependencies: ajv "^6.12.4" @@ -545,7 +545,7 @@ "@humanwhocodes/config-array@^0.9.2": version "0.9.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" + resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.2.tgz" integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== dependencies: "@humanwhocodes/object-schema" "^1.2.1" @@ -554,7 +554,7 @@ "@humanwhocodes/object-schema@^1.2.1": version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== "@istanbuljs/load-nyc-config@^1.0.0": @@ -575,7 +575,7 @@ "@jest/console@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.4.6.tgz#0742e6787f682b22bdad56f9db2a8a77f6a86107" + resolved "https://registry.npmjs.org/@jest/console/-/console-27.4.6.tgz" integrity sha512-jauXyacQD33n47A44KrlOVeiXHEXDqapSdfb9kTekOchH/Pd18kBIO1+xxJQRLuG+LUuljFCwTG92ra4NW7SpA== dependencies: "@jest/types" "^27.4.2" @@ -587,7 +587,7 @@ "@jest/core@^27.4.7": version "27.4.7" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.4.7.tgz#84eabdf42a25f1fa138272ed229bcf0a1b5e6913" + resolved "https://registry.npmjs.org/@jest/core/-/core-27.4.7.tgz" integrity sha512-n181PurSJkVMS+kClIFSX/LLvw9ExSb+4IMtD6YnfxZVerw9ANYtW0bPrm0MJu2pfe9SY9FJ9FtQ+MdZkrZwjg== dependencies: "@jest/console" "^27.4.6" @@ -621,7 +621,7 @@ "@jest/environment@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.4.6.tgz#1e92885d64f48c8454df35ed9779fbcf31c56d8b" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz" integrity sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg== dependencies: "@jest/fake-timers" "^27.4.6" @@ -631,7 +631,7 @@ "@jest/fake-timers@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.4.6.tgz#e026ae1671316dbd04a56945be2fa251204324e8" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz" integrity sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A== dependencies: "@jest/types" "^27.4.2" @@ -643,7 +643,7 @@ "@jest/globals@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.4.6.tgz#3f09bed64b0fd7f5f996920258bd4be8f52f060a" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.4.6.tgz" integrity sha512-kAiwMGZ7UxrgPzu8Yv9uvWmXXxsy0GciNejlHvfPIfWkSxChzv6bgTS3YqBkGuHcis+ouMFI2696n2t+XYIeFw== dependencies: "@jest/environment" "^27.4.6" @@ -652,7 +652,7 @@ "@jest/reporters@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.4.6.tgz#b53dec3a93baf9b00826abf95b932de919d6d8dd" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.4.6.tgz" integrity sha512-+Zo9gV81R14+PSq4wzee4GC2mhAN9i9a7qgJWL90Gpx7fHYkWpTBvwWNZUXvJByYR9tAVBdc8VxDWqfJyIUrIQ== dependencies: "@bcoe/v8-coverage" "^0.2.3" @@ -683,7 +683,7 @@ "@jest/source-map@^27.4.0": version "27.4.0" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.4.0.tgz#2f0385d0d884fb3e2554e8f71f8fa957af9a74b6" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.4.0.tgz" integrity sha512-Ntjx9jzP26Bvhbm93z/AKcPRj/9wrkI88/gK60glXDx1q+IeI0rf7Lw2c89Ch6ofonB0On/iRDreQuQ6te9pgQ== dependencies: callsites "^3.0.0" @@ -692,7 +692,7 @@ "@jest/test-result@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.4.6.tgz#b3df94c3d899c040f602cea296979844f61bdf69" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.4.6.tgz" integrity sha512-fi9IGj3fkOrlMmhQqa/t9xum8jaJOOAi/lZlm6JXSc55rJMXKHxNDN1oCP39B0/DhNOa2OMupF9BcKZnNtXMOQ== dependencies: "@jest/console" "^27.4.6" @@ -702,7 +702,7 @@ "@jest/test-sequencer@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz#447339b8a3d7b5436f50934df30854e442a9d904" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.4.6.tgz" integrity sha512-3GL+nsf6E1PsyNsJuvPyIz+DwFuCtBdtvPpm/LMXVkBJbdFvQYCDpccYT56qq5BGniXWlE81n2qk1sdXfZebnw== dependencies: "@jest/test-result" "^27.4.6" @@ -712,7 +712,7 @@ "@jest/transform@^27.4.6": version "27.4.6" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.4.6.tgz#153621940b1ed500305eacdb31105d415dc30231" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.4.6.tgz" integrity sha512-9MsufmJC8t5JTpWEQJ0OcOOAXaH5ioaIX6uHVBLBMoCZPfKKQF+EqP8kACAvCZ0Y1h2Zr3uOccg8re+Dr5jxyw== dependencies: "@babel/core" "^7.1.0" @@ -744,7 +744,7 @@ "@jest/types@^27.4.2": version "27.4.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.4.2.tgz#96536ebd34da6392c2b7c7737d693885b5dd44a5" + resolved "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz" integrity sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg== dependencies: "@types/istanbul-lib-coverage" "^2.0.0" @@ -800,7 +800,7 @@ "@sinonjs/fake-timers@^8.0.1": version "8.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz#1c1c9a91419f804e59ae8df316a07dd1c3a76b94" + resolved "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.0.1.tgz" integrity sha512-AU7kwFxreVd6OAXcAFlKSmZquiRUU0FvYm44k1Y1QbK7Co4m0aqfGMhjykIeQp/H6rcl+nFmj0zfdUcGVs9Dew== dependencies: "@sinonjs/commons" "^1.7.0" @@ -865,7 +865,7 @@ "@types/bcryptjs@2.4.2": version "2.4.2" - resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.2.tgz#e3530eac9dd136bfdfb0e43df2c4c5ce1f77dfae" + resolved "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz" integrity sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ== "@types/body-parser@*": @@ -899,14 +899,14 @@ "@types/express-session@1.17.4": version "1.17.4" - resolved "https://registry.yarnpkg.com/@types/express-session/-/express-session-1.17.4.tgz#97a30a35e853a61bdd26e727453b8ed314d6166b" + resolved "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.4.tgz" integrity sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg== dependencies: "@types/express" "*" "@types/express@*", "@types/express@4.17.13": version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + resolved "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz" integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== dependencies: "@types/body-parser" "*" @@ -916,7 +916,7 @@ "@types/faker@5.5.9": version "5.5.9" - resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.5.9.tgz#588ede92186dc557bff8341d294335d50d255f0c" + resolved "https://registry.npmjs.org/@types/faker/-/faker-5.5.9.tgz" integrity sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA== "@types/graceful-fs@^4.1.2": @@ -947,7 +947,7 @@ "@types/jest@27.4.0": version "27.4.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.4.0.tgz#037ab8b872067cae842a320841693080f9cb84ed" + resolved "https://registry.npmjs.org/@types/jest/-/jest-27.4.0.tgz" integrity sha512-gHl8XuC1RZ8H2j5sHv/JqsaxXkDDM9iDOgu0Wp8sjs4u/snb2PVehyWXJPr+ORA0RPpgw231mnutWI1+0hgjIQ== dependencies: jest-diff "^27.0.0" @@ -955,27 +955,27 @@ "@types/js-yaml@4.0.5": version "4.0.5" - resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" + resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.5.tgz" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== "@types/json-schema@^7.0.9": version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" + resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== "@types/json5@^0.0.29": version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + resolved "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= "@types/memoizee@0.4.7": version "0.4.7" - resolved "https://registry.yarnpkg.com/@types/memoizee/-/memoizee-0.4.7.tgz#fa0025b5ea1c8acb85fd52a76982c5a096c2c91e" + resolved "https://registry.npmjs.org/@types/memoizee/-/memoizee-0.4.7.tgz" integrity sha512-EMtvWnRC/W0KYmXxbPJAMg/M1OXkFboSpd/IzkNMDXfoFPE4uom1RHFl8q7m/4R0y9eG1yaoaIPiMHk0vMA0eA== "@types/mime@2.0.3": version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.3.tgz#c893b73721db73699943bfc3653b1deb7faa4a3a" + resolved "https://registry.npmjs.org/@types/mime/-/mime-2.0.3.tgz" integrity sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q== "@types/mime@^1": @@ -990,7 +990,7 @@ "@types/node-fetch@2.5.12": version "2.5.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" + resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.12.tgz" integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== dependencies: "@types/node" "*" @@ -1003,7 +1003,7 @@ "@types/nodemailer@6.4.4": version "6.4.4" - resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.4.tgz#c265f7e7a51df587597b3a49a023acaf0c741f4b" + resolved "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz" integrity sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw== dependencies: "@types/node" "*" @@ -1015,7 +1015,7 @@ "@types/owasp-password-strength-test@1.3.0": version "1.3.0" - resolved "https://registry.yarnpkg.com/@types/owasp-password-strength-test/-/owasp-password-strength-test-1.3.0.tgz#f639e38847eeb0db14bf7b70896cecd4342ac571" + resolved "https://registry.npmjs.org/@types/owasp-password-strength-test/-/owasp-password-strength-test-1.3.0.tgz" integrity sha512-eKYl6svyRua5OVUFm+AXSxdBrKo7snzrCcFv0KoqKNvNgB3fJzRq3s/xphf+jNTllqYxgsx1uWLeAcL4MjLWQQ== "@types/prettier@^2.1.5": @@ -1048,7 +1048,7 @@ "@types/stoppable@1.1.1": version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/stoppable/-/stoppable-1.1.1.tgz#a6f1f280e29f8f3c743277534425e0a75041d2f9" + resolved "https://registry.npmjs.org/@types/stoppable/-/stoppable-1.1.1.tgz" integrity sha512-b8N+fCADRIYYrGZOcmOR8ZNBOqhktWTB/bMUl5LvGtT201QKJZOOH5UsFyI3qtteM6ZAJbJqZoBcLqqxKIwjhw== dependencies: "@types/node" "*" @@ -1063,14 +1063,14 @@ "@types/supertest@2.0.11": version "2.0.11" - resolved "https://registry.yarnpkg.com/@types/supertest/-/supertest-2.0.11.tgz#2e70f69f220bc77b4f660d72c2e1a4231f44a77d" + resolved "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.11.tgz" integrity sha512-uci4Esokrw9qGb9bvhhSVEjd6rkny/dk5PK/Qz4yxKiyppEI+dOPlNrZBahE3i+PoKFYyDxChVXZ/ysS/nrm1Q== dependencies: "@types/superagent" "*" "@types/uuid@8.3.4": version "8.3.4" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" + resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== "@types/yargs-parser@*": @@ -1087,7 +1087,7 @@ "@typescript-eslint/eslint-plugin@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.1.tgz#e5a86d7e1f9dc0b3df1e6d94feaf20dd838d066c" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.9.1.tgz" integrity sha512-Xv9tkFlyD4MQGpJgTo6wqDqGvHIRmRgah/2Sjz1PUnJTawjHWIwBivUE9x0QtU2WVii9baYgavo/bHjrZJkqTw== dependencies: "@typescript-eslint/experimental-utils" "5.9.1" @@ -1102,7 +1102,7 @@ "@typescript-eslint/experimental-utils@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.1.tgz#8c407c4dd5ffe522329df6e4c9c2b52206d5f7f1" + resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.9.1.tgz" integrity sha512-cb1Njyss0mLL9kLXgS/eEY53SZQ9sT519wpX3i+U457l2UXRDuo87hgKfgRazmu9/tQb0x2sr3Y0yrU+Zz0y+w== dependencies: "@types/json-schema" "^7.0.9" @@ -1114,7 +1114,7 @@ "@typescript-eslint/parser@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.9.1.tgz#b114011010a87e17b3265ca715e16c76a9834cef" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.9.1.tgz" integrity sha512-PLYO0AmwD6s6n0ZQB5kqPgfvh73p0+VqopQQLuNfi7Lm0EpfKyDalchpVwkE+81k5HeiRrTV/9w1aNHzjD7C4g== dependencies: "@typescript-eslint/scope-manager" "5.9.1" @@ -1124,7 +1124,7 @@ "@typescript-eslint/scope-manager@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.9.1.tgz#6c27be89f1a9409f284d95dfa08ee3400166fe69" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.9.1.tgz" integrity sha512-8BwvWkho3B/UOtzRyW07ffJXPaLSUKFBjpq8aqsRvu6HdEuzCY57+ffT7QoV4QXJXWSU1+7g3wE4AlgImmQ9pQ== dependencies: "@typescript-eslint/types" "5.9.1" @@ -1132,7 +1132,7 @@ "@typescript-eslint/type-utils@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.9.1.tgz#c6832ffe655b9b1fec642d36db1a262d721193de" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.9.1.tgz" integrity sha512-tRSpdBnPRssjlUh35rE9ug5HrUvaB9ntREy7gPXXKwmIx61TNN7+l5YKgi1hMKxo5NvqZCfYhA5FvyuJG6X6vg== dependencies: "@typescript-eslint/experimental-utils" "5.9.1" @@ -1141,12 +1141,12 @@ "@typescript-eslint/types@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.9.1.tgz#1bef8f238a2fb32ebc6ff6d75020d9f47a1593c6" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.9.1.tgz" integrity sha512-SsWegWudWpkZCwwYcKoDwuAjoZXnM1y2EbEerTHho19Hmm+bQ56QG4L4jrtCu0bI5STaRTvRTZmjprWlTw/5NQ== "@typescript-eslint/typescript-estree@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.1.tgz#d5b996f49476495070d2b8dd354861cf33c005d6" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.9.1.tgz" integrity sha512-gL1sP6A/KG0HwrahVXI9fZyeVTxEYV//6PmcOn1tD0rw8VhUWYeZeuWHwwhnewnvEMcHjhnJLOBhA9rK4vmb8A== dependencies: "@typescript-eslint/types" "5.9.1" @@ -1159,7 +1159,7 @@ "@typescript-eslint/visitor-keys@5.9.1": version "5.9.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.1.tgz#f52206f38128dd4f675cf28070a41596eee985b7" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.9.1.tgz" integrity sha512-Xh37pNz9e9ryW4TVdwiFzmr4hloty8cFj8GTWMXh3Z8swGwyQWeCcNgF0hm6t09iZd6eiZmIf4zHedQVP6TVtg== dependencies: "@typescript-eslint/types" "5.9.1" @@ -1213,12 +1213,12 @@ acorn@^8.2.4, acorn@^8.4.1: acorn@^8.6.0: version "8.6.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.6.0.tgz#e3692ba0eb1a0c83eaa4f37f5fa7368dd7142895" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz" integrity sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw== acorn@^8.7.0: version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" + resolved "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== agent-base@6: @@ -1260,7 +1260,7 @@ ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: ansi-regex@^5.0.0, ansi-regex@^5.0.1, ansi-regex@^6.0.1: version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== ansi-styles@^3.2.1: @@ -1284,7 +1284,7 @@ ansi-styles@^5.0.0: ansi-styles@^6.0.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.1.0.tgz#87313c102b8118abd57371afab34618bf7350ed3" + resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.1.0.tgz" integrity sha512-VbqNsoz55SYGczauuup0MFUyXNQviSpFTj1RQtFzmQLk18qbVSpTFFGMT293rmDaQuKCT6InmbuEyUne4mTuxQ== anymatch@^3.0.3: @@ -1314,7 +1314,7 @@ argparse@^1.0.7: argparse@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== arr-diff@^4.0.0: @@ -1344,7 +1344,7 @@ array-flatten@1.1.1: array-includes@^3.1.4: version "3.1.4" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" + resolved "https://registry.npmjs.org/array-includes/-/array-includes-3.1.4.tgz" integrity sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw== dependencies: call-bind "^1.0.2" @@ -1370,7 +1370,7 @@ array-unique@^0.3.2: array.prototype.flat@^1.2.5: version "1.2.5" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz#07e0975d84bbc7c48cd1879d609e682598d33e13" + resolved "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.5.tgz" integrity sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg== dependencies: call-bind "^1.0.2" @@ -1384,12 +1384,12 @@ arrify@^1.0.1: asap@^2.0.0: version "2.0.6" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" + resolved "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= asn1@^0.2.4, asn1@~0.2.3: version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== dependencies: safer-buffer "~2.1.0" @@ -1446,7 +1446,7 @@ aws4@^1.8.0: babel-jest@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.4.6.tgz#4d024e69e241cdf4f396e453a07100f44f7ce314" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.4.6.tgz" integrity sha512-qZL0JT0HS1L+lOuH+xC2DVASR3nunZi/ozGhpgauJHgmI7f8rudxf6hUjEHympdQ/J64CdKmPkgfJ+A3U6QCrg== dependencies: "@jest/transform" "^27.4.6" @@ -1460,7 +1460,7 @@ babel-jest@^27.4.6: babel-plugin-istanbul@^6.1.1: version "6.1.1" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== dependencies: "@babel/helper-plugin-utils" "^7.0.0" @@ -1471,7 +1471,7 @@ babel-plugin-istanbul@^6.1.1: babel-plugin-jest-hoist@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz#d7831fc0f93573788d80dee7e682482da4c730d6" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.4.0.tgz" integrity sha512-Jcu7qS4OX5kTWBc45Hz7BMmgXuJqRnhatqpUhnzGC3OBYpOmf2tv6jFNwZpwM7wU7MUuv2r9IPS/ZlYOuburVw== dependencies: "@babel/template" "^7.3.3" @@ -1499,7 +1499,7 @@ babel-preset-current-node-syntax@^1.0.0: babel-preset-jest@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz#70d0e676a282ccb200fbabd7f415db5fdf393bca" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.4.0.tgz" integrity sha512-NK4jGYpnBvNxcGo7/ZpZJr51jCGT+3bwwpVIDY2oNfTxJJldRtB4VAcYdgp1loDE50ODuTu+yBjpMAswv5tlpg== dependencies: babel-plugin-jest-hoist "^27.4.0" @@ -1525,14 +1525,14 @@ base@^0.11.1: bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= dependencies: tweetnacl "^0.14.3" bcryptjs@^2.4.3: version "2.4.3" - resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" + resolved "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= bintrees@1.0.1: @@ -1615,7 +1615,7 @@ browserslist@^4.16.6: browserslist@^4.17.5: version "4.19.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.19.1.tgz#4ac0435b35ab655896c31d53018b6dd5e9e4c9a3" + resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz" integrity sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A== dependencies: caniuse-lite "^1.0.30001286" @@ -1721,7 +1721,7 @@ caniuse-lite@^1.0.30001248: caniuse-lite@^1.0.30001286: version "1.0.30001296" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001296.tgz#d99f0f3bee66544800b93d261c4be55a35f1cec8" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001296.tgz" integrity sha512-WfrtPEoNSoeATDlf4y3QvkwiELl9GyPLISV5GejTbbQRtQx4LhsXmc9IQ6XCL2d7UxCyEzToEZNMeqR79OUw8Q== caseless@~0.12.0: @@ -1753,7 +1753,7 @@ char-regex@^1.0.2: ci-info@^3.1.1, ci-info@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + resolved "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz" integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== cjs-module-lexer@^1.0.0: @@ -1785,7 +1785,7 @@ cli-cursor@^3.1.0: cli-truncate@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: slice-ansi "^3.0.0" @@ -1793,7 +1793,7 @@ cli-truncate@^2.1.0: cli-truncate@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-3.1.0.tgz#3f23ab12535e3d73e839bb43e73c9de487db1389" + resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz" integrity sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA== dependencies: slice-ansi "^5.0.0" @@ -1866,7 +1866,7 @@ colorette@1.2.1: colorette@2.0.16, colorette@^2.0.16: version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" + resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz" integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== colorette@^1.2.2: @@ -1903,7 +1903,7 @@ commander@^7.1.0: commander@^8.3.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== component-emitter@^1.2.1, component-emitter@^1.3.0: @@ -1953,7 +1953,7 @@ confusing-browser-globals@^1.0.10: connect-session-knex@^2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/connect-session-knex/-/connect-session-knex-2.1.0.tgz#7f1e32594d37f7a1ad24e6dee97d8f2d9799695e" + resolved "https://registry.npmjs.org/connect-session-knex/-/connect-session-knex-2.1.0.tgz" integrity sha512-6xHoDajVWxwByaq6UjfU+qGE89Nurajek4JsxeajtXscr8xznzbsIp+Q/3FKh3/2smfgoSeY+93+Gn+3ZXQVDQ== dependencies: bluebird "^3.7.2" @@ -2018,7 +2018,7 @@ cookiejar@^2.1.2: cookiejar@^2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.3.tgz#fc7a6216e408e74414b90230050842dacda75acc" + resolved "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz" integrity sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ== cookies@0.8.0: @@ -2036,7 +2036,7 @@ copy-descriptor@^0.1.0: copyfiles@2.4.1: version "2.4.1" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5" + resolved "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz" integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg== dependencies: glob "^7.0.5" @@ -2067,7 +2067,7 @@ cors@^2.8.5: coveralls@3.1.1: version "3.1.1" - resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.1.1.tgz#f5d4431d8b5ae69c5079c8f8ca00d64ac77cf081" + resolved "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz" integrity sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww== dependencies: js-yaml "^3.13.1" @@ -2078,7 +2078,7 @@ coveralls@3.1.1: cpu-features@0.0.2: version "0.0.2" - resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.2.tgz#9f636156f1155fd04bdbaa028bb3c2fbef3cea7a" + resolved "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz" integrity sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA== dependencies: nan "^2.14.1" @@ -2152,7 +2152,7 @@ data-urls@^2.0.0: date-fns@^2.25.0: version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.25.0.tgz" integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== date-format@^2.1.0: @@ -2189,7 +2189,7 @@ db-migrate-shared@1.2.0, db-migrate-shared@^1.2.0: db-migrate@0.11.13: version "0.11.13" - resolved "https://registry.yarnpkg.com/db-migrate/-/db-migrate-0.11.13.tgz#b9e5b242c62e82ef8ed2b29e118698813e953d67" + resolved "https://registry.npmjs.org/db-migrate/-/db-migrate-0.11.13.tgz" integrity sha512-OE/bbDo/mQvLmZrui/2jNAiAECJROSURCOU5xs6qKr3FvtUE2O6b0xBUI6WyAAKdili3LJQHFlpqugiYCGTSBA== dependencies: balanced-match "^1.0.0" @@ -2245,7 +2245,7 @@ debug@^3.2.7: debug@^4.3.3: version "4.3.3" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== dependencies: ms "2.1.2" @@ -2329,7 +2329,7 @@ define-property@^2.0.2: del-cli@4.0.1: version "4.0.1" - resolved "https://registry.yarnpkg.com/del-cli/-/del-cli-4.0.1.tgz#2303ccaa45708ee8c6211568344cf87336abf30a" + resolved "https://registry.npmjs.org/del-cli/-/del-cli-4.0.1.tgz" integrity sha512-KtR/6cBfZkGDAP2NA7z+bP4p1OMob3wjN9mq13+SWvExx6jT9gFWfLgXEeX8J2B47OKeNCq9yTONmtryQ+m+6g== dependencies: del "^6.0.0" @@ -2381,7 +2381,7 @@ detect-newline@^3.0.0: dezalgo@1.0.3: version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" + resolved "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz" integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= dependencies: asap "^2.0.0" @@ -2402,7 +2402,7 @@ diff-sequences@^27.0.6: diff-sequences@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.4.0.tgz#d783920ad8d06ec718a060d00196dfef25b132a5" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz" integrity sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww== diff@^4.0.1: @@ -2468,7 +2468,7 @@ electron-to-chromium@^1.3.793: electron-to-chromium@^1.4.17: version "1.4.34" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.34.tgz#7d87dc0e95c2c65cbd0687ae23146a662506d1ef" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.34.tgz" integrity sha512-B7g6Y9No9XMYk1VNrQ8KAmSEo1Iltrz/5EjOGxl1DffQAb3z/XbpHRCfYKwV8D+CPXm4Q7Xg1sceSt9osNwRIA== emittery@^0.8.1: @@ -2483,7 +2483,7 @@ emoji-regex@^8.0.0: emoji-regex@^9.2.2: version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== encodeurl@~1.0.2: @@ -2515,7 +2515,7 @@ errorhandler@^1.5.1: es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" + resolved "https://registry.npmjs.org/es-abstract/-/es-abstract-1.19.1.tgz" integrity sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w== dependencies: call-bind "^1.0.2" @@ -2623,7 +2623,7 @@ escodegen@^2.0.0: eslint-config-airbnb-base@15.0.0, eslint-config-airbnb-base@^15.0.0: version "15.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz#6b09add90ac79c2f8d723a2580e07f3925afd236" + resolved "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz" integrity sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig== dependencies: confusing-browser-globals "^1.0.10" @@ -2633,19 +2633,19 @@ eslint-config-airbnb-base@15.0.0, eslint-config-airbnb-base@^15.0.0: eslint-config-airbnb-typescript@16.0.0: version "16.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.0.0.tgz#75007e27d5a7fb75530721f48197817c1d2ad4d1" + resolved "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-16.0.0.tgz" integrity sha512-qDOyD0YYZo5Us1YvOnWig2Ly/+IlQKmMZpnqKnJgVtHdK8SkjaSyVBHKbD41dEaQxk8vRVGBC94PuR2ceSwbLQ== dependencies: eslint-config-airbnb-base "^15.0.0" eslint-config-prettier@8.3.0: version "8.3.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz#f7471b20b6fe8a9a9254cc684454202886a2dd7a" + resolved "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz" integrity sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew== eslint-import-resolver-node@^0.3.6: version "0.3.6" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + resolved "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz" integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== dependencies: debug "^3.2.7" @@ -2653,7 +2653,7 @@ eslint-import-resolver-node@^0.3.6: eslint-module-utils@^2.7.2: version "2.7.2" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz#1d0aa455dcf41052339b63cada8ab5fd57577129" + resolved "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.2.tgz" integrity sha512-zquepFnWCY2ISMFwD/DqzaM++H+7PDzOpUvotJWm/y1BAFt5R4oeULgdrTejKqLkz7MA/tgstsUMNYc7wNdTrg== dependencies: debug "^3.2.7" @@ -2661,7 +2661,7 @@ eslint-module-utils@^2.7.2: eslint-plugin-import@2.25.4: version "2.25.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" + resolved "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz" integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== dependencies: array-includes "^3.1.4" @@ -2680,7 +2680,7 @@ eslint-plugin-import@2.25.4: eslint-plugin-prettier@4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz#8b99d1e4b8b24a762472b4567992023619cb98e0" + resolved "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz" integrity sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ== dependencies: prettier-linter-helpers "^1.0.0" @@ -2695,7 +2695,7 @@ eslint-scope@^5.1.1: eslint-scope@^7.1.0: version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" + resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz" integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== dependencies: esrecurse "^4.3.0" @@ -2715,17 +2715,17 @@ eslint-visitor-keys@^2.0.0: eslint-visitor-keys@^3.0.0: version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz#e32e99c6cdc2eb063f204eda5db67bfe58bb4186" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.0.0.tgz" integrity sha512-mJOZa35trBTb3IyRmo8xmKBZlxf+N7OnUl4+ZhJHs/r+0770Wh/LEACE2pqMGMe27G/4y8P2bYGk4J70IC5k1Q== eslint-visitor-keys@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2" + resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz" integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA== eslint@8.6.0: version "8.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.6.0.tgz#4318c6a31c5584838c1a2e940c478190f58d558e" + resolved "https://registry.npmjs.org/eslint/-/eslint-8.6.0.tgz" integrity sha512-UvxdOJ7mXFlw7iuHZA4jmzPaUqIw54mZrv+XPYKNbKdLR0et4rf60lIZUU9kiNtnzzMzGWxMV+tQ7uG7JG8DPw== dependencies: "@eslint/eslintrc" "^1.0.5" @@ -2774,7 +2774,7 @@ esm@^3.2.25: espree@^9.2.0: version "9.2.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.2.0.tgz#c50814e01611c2d0f8bd4daa83c369eabba80dbc" + resolved "https://registry.npmjs.org/espree/-/espree-9.2.0.tgz" integrity sha512-oP3utRkynpZWF/F2x/HZJ+AGtnIclaR7z1pYPxy7NYM2fSO6LgK/Rkny8anRSPK/VwEA1eqm2squui0T7ZMOBg== dependencies: acorn "^8.6.0" @@ -2783,7 +2783,7 @@ espree@^9.2.0: espree@^9.3.0: version "9.3.0" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.0.tgz#c1240d79183b72aaee6ccfa5a90bc9111df085a8" + resolved "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz" integrity sha512-d/5nCsb0JcqsSEeQzFZ8DH1RmxPcglRWh24EFTlUEmCKoehXGdpsx0RkHDubqUI8LSAIKMQp4r9SzQ3n+sm4HQ== dependencies: acorn "^8.7.0" @@ -2892,7 +2892,7 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: expect@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/expect/-/expect-27.4.6.tgz#f335e128b0335b6ceb4fcab67ece7cbd14c942e6" + resolved "https://registry.npmjs.org/expect/-/expect-27.4.6.tgz" integrity sha512-1M/0kAALIaj5LaG66sFJTbRsWTADnylly82cu4bspI0nl+pgP4E6Bh/aqdHlTUjul06K7xQnnrAoqfxVU0+/ag== dependencies: "@jest/types" "^27.4.2" @@ -3008,7 +3008,7 @@ eyes@0.1.x: faker@5.5.3: version "5.5.3" - resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e" + resolved "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz" integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g== fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: @@ -3034,7 +3034,7 @@ fast-glob@^3.1.1: fast-json-patch@^3.1.0: version "3.1.0" - resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.0.tgz#ec8cd9b9c4c564250ec8b9140ef7a55f70acaee6" + resolved "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.0.tgz" integrity sha512-IhpytlsVTRndz0hU5t0/MGzS/etxLlfrpG5V5M9mVbuj9TrJLWaMfsox9REM5rkuGX0T+5qjpe8XA1o0gZ42nA== fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: @@ -3054,7 +3054,7 @@ fast-safe-stringify@^2.0.7: fast-safe-stringify@^2.1.1: version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" + resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fastq@^1.6.0: @@ -3073,7 +3073,7 @@ fb-watchman@^2.0.0: fetch-mock@9.11.0: version "9.11.0" - resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-9.11.0.tgz#371c6fb7d45584d2ae4a18ee6824e7ad4b637a3f" + resolved "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz" integrity sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q== dependencies: "@babel/core" "^7.0.0" @@ -3235,7 +3235,7 @@ form-data@^3.0.0: form-data@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz" integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== dependencies: asynckit "^0.4.0" @@ -3258,7 +3258,7 @@ formidable@^1.2.2: formidable@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-2.0.1.tgz#4310bc7965d185536f9565184dee74fbb75557ff" + resolved "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz" integrity sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ== dependencies: dezalgo "1.0.3" @@ -3348,7 +3348,7 @@ get-stream@^6.0.0: get-symbol-description@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz" integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== dependencies: call-bind "^1.0.2" @@ -3380,7 +3380,7 @@ glob-parent@^5.1.2: glob-parent@^6.0.1: version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz" integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== dependencies: is-glob "^4.0.3" @@ -3436,7 +3436,7 @@ globals@^13.6.0, globals@^13.9.0: globby@^11.0.1, globby@^11.0.4: version "11.0.4" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" + resolved "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== dependencies: array-union "^2.1.0" @@ -3544,12 +3544,12 @@ has@^1.0.3: helmet@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/helmet/-/helmet-5.0.0.tgz#3084c827f51b3c7680cfe99d5d8f29ec39a74fb9" + resolved "https://registry.npmjs.org/helmet/-/helmet-5.0.0.tgz" integrity sha512-wCuTCJZnEKXagvjcZiAnXkzS4lh8mTJ/JhhC5XjH5vPvSzzX/8Y88u6mfE3F66itB6UIA7uZEekXJsbdFTOiPw== hexoid@1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" + resolved "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== homedir-polyfill@^1.0.1: @@ -3633,7 +3633,7 @@ human-signals@^2.1.0: husky@7.0.4: version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" + resolved "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz" integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== iconv-lite@0.4.24: @@ -3650,7 +3650,7 @@ ignore@^4.0.6: ignore@^5.1.4, ignore@^5.1.8: version "5.1.8" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + resolved "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz" integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== import-fresh@^3.0.0, import-fresh@^3.2.1: @@ -3797,7 +3797,7 @@ is-core-module@^2.2.0: is-core-module@^2.8.0: version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" + resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.0.tgz" integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== dependencies: has "^1.0.3" @@ -3865,7 +3865,7 @@ is-fullwidth-code-point@^3.0.0: is-fullwidth-code-point@^4.0.0: version "4.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz#fae3167c729e7463f8461ce512b080a49268aa88" + resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz" integrity sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ== is-generator-fn@^2.0.0: @@ -3882,7 +3882,7 @@ is-glob@^4.0.0, is-glob@^4.0.1: is-glob@^4.0.3: version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" @@ -3945,7 +3945,7 @@ is-potential-custom-element-name@^1.0.1: is-primitive@^3.0.1: version "3.0.1" - resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-3.0.1.tgz#98c4db1abff185485a657fc2905052b940524d05" + resolved "https://registry.npmjs.org/is-primitive/-/is-primitive-3.0.1.tgz" integrity sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w== is-promise@^2.2.2: @@ -3970,7 +3970,7 @@ is-relative@^1.0.0: is-shared-array-buffer@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" + resolved "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz" integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== is-stream@^2.0.0: @@ -4011,7 +4011,7 @@ is-unc-path@^1.0.0: is-weakref@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.1.tgz#842dba4ec17fa9ac9850df2d6efbc1737274f2a2" + resolved "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.1.tgz" integrity sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ== dependencies: call-bind "^1.0.0" @@ -4060,12 +4060,12 @@ istanbul-lib-coverage@^3.0.0: istanbul-lib-coverage@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz" integrity sha512-czwUz525rkOFDJxfKK6mYfIs9zBKILyrZQxjz3ABhjQXhbhFsSbo1HW/BFcsDnfJYJWA6thRR5/TUY2qs5W99Q== dependencies: "@babel/core" "^7.12.3" @@ -4094,7 +4094,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-reports@^3.1.3: version "3.1.3" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.3.tgz#4bcae3103b94518117930d51283690960b50d3c2" + resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.3.tgz" integrity sha512-x9LtDVtfm/t1GFiLl3NffC7hz+I1ragvgX1P/Lg1NlIagifZDKUkuuaAxH/qpwj2IuEfD8G2Bs/UKp+sZ/pKkg== dependencies: html-escaper "^2.0.0" @@ -4102,7 +4102,7 @@ istanbul-reports@^3.1.3: jest-changed-files@^27.4.2: version "27.4.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.4.2.tgz#da2547ea47c6e6a5f6ed336151bd2075736eb4a5" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.4.2.tgz" integrity sha512-/9x8MjekuzUQoPjDHbBiXbNEBauhrPU2ct7m8TfCg69ywt1y/N+yYwGh3gCpnqUS3klYWDU/lSNgv+JhoD2k1A== dependencies: "@jest/types" "^27.4.2" @@ -4111,7 +4111,7 @@ jest-changed-files@^27.4.2: jest-circus@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.4.6.tgz#d3af34c0eb742a967b1919fbb351430727bcea6c" + resolved "https://registry.npmjs.org/jest-circus/-/jest-circus-27.4.6.tgz" integrity sha512-UA7AI5HZrW4wRM72Ro80uRR2Fg+7nR0GESbSI/2M+ambbzVuA63mn5T1p3Z/wlhntzGpIG1xx78GP2YIkf6PhQ== dependencies: "@jest/environment" "^27.4.6" @@ -4136,7 +4136,7 @@ jest-circus@^27.4.6: jest-cli@^27.4.7: version "27.4.7" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.4.7.tgz#d00e759e55d77b3bcfea0715f527c394ca314e5a" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.4.7.tgz" integrity sha512-zREYhvjjqe1KsGV15mdnxjThKNDgza1fhDT+iUsXWLCq3sxe9w5xnvyctcYVT5PcdLSjv7Y5dCwTS3FCF1tiuw== dependencies: "@jest/core" "^27.4.7" @@ -4154,7 +4154,7 @@ jest-cli@^27.4.7: jest-config@^27.4.7: version "27.4.7" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.4.7.tgz#4f084b2acbd172c8b43aa4cdffe75d89378d3972" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.4.7.tgz" integrity sha512-xz/o/KJJEedHMrIY9v2ParIoYSrSVY6IVeE4z5Z3i101GoA5XgfbJz+1C8EYPsv7u7f39dS8F9v46BHDhn0vlw== dependencies: "@babel/core" "^7.8.0" @@ -4192,7 +4192,7 @@ jest-diff@^27.0.0: jest-diff@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.4.6.tgz#93815774d2012a2cbb6cf23f84d48c7a2618f98d" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.6.tgz" integrity sha512-zjaB0sh0Lb13VyPsd92V7HkqF6yKRH9vm33rwBt7rPYrpQvS1nCvlIy2pICbKta+ZjWngYLNn4cCK4nyZkjS/w== dependencies: chalk "^4.0.0" @@ -4202,14 +4202,14 @@ jest-diff@^27.4.6: jest-docblock@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.4.0.tgz#06c78035ca93cbbb84faf8fce64deae79a59f69f" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.4.0.tgz" integrity sha512-7TBazUdCKGV7svZ+gh7C8esAnweJoG+SvcF6Cjqj4l17zA2q1cMwx2JObSioubk317H+cjcHgP+7fTs60paulg== dependencies: detect-newline "^3.0.0" jest-each@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.4.6.tgz#e7e8561be61d8cc6dbf04296688747ab186c40ff" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.4.6.tgz" integrity sha512-n6QDq8y2Hsmn22tRkgAk+z6MCX7MeVlAzxmZDshfS2jLcaBlyhpF3tZSJLR+kXmh23GEvS0ojMR8i6ZeRvpQcA== dependencies: "@jest/types" "^27.4.2" @@ -4220,7 +4220,7 @@ jest-each@^27.4.6: jest-environment-jsdom@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz#c23a394eb445b33621dfae9c09e4c8021dea7b36" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.4.6.tgz" integrity sha512-o3dx5p/kHPbUlRvSNjypEcEtgs6LmvESMzgRFQE6c+Prwl2JLA4RZ7qAnxc5VM8kutsGRTB15jXeeSbJsKN9iA== dependencies: "@jest/environment" "^27.4.6" @@ -4233,7 +4233,7 @@ jest-environment-jsdom@^27.4.6: jest-environment-node@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.4.6.tgz#ee8cd4ef458a0ef09d087c8cd52ca5856df90242" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.4.6.tgz" integrity sha512-yfHlZ9m+kzTKZV0hVfhVu6GuDxKAYeFHrfulmy7Jxwsq4V7+ZK7f+c0XP/tbVDMQW7E4neG2u147hFkuVz0MlQ== dependencies: "@jest/environment" "^27.4.6" @@ -4245,7 +4245,7 @@ jest-environment-node@^27.4.6: jest-fetch-mock@3.0.3: version "3.0.3" - resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz#31749c456ae27b8919d69824f1c2bd85fe0a1f3b" + resolved "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz" integrity sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw== dependencies: cross-fetch "^3.0.4" @@ -4258,12 +4258,12 @@ jest-get-type@^27.0.6: jest-get-type@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.4.0.tgz#7503d2663fffa431638337b3998d39c5e928e9b5" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz" integrity sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ== jest-haste-map@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.4.6.tgz#c60b5233a34ca0520f325b7e2cc0a0140ad0862a" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.4.6.tgz" integrity sha512-0tNpgxg7BKurZeFkIOvGCkbmOHbLFf4LUQOxrQSMjvrQaQe3l6E8x6jYC1NuWkGo5WDdbr8FEzUxV2+LWNawKQ== dependencies: "@jest/types" "^27.4.2" @@ -4283,7 +4283,7 @@ jest-haste-map@^27.4.6: jest-jasmine2@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz#109e8bc036cb455950ae28a018f983f2abe50127" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.4.6.tgz" integrity sha512-uAGNXF644I/whzhsf7/qf74gqy9OuhvJ0XYp8SDecX2ooGeaPnmJMjXjKt0mqh1Rl5dtRGxJgNrHlBQIBfS5Nw== dependencies: "@jest/environment" "^27.4.6" @@ -4306,7 +4306,7 @@ jest-jasmine2@^27.4.6: jest-leak-detector@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz#ed9bc3ce514b4c582637088d9faf58a33bd59bf4" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.4.6.tgz" integrity sha512-kkaGixDf9R7CjHm2pOzfTxZTQQQ2gHTIWKY/JZSiYTc90bZp8kSZnUMS3uLAfwTZwc0tcMRoEX74e14LG1WapA== dependencies: jest-get-type "^27.4.0" @@ -4314,7 +4314,7 @@ jest-leak-detector@^27.4.6: jest-matcher-utils@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz#53ca7f7b58170638590e946f5363b988775509b8" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.4.6.tgz" integrity sha512-XD4PKT3Wn1LQnRAq7ZsTI0VRuEc9OrCPFiO1XL7bftTGmfNF0DcEwMHRgqiu7NGf8ZoZDREpGrCniDkjt79WbA== dependencies: chalk "^4.0.0" @@ -4324,7 +4324,7 @@ jest-matcher-utils@^27.4.6: jest-message-util@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.4.6.tgz#9fdde41a33820ded3127465e1a5896061524da31" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz" integrity sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA== dependencies: "@babel/code-frame" "^7.12.13" @@ -4339,7 +4339,7 @@ jest-message-util@^27.4.6: jest-mock@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.4.6.tgz#77d1ba87fbd33ccb8ef1f061697e7341b7635195" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz" integrity sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw== dependencies: "@jest/types" "^27.4.2" @@ -4352,12 +4352,12 @@ jest-pnp-resolver@^1.2.2: jest-regex-util@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.4.0.tgz#e4c45b52653128843d07ad94aec34393ea14fbca" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz" integrity sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg== jest-resolve-dependencies@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz#fc50ee56a67d2c2183063f6a500cc4042b5e2327" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.4.6.tgz" integrity sha512-W85uJZcFXEVZ7+MZqIPCscdjuctruNGXUZ3OHSXOfXR9ITgbUKeHj+uGcies+0SsvI5GtUfTw4dY7u9qjTvQOw== dependencies: "@jest/types" "^27.4.2" @@ -4366,7 +4366,7 @@ jest-resolve-dependencies@^27.4.6: jest-resolve@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.4.6.tgz#2ec3110655e86d5bfcfa992e404e22f96b0b5977" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.4.6.tgz" integrity sha512-SFfITVApqtirbITKFAO7jOVN45UgFzcRdQanOFzjnbd+CACDoyeX7206JyU92l4cRr73+Qy/TlW51+4vHGt+zw== dependencies: "@jest/types" "^27.4.2" @@ -4382,7 +4382,7 @@ jest-resolve@^27.4.6: jest-runner@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.4.6.tgz#1d390d276ec417e9b4d0d081783584cbc3e24773" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.4.6.tgz" integrity sha512-IDeFt2SG4DzqalYBZRgbbPmpwV3X0DcntjezPBERvnhwKGWTW7C5pbbA5lVkmvgteeNfdd/23gwqv3aiilpYPg== dependencies: "@jest/console" "^27.4.6" @@ -4410,7 +4410,7 @@ jest-runner@^27.4.6: jest-runtime@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.4.6.tgz#83ae923818e3ea04463b22f3597f017bb5a1cffa" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.4.6.tgz" integrity sha512-eXYeoR/MbIpVDrjqy5d6cGCFOYBFFDeKaNWqTp0h6E74dK0zLHzASQXJpl5a2/40euBmKnprNLJ0Kh0LCndnWQ== dependencies: "@jest/environment" "^27.4.6" @@ -4438,7 +4438,7 @@ jest-runtime@^27.4.6: jest-serializer@^27.4.0: version "27.4.0" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.4.0.tgz#34866586e1cae2388b7d12ffa2c7819edef5958a" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.4.0.tgz" integrity sha512-RDhpcn5f1JYTX2pvJAGDcnsNTnsV9bjYPU8xcV+xPwOXnUPOQwf4ZEuiU6G9H1UztH+OapMgu/ckEVwO87PwnQ== dependencies: "@types/node" "*" @@ -4446,7 +4446,7 @@ jest-serializer@^27.4.0: jest-snapshot@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.4.6.tgz#e2a3b4fff8bdce3033f2373b2e525d8b6871f616" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.4.6.tgz" integrity sha512-fafUCDLQfzuNP9IRcEqaFAMzEe7u5BF7mude51wyWv7VRex60WznZIC7DfKTgSIlJa8aFzYmXclmN328aqSDmQ== dependencies: "@babel/core" "^7.7.2" @@ -4486,7 +4486,7 @@ jest-util@^27.0.0: jest-util@^27.4.2: version "27.4.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.4.2.tgz" integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA== dependencies: "@jest/types" "^27.4.2" @@ -4498,7 +4498,7 @@ jest-util@^27.4.2: jest-validate@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.4.6.tgz#efc000acc4697b6cf4fa68c7f3f324c92d0c4f1f" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.4.6.tgz" integrity sha512-872mEmCPVlBqbA5dToC57vA3yJaMRfIdpCoD3cyHWJOMx+SJwLNw0I71EkWs41oza/Er9Zno9XuTkRYCPDUJXQ== dependencies: "@jest/types" "^27.4.2" @@ -4510,7 +4510,7 @@ jest-validate@^27.4.6: jest-watcher@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.4.6.tgz#673679ebeffdd3f94338c24f399b85efc932272d" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.4.6.tgz" integrity sha512-yKQ20OMBiCDigbD0quhQKLkBO+ObGN79MO4nT7YaCuQ5SM+dkBNWE8cZX0FjU6czwMvWw6StWbe+Wv4jJPJ+fw== dependencies: "@jest/test-result" "^27.4.6" @@ -4523,7 +4523,7 @@ jest-watcher@^27.4.6: jest-worker@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.4.6.tgz#5d2d93db419566cb680752ca0792780e71b3273e" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.4.6.tgz" integrity sha512-gHWJF/6Xi5CTG5QCvROr6GcmpIqNYpDJyc8A1h/DyXqH1tD6SnRCM0d3U5msV31D2LB/U+E0M+W4oyvKV44oNw== dependencies: "@types/node" "*" @@ -4532,7 +4532,7 @@ jest-worker@^27.4.6: jest@27.4.7: version "27.4.7" - resolved "https://registry.yarnpkg.com/jest/-/jest-27.4.7.tgz#87f74b9026a1592f2da05b4d258e57505f28eca4" + resolved "https://registry.npmjs.org/jest/-/jest-27.4.7.tgz" integrity sha512-8heYvsx7nV/m8m24Vk26Y87g73Ba6ueUd0MWed/NXMhSZIm62U/llVbS0PJe1SHunbyXjJ/BqG1z9bFjGUIvTg== dependencies: "@jest/core" "^27.4.7" @@ -4565,7 +4565,7 @@ js-yaml@^3.13.1: js-yaml@^4.1.0: version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: argparse "^2.0.1" @@ -4647,7 +4647,7 @@ json5@2.x, json5@^2.1.2: json5@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + resolved "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz" integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== dependencies: minimist "^1.2.0" @@ -4707,7 +4707,7 @@ kleur@^3.0.3: knex@0.95.15: version "0.95.15" - resolved "https://registry.yarnpkg.com/knex/-/knex-0.95.15.tgz#39d7e7110a6e2ad7de5d673d2dea94143015e0e7" + resolved "https://registry.npmjs.org/knex/-/knex-0.95.15.tgz" integrity sha512-Loq6WgHaWlmL2bfZGWPsy4l8xw4pOE+tmLGkPG0auBppxpI0UcK+GYCycJcqz9W54f2LiGewkCVLBm3Wq4ur/w== dependencies: colorette "2.0.16" @@ -4784,7 +4784,7 @@ liftoff@3.1.0: lilconfig@2.0.4: version "2.0.4" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz" integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== lines-and-columns@^1.1.6: @@ -4794,7 +4794,7 @@ lines-and-columns@^1.1.6: lint-staged@12.1.7: version "12.1.7" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-12.1.7.tgz#fe9137992ac18a456422bb8484dd30be0140629f" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-12.1.7.tgz" integrity sha512-bltv/ejiLWtowExpjU+s5z8j1Byjg9AlmaAjMmqNbIicY69u6sYIwXGg0dCn0TlkrrY2CphtHIXAkbZ+1VoWQQ== dependencies: cli-truncate "^3.1.0" @@ -4813,7 +4813,7 @@ lint-staged@12.1.7: listr2@^3.13.5: version "3.13.5" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.13.5.tgz#105a813f2eb2329c4aae27373a281d610ee4985f" + resolved "https://registry.npmjs.org/listr2/-/listr2-3.13.5.tgz" integrity sha512-3n8heFQDSk+NcwBn3CgxEibZGaRzx+pC64n3YjpMD1qguV4nWus3Al+Oo3KooqFKTQEJ1v7MmnbnyyNspgx3NA== dependencies: cli-truncate "^2.1.0" @@ -4859,7 +4859,7 @@ lodash.isequal@^4.5.0: lodash.memoize@4.x: version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + resolved "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= lodash.merge@^4.6.2: @@ -5085,7 +5085,7 @@ mime@^2.4.2, mime@^2.4.6: mime@^2.5.0: version "2.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + resolved "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== mimic-fn@^2.1.0: @@ -5195,7 +5195,7 @@ mute-stream@~0.0.4: nan@^2.14.1, nan@^2.15.0: version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" + resolved "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz" integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== nanomatch@^1.2.9: @@ -5246,9 +5246,9 @@ node-fetch@2.6.1, node-fetch@^2.6.1: integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== node-forge@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.0.0.tgz#a025e3beeeb90d9cee37dae34d25b968ec3e6f15" - integrity sha512-ShkiiAlzSsgH1IwGlA0jybk9vQTIOLyJ9nBd0JTuP+nzADJFLY0NoDijM2zvD/JaezooGu3G2p2FNxOAK6459g== + version "1.2.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.2.1.tgz#82794919071ef2eb5c509293325cec8afd0fd53c" + integrity sha512-Fcvtbb+zBcZXbTTVwqGA5W+MKBj56UjVRevvchv5XrcyXbmNdesfZL37nlcWOfpgHhgmxApw3tQbTr4CqNmX4w== node-fs@~0.1.5: version "0.1.7" @@ -5267,7 +5267,7 @@ node-releases@^1.1.73: node-releases@^2.0.1: version "2.0.1" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" + resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz" integrity sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA== nodemailer@^6.5.0: @@ -5336,7 +5336,7 @@ object-inspect@^1.11.0, object-inspect@^1.9.0: object-inspect@^1.11.1: version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== object-keys@^1.0.12, object-keys@^1.1.1: @@ -5373,7 +5373,7 @@ object.defaults@^1.1.0: object.entries@^1.1.5: version "1.1.5" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + resolved "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz" integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== dependencies: call-bind "^1.0.2" @@ -5397,7 +5397,7 @@ object.pick@^1.2.0, object.pick@^1.3.0: object.values@^1.1.5: version "1.1.5" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + resolved "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz" integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== dependencies: call-bind "^1.0.2" @@ -5698,7 +5698,7 @@ pgpass@1.x: picocolors@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== picomatch@^2.0.4, picomatch@^2.2.3: @@ -5708,7 +5708,7 @@ picomatch@^2.0.4, picomatch@^2.2.3: pirates@^4.0.4: version "4.0.4" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.4.tgz#07df81e61028e402735cdd49db701e4885b4e6e6" + resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz" integrity sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw== pkg-dir@^4.2.0: @@ -5769,7 +5769,7 @@ prettier-linter-helpers@^1.0.0: prettier@2.5.1: version "2.5.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a" + resolved "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz" integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== pretty-format@^27.0.0, pretty-format@^27.0.6: @@ -5784,7 +5784,7 @@ pretty-format@^27.0.0, pretty-format@^27.0.6: pretty-format@^27.4.6: version "27.4.6" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.4.6.tgz#1b784d2f53c68db31797b2348fa39b49e31846b7" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz" integrity sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g== dependencies: ansi-regex "^5.0.1" @@ -5803,7 +5803,7 @@ progress@^2.0.0: prom-client@^14.0.0: version "14.0.0" - resolved "https://registry.yarnpkg.com/prom-client/-/prom-client-14.0.0.tgz#7af5a0f8f544743f0dee4447c06ac9bb0d052719" + resolved "https://registry.npmjs.org/prom-client/-/prom-client-14.0.0.tgz" integrity sha512-etPa4SMO4j6qTn2uaSZy7+uahGK0kXUZwO7WhoDpTf3yZ837I3jqUDYmG6N0caxuU6cyqrg0xmOxh+yneczvyA== dependencies: tdigest "^0.1.1" @@ -5842,7 +5842,7 @@ proxy-addr@~2.0.5: proxyquire@2.1.3: version "2.1.3" - resolved "https://registry.yarnpkg.com/proxyquire/-/proxyquire-2.1.3.tgz#2049a7eefa10a9a953346a18e54aab2b4268df39" + resolved "https://registry.npmjs.org/proxyquire/-/proxyquire-2.1.3.tgz" integrity sha512-BQWfCqYM+QINd+yawJz23tbBM40VIGXOdDw3X344KcclI/gtBbdWF6SlQ4nK/bYhF9d27KYug9WzljHC6B9Ysg== dependencies: fill-keys "^1.0.2" @@ -5873,12 +5873,12 @@ qs@6.7.0: qs@6.9.3: version "6.9.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.3.tgz#bfadcd296c2d549f1dffa560619132c977f5008e" + resolved "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz" integrity sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw== qs@^6.10.1: version "6.10.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.2.tgz#c1431bea37fc5b24c5bdbafa20f16bdf2a4b9ffe" + resolved "https://registry.npmjs.org/qs/-/qs-6.10.2.tgz" integrity sha512-mSIdjzqznWgfd4pMii7sHtaYF8rx8861hBO80SraY5GT0XQibWZWJSid0avzHGkDIZLImux2S5mXO0Hfct2QCw== dependencies: side-channel "^1.0.4" @@ -6050,7 +6050,7 @@ regex-not@^1.0.0, regex-not@^1.0.2: regexpp@^3.2.0: version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== repeat-element@^1.1.2: @@ -6131,7 +6131,7 @@ resolve-url@^0.2.1: resolve.exports@^1.1.0: version "1.1.0" - resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + resolved "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz" integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== resolve@^1.1.6, resolve@^1.1.7, resolve@^1.11.1, resolve@^1.20.0, resolve@^1.9.0: @@ -6194,7 +6194,7 @@ run-parallel@^1.1.9: rxjs@^7.4.0: version "7.4.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.4.0.tgz#a12a44d7eebf016f5ff2441b87f28c9a51cebc68" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz" integrity sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w== dependencies: tslib "~2.1.0" @@ -6369,7 +6369,7 @@ slice-ansi@^4.0.0: slice-ansi@^5.0.0: version "5.0.0" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-5.0.0.tgz#b73063c57aa96f9cd881654b15294d95d285c42a" + resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz" integrity sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ== dependencies: ansi-styles "^6.0.0" @@ -6418,7 +6418,7 @@ source-map-resolve@^0.5.0: source-map-support@0.5.21: version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== dependencies: buffer-from "^1.0.0" @@ -6505,9 +6505,9 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= ssh2@0.5.4, ssh2@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.4.0.tgz#e32e8343394364c922bad915a5a7fecd67d0f5c5" - integrity sha512-XvXwcXKvS452DyQvCa6Ct+chpucwc/UyxgliYz+rWXJ3jDHdtBb9xgmxJdMmnIn5bpgGAEV3KaEsH98ZGPHqwg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.5.0.tgz#4dc559ba98a1cbb420e8d42998dfe35d0eda92bc" + integrity sha512-iUmRkhH9KGeszQwDW7YyyqjsMTf4z+0o48Cp4xOwlY5LjtbIAvyd3fwnsoUZW/hXmTCRA3yt7S/Jb9uVjErVlA== dependencies: asn1 "^0.2.4" bcrypt-pbkdf "^1.0.2" @@ -6588,7 +6588,7 @@ string-argv@^0.1.1: string-argv@^0.3.1: version "0.3.1" - resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" + resolved "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz" integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg== string-length@^4.0.1: @@ -6610,7 +6610,7 @@ string-width@^4.1.0, string-width@^4.2.0: string-width@^5.0.0: version "5.0.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.0.1.tgz#0d8158335a6cfd8eb95da9b6b262ce314a036ffd" + resolved "https://registry.npmjs.org/string-width/-/string-width-5.0.1.tgz" integrity sha512-5ohWO/M4//8lErlUUtrFy3b11GtNOuMOU0ysKCDXFcfXuuvUXu95akgj/i8ofmaGdN0hCqyl6uu9i8dS/mQp5g== dependencies: emoji-regex "^9.2.2" @@ -6661,14 +6661,14 @@ strip-ansi@^6.0.0: strip-ansi@^6.0.1: version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" strip-ansi@^7.0.1: version "7.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz" integrity sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw== dependencies: ansi-regex "^6.0.1" @@ -6707,7 +6707,7 @@ strip-json-comments@~2.0.1: superagent@6.1.0: version "6.1.0" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-6.1.0.tgz#09f08807bc41108ef164cfb4be293cebd480f4a6" + resolved "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz" integrity sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg== dependencies: component-emitter "^1.3.0" @@ -6770,7 +6770,7 @@ supports-color@^8.0.0: supports-color@^9.2.1: version "9.2.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.2.1.tgz#599dc9d45acf74c6176e0d880bab1d7d718fe891" + resolved "https://registry.npmjs.org/supports-color/-/supports-color-9.2.1.tgz" integrity sha512-Obv7ycoCTG51N7y175StI9BlAXrmgZrFhZOb0/PyjHBher/NmsdBgbbQ1Inhq+gIhz6+7Gb+jWF2Vqi7Mf1xnQ== supports-hyperlinks@^2.0.0: @@ -6853,7 +6853,7 @@ timers-ext@^0.1.7: tmpl@1.0.x: version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-fast-properties@^2.0.0: @@ -6936,7 +6936,7 @@ trim-newlines@^4.0.2: ts-jest@27.1.2: version "27.1.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.2.tgz#5991d6eb3fd8e1a8d4b8f6de3ec0a3cc567f3151" + resolved "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.2.tgz" integrity sha512-eSOiJOWq6Hhs6Khzk5wKC5sgWIXgXqOCiIl1+3lfnearu58Hj4QpE5tUhQcA3xtZrELbcvAGCsd6HB8OsaVaTA== dependencies: bs-logger "0.x" @@ -6950,7 +6950,7 @@ ts-jest@27.1.2: ts-node@10.4.0: version "10.4.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" + resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz" integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== dependencies: "@cspotcode/source-map-support" "0.7.0" @@ -6968,7 +6968,7 @@ ts-node@10.4.0: tsc-watch@4.6.0: version "4.6.0" - resolved "https://registry.yarnpkg.com/tsc-watch/-/tsc-watch-4.6.0.tgz#a0eba1300cbe3048ab6d3a3e06de47141b613beb" + resolved "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.6.0.tgz" integrity sha512-DRMADjFe44EDWb+YMIOj4b83UrU6le27L3/o0/9FlmA01ikFd5Dl9RD5h1hpeh0mQdIqXvwfHZszCcjhH3bAmQ== dependencies: cross-spawn "^7.0.3" @@ -6979,7 +6979,7 @@ tsc-watch@4.6.0: tsconfig-paths@^3.12.0: version "3.12.0" - resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b" + resolved "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz" integrity sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg== dependencies: "@types/json5" "^0.0.29" @@ -6994,7 +6994,7 @@ tslib@^1.8.1: tslib@~2.1.0: version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== tsscmp@1.0.6: @@ -7101,7 +7101,7 @@ typedarray@^0.0.6: typescript@4.5.4: version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + resolved "https://registry.npmjs.org/typescript/-/typescript-4.5.4.tgz" integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== uid-safe@~2.1.5: @@ -7141,10 +7141,10 @@ universalify@^0.1.0, universalify@^0.1.2: resolved "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== -unleash-frontend@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-4.4.1.tgz#753008e8a1a25b204edf23595261635f030b8590" - integrity sha512-hyVd56nbWkFdyEeCeHMVZjKlQyWu82QqzGT6IDKzYJruYpIk00/9dm7zycziZIwzu7GXfKkI4J6fnm6Ge7mB5g== +unleash-frontend@4.6.0-beta.1: + version "4.6.0-beta.1" + resolved "https://registry.yarnpkg.com/unleash-frontend/-/unleash-frontend-4.6.0-beta.1.tgz#d5c385088099ce0751de951bdd67551fa28814d1" + integrity sha512-ioXa7Bylx7LlzkhJZfgYitXqXZqKrhH9dtEbrHwwcEXYCnirWVgiZgtJRVWR3EXww08t3hf8g95YGrgjdYFRgg== unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" @@ -7208,7 +7208,7 @@ v8-compile-cache@^2.0.3: v8-to-istanbul@^8.1.0: version "8.1.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz" integrity sha512-/PRhfd8aTNp9Ggr62HPzXg2XasNFGy5PBt0Rp04du7/8GNNSgxFL6WBTkgMKSL9bFjH+8kKEG3f37FmxiTqUUA== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" @@ -7432,7 +7432,7 @@ yallist@^4.0.0: yaml@^1.10.2: version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + resolved "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.9: