diff --git a/src/api-openapi.js b/src/api-openapi.js index bcf052c44..b2d1f99a2 100644 --- a/src/api-openapi.js +++ b/src/api-openapi.js @@ -23,13 +23,6 @@ class ApiOpenapiApp { RegisterRoutes(app); - // RFC 8631 service-desc link relation - // https://datatracker.ietf.org/doc/html/rfc8631 - app.get('/ds/api', (req, res) => { - res.setHeader('Link', '; rel="service-desc"'); - res.status(204).end(); - }); - app.get('/ds/api/openapi.yaml', (req, res) => { res.setHeader('Content-Type', 'application/yaml'); res.send(spec); @@ -54,14 +47,9 @@ class ApiOpenapiApp { */ specObject.components.securitySchemes.jwt.flows.clientCredentials.tokenUrl = `${process.env.OIDC_ISSUER}/protocol/openid-connect/token`; - RegisterRoutes(app); + specObject.components.securitySchemes.openid.openIdConnectUrl = `${process.env.OIDC_ISSUER}/.well-known/openid-configuration`; - // RFC 8631 service-desc link relation - // https://datatracker.ietf.org/doc/html/rfc8631 - app.get('/ds/api/v2', (req, res) => { - res.setHeader('Link', '; rel="service-desc"'); - res.status(204).end(); - }); + RegisterRoutes(app); app.get('/ds/api/v2/openapi.yaml', (req, res) => { res.setHeader('Content-Type', 'application/yaml'); @@ -86,6 +74,13 @@ class ApiOpenapiApp { this.prepareV2(app); this.prepareV1(app); + // RFC 8631 service-desc link relation + // https://datatracker.ietf.org/doc/html/rfc8631 + app.get('/ds/api', (req, res) => { + res.setHeader('Link', '; rel="service-desc"'); + res.status(204).end(); + }); + app.use(function errorHandler(err, req, res, next) { if (err instanceof UnauthorizedError) { return res.status(err.status).json({ diff --git a/src/api-proxy.js b/src/api-proxy.js index 3c7d89558..8540c1521 100644 --- a/src/api-proxy.js +++ b/src/api-proxy.js @@ -1,4 +1,3 @@ - const express = require('express'); const pathModule = require('path'); @@ -11,31 +10,38 @@ class ApiProxyApp { prepareMiddleware({ keystone }) { const app = express(); - - const apiProxy = createProxyMiddleware({ - target: this._gwaApiUrl, - changeOrigin: true, - pathRewrite: { '^/gw/api/': '/v2/' }, - onProxyReq: (proxyReq, req) => { - // console.log(req.headers) - // proxyReq.removeHeader("cookie"); - proxyReq.removeHeader("cookie"); - proxyReq.setHeader('Accept', 'application/json') - proxyReq.setHeader('Authorization', `Bearer ${req.header('x-forwarded-access-token')}`) }, - onError:(err, req, res, target) => { - console.log(err) - res.writeHead(400, { - 'Content-Type': 'text/plain', - }); - res.end('error reaching api'); - } - }) - app.all(/^\/gw\/api\//, apiProxy) + + const apiProxy = createProxyMiddleware({ + target: this._gwaApiUrl, + changeOrigin: true, + logLevel: 'debug', + pathRewrite: { '^/gw/api/': '/v2/' }, + onProxyReq: (proxyReq, req) => { + //console.log(req.headers) + // proxyReq.removeHeader("cookie"); + proxyReq.removeHeader('cookie'); + proxyReq.setHeader('Accept', 'application/json'); + proxyReq.setHeader( + 'Authorization', + 'authorization' in req.headers + ? req.header('authorization') + : `Bearer ${req.header('x-forwarded-access-token')}` + ); + }, + onError: (err, req, res, target) => { + console.log(err); + res.writeHead(400, { + 'Content-Type': 'text/plain', + }); + res.end('error reaching api'); + }, + }); + app.all(/^\/gw\/api\//, apiProxy); return app; } } module.exports = { - ApiProxyApp, -}; \ No newline at end of file + ApiProxyApp, +}; diff --git a/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-85071c.gql b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-85071c.gql new file mode 100644 index 000000000..5fdb60eee --- /dev/null +++ b/src/authz/graphql-whitelist/httplocalhost4180managernamespaces-85071c.gql @@ -0,0 +1,7 @@ + + mutation CreateNamespace($name: String!) { + createNamespace(name: $name) { + id + name + } + } diff --git a/src/batch/data-rules.js b/src/batch/data-rules.js index 847310919..241e38969 100644 --- a/src/batch/data-rules.js +++ b/src/batch/data-rules.js @@ -198,7 +198,7 @@ const metadata = { Namespace: { query: 'allNamespaces', refKey: 'extRefId', - sync: ['name'], + sync: ['name', 'displayName'], transformations: { // members: { // name: 'connectExclusiveList', @@ -474,7 +474,7 @@ const metadata = { resourceScopes: { name: 'toStringDefaultArray' }, clientRoles: { name: 'toStringDefaultArray' }, clientMappers: { name: 'toStringDefaultArray' }, - environmentDetails: { name: 'toString' }, + environmentDetails: { name: 'toStringDefaultArray' }, inheritFrom: { name: 'connectOne', list: 'allSharedIdPs', diff --git a/src/batch/feed-worker.ts b/src/batch/feed-worker.ts index 76122ea68..b33db0978 100644 --- a/src/batch/feed-worker.ts +++ b/src/batch/feed-worker.ts @@ -476,9 +476,14 @@ export const syncRecords = async function ( } if (Object.keys(data).length === 0) { logger.debug('[%s] [%s] no update', entity, localRecord.id); + const firstChildResult = childResults + .filter((r) => r.result != 'no-change') + .pop(); return { status: 200, - result: 'no-change', + result: firstChildResult + ? firstChildResult.result + '-child' + : 'no-change', id: localRecord['id'], childResults, ownedBy: diff --git a/src/batch/transformations/toStringDefaultArray.ts b/src/batch/transformations/toStringDefaultArray.ts index 41973c2ae..a07fa9214 100644 --- a/src/batch/transformations/toStringDefaultArray.ts +++ b/src/batch/transformations/toStringDefaultArray.ts @@ -7,6 +7,10 @@ export function toStringDefaultArray( inputData: any, key: string ) { + // if new and not passed, then set an empty array as a default + if (inputData[key] == null && currentData == null) { + return '[]'; + } return inputData[key] == null || (currentData != null && currentData[key] === stringify(inputData[key])) ? null diff --git a/src/controllers/ioc/keystoneInjector.ts b/src/controllers/ioc/keystoneInjector.ts index 1d6e4b41f..a29f9dd4c 100644 --- a/src/controllers/ioc/keystoneInjector.ts +++ b/src/controllers/ioc/keystoneInjector.ts @@ -41,12 +41,14 @@ export class KeystoneService { public createContext(request: any, skipAccessControl: boolean = false): any { const _scopes = scopes(request.user.scope); + const identityProvider = request.user.identity_provider; + const identity = { id: null, name: resolveName(request.user), username: resolveUsername(request.user), namespace: request.params.ns, - roles: JSON.stringify(scopesToRoles(null, _scopes)), + roles: JSON.stringify(scopesToRoles(identityProvider, _scopes)), scopes: _scopes, userId: null, } as any; diff --git a/src/controllers/v2/GatewayController.ts b/src/controllers/v2/GatewayController.ts index 94a7f5c1c..79b0b7a54 100644 --- a/src/controllers/v2/GatewayController.ts +++ b/src/controllers/v2/GatewayController.ts @@ -9,6 +9,8 @@ import { Security, Body, Tags, + FormField, + UploadedFile, } from 'tsoa'; import { KeystoneService } from '../ioc/keystoneInjector'; import { inject, injectable } from 'tsyringe'; @@ -20,6 +22,7 @@ import { removeKeys, } from '../../batch/feed-worker'; import { GatewayRoute } from './types'; +import { PublishResult } from './types-extra'; @injectable() @Route('/namespaces/{ns}/gateway') @@ -31,6 +34,17 @@ export class GatewayController extends Controller { this.keystone = _keystone; } + @Put() + @OperationId('publish-gateway-config') + @Security('jwt', ['Gateway.Config']) + public async put( + @FormField() dryRun: boolean, + @UploadedFile() configFile: Express.Multer.File + ): Promise { + // stub - gwa-api implements this + return { error: 'Stub - not implemented' }; + } + /** * Get a summary of your Gateway Services * > `Required Scope:` Namespace.Manage diff --git a/src/controllers/v2/NamespaceController.ts b/src/controllers/v2/NamespaceController.ts index 5a1007aff..1ef0715d6 100644 --- a/src/controllers/v2/NamespaceController.ts +++ b/src/controllers/v2/NamespaceController.ts @@ -9,13 +9,15 @@ import { Tags, Delete, Query, + Post, + Body, } from 'tsoa'; import { ValidateError, FieldErrors } from 'tsoa'; import { KeystoneService } from '../ioc/keystoneInjector'; import { inject, injectable } from 'tsyringe'; import { gql } from 'graphql-request'; import { WorkbookService } from '../../services/report/workbook.service'; -import { Namespace } from '../../services/keystone/types'; +import { Namespace, NamespaceInput } from '../../services/keystone/types'; import { Readable } from 'stream'; import { @@ -33,6 +35,7 @@ import { Activity } from './types'; import { getActivity } from '../../services/keystone/activity'; import { transformActivity } from '../../services/workflow'; import { ActivityDetail } from './types-extra'; + const logger = Logger('controllers.Namespace'); /** @@ -107,7 +110,7 @@ export class NamespaceController extends Controller { query: list, }); logger.debug('Result %j', result); - return result.data.allNamespaces.map((ns: Namespace) => ns.name); + return result.data.allNamespaces.map((ns: Namespace) => ns.name).sort(); } /** @@ -136,6 +139,40 @@ export class NamespaceController extends Controller { return result.data.namespace; } + /** + * Create a namespace + * + * @summary Create Namespace + * @param ns + * @param request + * @returns + */ + @Post() + @OperationId('create-namespace') + @Security('jwt', []) + public async create( + @Request() request: any, + @Body() vars: NamespaceInput + ): Promise { + const result = await this.keystone.executeGraphQL({ + context: this.keystone.createContext(request), + query: createNS, + variables: vars, + }); + logger.debug('Result %j', result); + if (result.errors) { + const errors: FieldErrors = {}; + result.errors.forEach((err: any, ind: number) => { + errors[`d${ind}`] = { message: err.message }; + }); + throw new ValidateError(errors, 'Unable to create namespace'); + } + return { + name: result.data.createNamespace.name, + displayName: result.data.createNamespace.displayName, + }; + } + /** * Delete the namespace * > `Required Scope:` Namespace.Manage @@ -214,6 +251,7 @@ const item = gql` query Namespace($ns: String!) { namespace(ns: $ns) { name + displayName scopes { name } @@ -234,3 +272,12 @@ const deleteNS = gql` forceDeleteNamespace(namespace: $ns, force: $force) } `; + +const createNS = gql` + mutation CreateNamespace($name: String, $displayName: String) { + createNamespace(name: $name, displayName: $displayName) { + name + displayName + } + } +`; diff --git a/src/controllers/v2/openapi.yaml b/src/controllers/v2/openapi.yaml index 5c6ad39cd..306e8d3ac 100644 --- a/src/controllers/v2/openapi.yaml +++ b/src/controllers/v2/openapi.yaml @@ -188,6 +188,16 @@ components: - tag2 organization: ministry-of-citizens-services organizationUnit: databc + PublishResult: + properties: + message: + type: string + results: + type: string + error: + type: string + type: object + additionalProperties: false GatewayServiceRefID: type: string GatewayRouteRefID: @@ -277,9 +287,7 @@ components: clientRegistration: managed clientId: a-client-id clientSecret: a-client-secret - CredentialIssuerRefID: - type: string - UserRefID: + undefinedRefID: type: string CredentialIssuer: properties: @@ -338,9 +346,9 @@ components: type: string type: array inheritFrom: - $ref: '#/components/schemas/CredentialIssuerRefID' + $ref: '#/components/schemas/undefinedRefID' owner: - $ref: '#/components/schemas/UserRefID' + $ref: '#/components/schemas/undefinedRefID' type: object additionalProperties: false example: @@ -350,7 +358,7 @@ components: clientAuthenticator: client-secret mode: auto environmentDetails: [] - owner: acope@idir + owner: janis@gov.bc.ca Maybe_Scalars-at-String_: type: string nullable: true @@ -414,6 +422,8 @@ components: $ref: '#/components/schemas/Maybe_Scalars-at-String_' scopes: $ref: '#/components/schemas/Maybe_Array_Maybe_UmaScope___' + displayName: + $ref: '#/components/schemas/Maybe_Scalars-at-String_' name: type: string id: @@ -426,6 +436,13 @@ components: required: - name type: object + NamespaceInput: + properties: + displayName: + $ref: '#/components/schemas/Maybe_Scalars-at-String_' + name: + $ref: '#/components/schemas/Maybe_Scalars-at-String_' + type: object ActivityDetail: properties: id: @@ -539,6 +556,8 @@ components: type: string LegalRefID: type: string + CredentialIssuerRefID: + type: string Environment: properties: appId: @@ -621,6 +640,10 @@ components: description: 'Authz Portal Login' scheme: bearer bearerFormat: JWT + openid: + type: openIdConnect + description: 'OIDC Login' + openIdConnectUrl: 'https://well_known_endpoint' info: title: 'APS Directory API' version: 1.1.0 @@ -961,6 +984,37 @@ paths: schema: type: string '/namespaces/{ns}/gateway': + put: + operationId: publish-gateway-config + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/PublishResult' + tags: + - 'Gateway Services' + security: + - + jwt: + - Gateway.Config + parameters: [] + requestBody: + required: true + content: + multipart/form-data: + schema: + type: object + properties: + dryRun: + type: string + configFile: + type: string + format: binary + required: + - dryRun + - configFile get: operationId: get-gateway-routes responses: @@ -1140,6 +1194,29 @@ paths: - jwt: [] parameters: [] + post: + operationId: create-namespace + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/Namespace' + description: 'Create a namespace' + summary: 'Create Namespace' + tags: + - Namespaces + security: + - + jwt: [] + parameters: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/NamespaceInput' '/namespaces/{ns}': get: operationId: namespace-profile diff --git a/src/controllers/v2/routes.ts b/src/controllers/v2/routes.ts index 21bb4b1ec..9f05c5aee 100644 --- a/src/controllers/v2/routes.ts +++ b/src/controllers/v2/routes.ts @@ -34,6 +34,8 @@ const promiseAny = require('promise.any'); import { iocContainer } from './../ioc'; import { IocContainer, IocContainerFactory } from '@tsoa/runtime'; import * as express from 'express'; +const multer = require('multer'); +const upload = multer(); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa @@ -128,6 +130,16 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "PublishResult": { + "dataType": "refObject", + "properties": { + "message": {"dataType":"string"}, + "results": {"dataType":"string"}, + "error": {"dataType":"string"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "GatewayServiceRefID": { "dataType": "refAlias", "type": {"dataType":"string","validators":{}}, @@ -185,12 +197,7 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "CredentialIssuerRefID": { - "dataType": "refAlias", - "type": {"dataType":"string","validators":{}}, - }, - // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "UserRefID": { + "undefinedRefID": { "dataType": "refAlias", "type": {"dataType":"string","validators":{}}, }, @@ -215,8 +222,8 @@ const models: TsoaRoute.Models = { "resourceScopes": {"dataType":"array","array":{"dataType":"string"}}, "clientRoles": {"dataType":"array","array":{"dataType":"string"}}, "clientMappers": {"dataType":"array","array":{"dataType":"string"}}, - "inheritFrom": {"ref":"CredentialIssuerRefID"}, - "owner": {"ref":"UserRefID"}, + "inheritFrom": {"ref":"undefinedRefID"}, + "owner": {"ref":"undefinedRefID"}, }, "additionalProperties": false, }, @@ -263,7 +270,12 @@ const models: TsoaRoute.Models = { // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Namespace": { "dataType": "refAlias", - "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"orgAdmins":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"orgNoticeViewed":{"ref":"Maybe_Scalars-at-Boolean_"},"orgEnabled":{"ref":"Maybe_Scalars-at-Boolean_"},"orgUpdatedAt":{"ref":"Maybe_Scalars-at-Float_"},"orgUnit":{"ref":"Maybe_Scalars-at-JSON_"},"org":{"ref":"Maybe_Scalars-at-JSON_"},"permProtectedNs":{"ref":"Maybe_Scalars-at-String_"},"permDataPlane":{"ref":"Maybe_Scalars-at-String_"},"permDomains":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"prodEnvId":{"ref":"Maybe_Scalars-at-String_"},"scopes":{"ref":"Maybe_Array_Maybe_UmaScope___"},"name":{"dataType":"string","required":true},"id":{"ref":"Maybe_Scalars-at-String_"},"__typename":{"dataType":"enum","enums":["Namespace"]}},"validators":{}}, + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"orgAdmins":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"orgNoticeViewed":{"ref":"Maybe_Scalars-at-Boolean_"},"orgEnabled":{"ref":"Maybe_Scalars-at-Boolean_"},"orgUpdatedAt":{"ref":"Maybe_Scalars-at-Float_"},"orgUnit":{"ref":"Maybe_Scalars-at-JSON_"},"org":{"ref":"Maybe_Scalars-at-JSON_"},"permProtectedNs":{"ref":"Maybe_Scalars-at-String_"},"permDataPlane":{"ref":"Maybe_Scalars-at-String_"},"permDomains":{"ref":"Maybe_Array_Maybe_Scalars-at-String___"},"prodEnvId":{"ref":"Maybe_Scalars-at-String_"},"scopes":{"ref":"Maybe_Array_Maybe_UmaScope___"},"displayName":{"ref":"Maybe_Scalars-at-String_"},"name":{"dataType":"string","required":true},"id":{"ref":"Maybe_Scalars-at-String_"},"__typename":{"dataType":"enum","enums":["Namespace"]}},"validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "NamespaceInput": { + "dataType": "refAlias", + "type": {"dataType":"nestedObjectLiteral","nestedProperties":{"displayName":{"ref":"Maybe_Scalars-at-String_"},"name":{"ref":"Maybe_Scalars-at-String_"}},"validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "ActivityDetail": { @@ -355,6 +367,11 @@ const models: TsoaRoute.Models = { "type": {"dataType":"string","validators":{}}, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "CredentialIssuerRefID": { + "dataType": "refAlias", + "type": {"dataType":"string","validators":{}}, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa "Environment": { "dataType": "refObject", "properties": { @@ -780,6 +797,37 @@ export function RegisterRoutes(app: express.Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.put('/ds/api/v2/namespaces/:ns/gateway', + authenticateMiddleware([{"jwt":["Gateway.Config"]}]), + upload.single('configFile'), + + async function GatewayController_put(request: any, response: any, next: any) { + const args = { + dryRun: {"in":"formData","name":"dryRun","required":true,"dataType":"string"}, + configFile: {"in":"formData","name":"configFile","required":true,"dataType":"file"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(GatewayController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.put.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.get('/ds/api/v2/namespaces/:ns/gateway', authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), @@ -1019,6 +1067,36 @@ export function RegisterRoutes(app: express.Router) { } }); // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + app.post('/ds/api/v2/namespaces', + authenticateMiddleware([{"jwt":[]}]), + + async function NamespaceController_create(request: any, response: any, next: any) { + const args = { + request: {"in":"request","name":"request","required":true,"dataType":"object"}, + vars: {"in":"body","name":"vars","required":true,"ref":"NamespaceInput"}, + }; + + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + + let validatedArgs: any[] = []; + try { + validatedArgs = getValidatedArgs(args, request, response); + + const container: IocContainer = typeof iocContainer === 'function' ? (iocContainer as IocContainerFactory)(request) : iocContainer; + + const controller: any = await container.get(NamespaceController); + if (typeof controller['setStatus'] === 'function') { + controller.setStatus(undefined); + } + + + const promise = controller.create.apply(controller, validatedArgs as any); + promiseHandler(controller, promise, response, undefined, next); + } catch (err) { + return next(err); + } + }); + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa app.delete('/ds/api/v2/namespaces/:ns', authenticateMiddleware([{"jwt":["Namespace.Manage"]}]), diff --git a/src/controllers/v2/types-extra.ts b/src/controllers/v2/types-extra.ts index ecc3eede2..438ddde0d 100644 --- a/src/controllers/v2/types-extra.ts +++ b/src/controllers/v2/types-extra.ts @@ -10,3 +10,12 @@ export interface ActivityDetail { activityAt: Scalars['DateTime']; blob?: any; } + +/** + * @tsoaModel + */ +export interface PublishResult { + message?: string; + results?: string; + error?: string; +} diff --git a/src/controllers/v2/types.ts b/src/controllers/v2/types.ts index 7e4c9839c..366bbdbfb 100644 --- a/src/controllers/v2/types.ts +++ b/src/controllers/v2/types.ts @@ -134,6 +134,7 @@ export interface Alert { export interface Namespace { extRefId?: string; // Primary Key name?: string; + displayName?: string; } @@ -315,7 +316,7 @@ export interface Environment { * "clientAuthenticator": "client-secret", * "mode": "auto", * "environmentDetails": [], - * "owner": "acope@idir" + * "owner": "janis@gov.bc.ca" * } */ export interface CredentialIssuer { @@ -336,8 +337,8 @@ export interface CredentialIssuer { resourceScopes?: string[]; clientRoles?: string[]; clientMappers?: string[]; - inheritFrom?: CredentialIssuerRefID; - owner?: UserRefID; + inheritFrom?: undefinedRefID; + owner?: undefinedRefID; } @@ -477,6 +478,7 @@ export interface User { name?: string; email?: string; legalsAgreed?: UserLegalsAgreed[]; + provider?: string; } @@ -554,3 +556,8 @@ export type OrganizationUnitRefID = string * @tsoaModel */ export type UserRefID = string + +/** + * @tsoaModel + */ +export type undefinedRefID = string diff --git a/src/lists/CredentialIssuer.js b/src/lists/CredentialIssuer.js index 1b51a1103..6147b2c5b 100644 --- a/src/lists/CredentialIssuer.js +++ b/src/lists/CredentialIssuer.js @@ -26,9 +26,10 @@ const { const { syncSharedIdp, addClientsToSharedIdP, + DeleteClientsFromSharedIdP, } = require('../services/workflow'); const { Logger } = require('../logger'); -const { kebabCase } = require('lodash'); +const kebabCase = require('just-kebab-case'); const logger = Logger('lists.credentialissuer'); module.exports = { @@ -249,6 +250,12 @@ module.exports = { }, afterDelete: async function ({ existingItem, context }) { + await DeleteClientsFromSharedIdP( + context, + existingItem.clientId, + `${existingItem.inheritFrom}` + ); + await new StructuredActivityService( context, context.authedItem['namespace'] diff --git a/src/lists/extensions/CredentialIssuerExt.ts b/src/lists/extensions/CredentialIssuerExt.ts index f44463ff1..5913f0e27 100644 --- a/src/lists/extensions/CredentialIssuerExt.ts +++ b/src/lists/extensions/CredentialIssuerExt.ts @@ -1,5 +1,5 @@ const { EnforcementPoint } = require('../../authz/enforcement'); -import { kebabCase } from 'lodash'; +import kebabCase from 'just-kebab-case'; import { generateEnvDetails, lookupSharedIssuers, diff --git a/src/lists/extensions/Namespace.ts b/src/lists/extensions/Namespace.ts index 9bb8b4e7c..59c815c5c 100644 --- a/src/lists/extensions/Namespace.ts +++ b/src/lists/extensions/Namespace.ts @@ -7,7 +7,6 @@ import { ResourceSetInput, } from '../../services/uma2'; import { - getOrganizationUnit, lookupProductEnvironmentServicesBySlug, lookupUsersByUsernames, recordActivity, @@ -18,7 +17,6 @@ import { getNamespaceResourceSets, isUserBasedResourceOwners, doClientLoginForCredentialIssuer, - EnvironmentContext, getOrgPoliciesForResource, } from './Common'; import type { TokenExchangeResult } from './Common'; @@ -32,17 +30,12 @@ import { DeleteNamespaceValidate, } from '../../services/workflow/delete-namespace'; import { GWAService } from '../../services/gwaapi'; -import { - camelCaseAttributes, - transformSingleValueAttributes, -} from '../../services/utils'; +import { regExprValidation } from '../../services/utils'; import getSubjectToken from '../../auth/auth-token'; import { GroupAccessService, NamespaceService, } from '../../services/org-groups'; -import { IssuerEnvironmentConfig } from '../../services/workflow/types'; -import { Keystone } from '@keystonejs/keystone'; import { Logger } from '../../logger'; import { getGwaProductEnvironment } from '../../services/workflow'; import { NotificationService } from '../../services/notification/notification.service'; @@ -54,6 +47,7 @@ import { getResource, transformOrgAndOrgUnit, } from '../../services/keycloak/namespace-details'; +import { newNamespaceID } from '../../services/identifiers'; const logger = Logger('ext.Namespace'); @@ -69,6 +63,7 @@ const typeNamespace = ` type Namespace { id: String name: String!, + displayName: String, scopes: [UMAScope], prodEnvId: String, permDomains: [String], @@ -85,7 +80,8 @@ type Namespace { const typeNamespaceInput = ` input NamespaceInput { - name: String!, + name: String, + displayName: String } `; @@ -422,7 +418,8 @@ module.exports = { }, }, { - schema: 'createNamespace(namespace: String!): Namespace', + schema: + 'createNamespace(name: String, displayName: String): Namespace', resolver: async ( item: any, args: any, @@ -431,11 +428,13 @@ module.exports = { { query, access }: any ) => { const namespaceValidationRule = '^[a-z][a-z0-9-]{4,14}$'; - const re = new RegExp(namespaceValidationRule); - assert.strictEqual( - re.test(args.namespace), - true, - 'Namespace name must be between 5 and 15 alpha-numeric lowercase characters and begin with an alphabet.' + + const newNS = args.name ? args.name : newNamespaceID(); + + regExprValidation( + namespaceValidationRule, + newNS, + 'Namespace name must be between 5 and 15 alpha-numeric lowercase characters and start and end with an alphabet.' ); const noauthContext = context.createContext({ @@ -458,7 +457,7 @@ module.exports = { envCtx.issuerEnvConfig.clientId, envCtx.issuerEnvConfig.clientSecret ); - await nsService.checkNamespaceAvailable(args.namespace); + await nsService.checkNamespaceAvailable(newNS); // This function gets all resources but also sets the accessToken in envCtx // which we need to create the resource set @@ -478,7 +477,8 @@ module.exports = { 'CredentialIssuer.Admin', ]; const res = { - name: args.namespace, + name: newNS, + displayName: args.displayName, type: 'namespace', resource_scopes: scopes, ownerManagedAccess: true, @@ -491,12 +491,18 @@ module.exports = { envCtx.issuerEnvConfig.issuerUrl, envCtx.accessToken ); - await permissionApi.createPermission( - rset.id, - envCtx.subjectUuid, - true, - 'Namespace.Manage' - ); + for (const scope of [ + 'Namespace.Manage', + 'CredentialIssuer.Admin', + 'GatewayConfig.Publish', + ]) { + await permissionApi.createPermission( + rset.id, + envCtx.subjectUuid, + true, + scope + ); + } } const kcGroupService = new KeycloakGroupService( @@ -507,27 +513,24 @@ module.exports = { envCtx.issuerEnvConfig.clientSecret ); - await kcGroupService.createIfMissing('ns', args.namespace); + await kcGroupService.createIfMissing('ns', newNS); await recordActivity( context.sudo(), 'create', 'Namespace', - args.namespace, - `Created ${args.namespace} namespace`, + newNS, + `Created ${newNS} namespace`, 'success', JSON.stringify({ message: '{actor} created {ns} namespace', params: { actor: context.authedItem.name, - ns: args.namespace, + ns: newNS, }, }), - args.namespace, - [ - `Namespace:${args.namespace}`, - `actor:${context.authedItem.name}`, - ] + newNS, + [`Namespace:${newNS}`, `actor:${context.authedItem.name}`] ); return rset; diff --git a/src/lists/extensions/ServiceAccount.ts b/src/lists/extensions/ServiceAccount.ts index f2aa9917e..ed50e19ee 100644 --- a/src/lists/extensions/ServiceAccount.ts +++ b/src/lists/extensions/ServiceAccount.ts @@ -81,11 +81,13 @@ module.exports = { info: any, { query, access }: any ) => { - context.skipAccessControl = true; + const noauthContext = context.createContext({ + skipAccessControl: true, + }); const productEnvironmentSlug = process.env.GWA_PROD_ENV_SLUG; const { newCredentials } = await CreateServiceAccount( - context, + noauthContext, productEnvironmentSlug, context.req.user.namespace, args.resourceId, diff --git a/src/lists/extensions/UserExt.ts b/src/lists/extensions/UserExt.ts index 76b33e03d..6d3784ab4 100644 --- a/src/lists/extensions/UserExt.ts +++ b/src/lists/extensions/UserExt.ts @@ -1,16 +1,6 @@ const { EnforcementPoint } = require('../../authz/enforcement'); import { lookupProviderUserByEmail } from '../../services/keystone/user'; -import { kebabCase } from 'lodash'; -import { - generateEnvDetails, - lookupSharedIssuers, -} from '../../services/keystone'; -import { - CredentialIssuer, - CredentialIssuerWhereInput, - User, - UserWhereInput, -} from '../../services/keystone/types'; +import { User, UserWhereInput } from '../../services/keystone/types'; module.exports = { extensions: [ diff --git a/src/nextapp/components/api-product-item/api-product-item.tsx b/src/nextapp/components/api-product-item/api-product-item.tsx index 649a85bab..72a4c8c73 100644 --- a/src/nextapp/components/api-product-item/api-product-item.tsx +++ b/src/nextapp/components/api-product-item/api-product-item.tsx @@ -63,15 +63,15 @@ const ApiProductItem: React.FC = ({ )} - {!isPublic && !isTiered && ( + {!isTiered && ( <> {isPublic && ( - + )} {!isPublic && ( ; + displayName?: Maybe; }; @@ -5415,6 +5416,7 @@ export type Namespace = { __typename?: 'Namespace'; id?: Maybe; name: Scalars['String']; + displayName?: Maybe; scopes?: Maybe>>; prodEnvId?: Maybe; permDomains?: Maybe>>; @@ -5429,7 +5431,8 @@ export type Namespace = { }; export type NamespaceInput = { - name: Scalars['String']; + name?: Maybe; + displayName?: Maybe; }; /** A keystone list */ diff --git a/src/package-lock.json b/src/package-lock.json index bd8cafbc2..3e70e7aa5 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -50,7 +50,7 @@ "graphql": "^15.4.0", "graphql-request": "^3.4.0", "graphql-tools": "^7.0.2", - "http-proxy-middleware": "^1.1.2", + "http-proxy-middleware": "^2.0.6", "isomorphic-unfetch": "^3.1.0", "js-yaml": "^4.1.0", "json-stable-stringify": "^1.0.2", @@ -15831,9 +15831,9 @@ "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==" }, "node_modules/@types/http-proxy": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.5.tgz", - "integrity": "sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q==", + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", "dependencies": { "@types/node": "*" } @@ -28528,18 +28528,26 @@ } }, "node_modules/http-proxy-middleware": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.1.2.tgz", - "integrity": "sha512-YRFUeOG3q85FJjAaYVJUoNRW9a73SDlOtAyQOS5PHLr18QeZ/vEhxywNoOPiEO8BxCegz4RXzTHcvyLEGB78UA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "dependencies": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", "micromatch": "^4.0.2" }, "engines": { - "node": ">=8.0.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } } }, "node_modules/http2-wrapper": { @@ -49255,7 +49263,8 @@ "@apollographql/apollo-tools": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.3.tgz", - "integrity": "sha512-VcsXHfTFoCodDAgJZxN04GdFK1kqOhZQnQY/9Fa147P+I8xfvOSz5d+lKAPB+hwSgBNyd7ncAKGIs4+utbL+yA==" + "integrity": "sha512-VcsXHfTFoCodDAgJZxN04GdFK1kqOhZQnQY/9Fa147P+I8xfvOSz5d+lKAPB+hwSgBNyd7ncAKGIs4+utbL+yA==", + "requires": {} }, "@apollographql/graphql-playground-html": { "version": "1.6.29", @@ -51676,7 +51685,8 @@ "@chakra-ui/css-reset": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@chakra-ui/css-reset/-/css-reset-1.0.0.tgz", - "integrity": "sha512-UaPsImGHvCgFO3ayp6Ugafu2/3/EG8wlW/8Y9Ihfk1UFv8cpV+3BfWKmuZ7IcmxcBL9dkP6E8p3/M1T0FB92hg==" + "integrity": "sha512-UaPsImGHvCgFO3ayp6Ugafu2/3/EG8wlW/8Y9Ihfk1UFv8cpV+3BfWKmuZ7IcmxcBL9dkP6E8p3/M1T0FB92hg==", + "requires": {} }, "@chakra-ui/descendant": { "version": "2.0.0", @@ -53410,7 +53420,8 @@ "@graphql-typed-document-node/core": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.1.0.tgz", - "integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==" + "integrity": "sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==", + "requires": {} }, "@hapi/accept": { "version": "5.0.1", @@ -57575,7 +57586,8 @@ "slate-react-placeholder": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/slate-react-placeholder/-/slate-react-placeholder-0.2.9.tgz", - "integrity": "sha512-YSJ9Gb4tGpbzPje3eNKtu26hWM8ApxTk9RzjK+6zfD5V/RMTkuWONk24y6c9lZk0OAYNZNUmrnb/QZfU3j9nag==" + "integrity": "sha512-YSJ9Gb4tGpbzPje3eNKtu26hWM8ApxTk9RzjK+6zfD5V/RMTkuWONk24y6c9lZk0OAYNZNUmrnb/QZfU3j9nag==", + "requires": {} } } }, @@ -57655,7 +57667,8 @@ "react-codemirror2": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/react-codemirror2/-/react-codemirror2-7.2.1.tgz", - "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==" + "integrity": "sha512-t7YFmz1AXdlImgHXA9Ja0T6AWuopilub24jRaQdPVbzUJVNKIYuy3uCFZYa7CE5S3UW6SrSa5nAqVQvtzRF9gw==", + "requires": {} } } }, @@ -58302,7 +58315,8 @@ "version": "1.6.22", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-1.6.22.tgz", "integrity": "sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg==", - "dev": true + "dev": true, + "requires": {} }, "@mdx-js/util": { "version": "1.6.22", @@ -58358,7 +58372,8 @@ "@n1ru4l/graphql-live-query": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz", - "integrity": "sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg==" + "integrity": "sha512-BTpWy1e+FxN82RnLz4x1+JcEewVdfmUhV1C6/XYD5AjS7PQp9QFF7K8bCD6gzPTr2l+prvqOyVueQhFJxB1vfg==", + "requires": {} }, "@next/env": { "version": "9.5.5", @@ -58546,7 +58561,8 @@ "@primer/octicons-react": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/@primer/octicons-react/-/octicons-react-11.3.0.tgz", - "integrity": "sha512-4sVhkrBKuj3h+PFw69yOyO/l3nQB/mm95V+Kz7LRSlIrbZr6hZarZD5Ft4ewdONPROkIHQM/6KSK90+OAimxsQ==" + "integrity": "sha512-4sVhkrBKuj3h+PFw69yOyO/l3nQB/mm95V+Kz7LRSlIrbZr6hZarZD5Ft4ewdONPROkIHQM/6KSK90+OAimxsQ==", + "requires": {} }, "@prisma/bar": { "version": "0.0.1", @@ -59851,7 +59867,8 @@ "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -60202,7 +60219,8 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz", "integrity": "sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -61128,9 +61146,9 @@ "integrity": "sha512-EqX+YQxINb+MeXaIqYDASb6U6FCHbWjkj4a1CKDBks3d/QiB2+PqBLyO72vLDgAO1wUI4O+9gweRcQK11bTL/w==" }, "@types/http-proxy": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.5.tgz", - "integrity": "sha512-GNkDE7bTv6Sf8JbV2GksknKOsk7OznNYHSdrtvPJXO0qJ9odZig6IZKUi5RFGi6d1bf6dgIAe4uXi3DBc7069Q==", + "version": "1.17.11", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.11.tgz", + "integrity": "sha512-HC8G7c1WmaF2ekqpnFq626xd3Zz0uvaqFmBJNRZCGEZCXkvSdJoNFn/8Ygbd9fKNQj8UzLdCETaI0UWPAjK7IA==", "requires": { "@types/node": "*" } @@ -62358,7 +62376,8 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true + "dev": true, + "requires": {} }, "acorn-node": { "version": "1.8.2", @@ -62479,12 +62498,14 @@ "ajv-errors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==" + "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", + "requires": {} }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==" + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "requires": {} }, "ally.js": { "version": "1.4.1", @@ -62782,7 +62803,8 @@ "apollo-server-errors": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.5.0.tgz", - "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==" + "integrity": "sha512-lO5oTjgiC3vlVg2RKr3RiXIIQ5pGXBFxYGGUkKDhTud3jMIhs+gel8L8zsEjKaKxkjHhCQAA/bcEfYiKkGQIvA==", + "requires": {} }, "apollo-server-express": { "version": "2.25.3", @@ -63660,7 +63682,8 @@ "version": "0.3.7", "resolved": "https://registry.npmjs.org/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.7.tgz", "integrity": "sha512-squySRkf+6JGnvjoUtDEjSREJEBirnXi9NqP6rjSYsylxQxqBTz+pkmf395i9E2zsvmYUaI40BHo6SqZUdydlw==", - "dev": true + "dev": true, + "requires": {} }, "babel-plugin-polyfill-corejs2": { "version": "0.3.1", @@ -66072,7 +66095,8 @@ "create-react-context": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.1.6.tgz", - "integrity": "sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw==" + "integrity": "sha512-eCnYYEUEc5i32LHwpE/W7NlddOB9oHwsPaWtWzYtflNkkwa3IfindIcoXdVWs12zCbwaMCavKNu84EXogVIWHw==", + "requires": {} }, "create-require": { "version": "1.1.1", @@ -66235,7 +66259,8 @@ "icss-utils": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==" + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "requires": {} }, "lru-cache": { "version": "7.8.0", @@ -66255,7 +66280,8 @@ "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==" + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -67877,7 +67903,8 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz", "integrity": "sha512-9sm5/PxaFG7qNJvJzTROMM1Bk1ozXVTKI0buKOyb0Bsr1hrwi0H/TzxF/COtf1uxikIK8SwhX7K6zg78jAzbeA==", - "dev": true + "dev": true, + "requires": {} }, "eslint-plugin-jest": { "version": "24.1.3", @@ -67942,7 +67969,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.2.0.tgz", "integrity": "sha512-623WEiZJqxR7VdxFCKLI6d6LLpwJkGPYKODnkH3D7WpOG5KM8yWueBd8TLsNAetEJNF5iJmolaAKO3F8yzyVBQ==", - "dev": true + "dev": true, + "requires": {} }, "eslint-scope": { "version": "5.1.1", @@ -70061,7 +70089,8 @@ "graphql-executor": { "version": "0.0.22", "resolved": "https://registry.npmjs.org/graphql-executor/-/graphql-executor-0.0.22.tgz", - "integrity": "sha512-WbKSnSHFn6REKKH4T6UAwDM3mLUnYMQlQLNG0Fw+Lkb3ilCnL3m5lkJ7411LAI9sF7BvPbthovVZhsEUh9Xfag==" + "integrity": "sha512-WbKSnSHFn6REKKH4T6UAwDM3mLUnYMQlQLNG0Fw+Lkb3ilCnL3m5lkJ7411LAI9sF7BvPbthovVZhsEUh9Xfag==", + "requires": {} }, "graphql-extensions": { "version": "0.15.0", @@ -70192,7 +70221,8 @@ "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==" + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} } } }, @@ -70272,7 +70302,8 @@ "graphql-ws": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.7.0.tgz", - "integrity": "sha512-8yYuvnyqIjlJ/WfebOyu2GSOQeFauRxnfuTveY9yvrDGs2g3kR9Nv4gu40AKvRHbXlSJwTbMJ6dVxAtEyKwVRA==" + "integrity": "sha512-8yYuvnyqIjlJ/WfebOyu2GSOQeFauRxnfuTveY9yvrDGs2g3kR9Nv4gu40AKvRHbXlSJwTbMJ6dVxAtEyKwVRA==", + "requires": {} }, "minimatch": { "version": "4.2.1", @@ -70444,7 +70475,8 @@ "ws": { "version": "8.5.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==" + "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", + "requires": {} } } }, @@ -70524,7 +70556,8 @@ "graphql-ws": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.7.0.tgz", - "integrity": "sha512-8yYuvnyqIjlJ/WfebOyu2GSOQeFauRxnfuTveY9yvrDGs2g3kR9Nv4gu40AKvRHbXlSJwTbMJ6dVxAtEyKwVRA==" + "integrity": "sha512-8yYuvnyqIjlJ/WfebOyu2GSOQeFauRxnfuTveY9yvrDGs2g3kR9Nv4gu40AKvRHbXlSJwTbMJ6dVxAtEyKwVRA==", + "requires": {} }, "minimatch": { "version": "4.2.1", @@ -70607,7 +70640,8 @@ "graphql-sse": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/graphql-sse/-/graphql-sse-1.1.0.tgz", - "integrity": "sha512-xE8AGPJa5X+g7iFmRQw/8H+7lXIDJvSkW6lou/XSSq17opPQl+dbKOMiqraHMx52VrDgS061ZVx90OSuqS6ykA==" + "integrity": "sha512-xE8AGPJa5X+g7iFmRQw/8H+7lXIDJvSkW6lou/XSSq17opPQl+dbKOMiqraHMx52VrDgS061ZVx90OSuqS6ykA==", + "requires": {} }, "graphql-subscriptions": { "version": "1.1.0", @@ -70668,7 +70702,8 @@ "graphql-type-json": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/graphql-type-json/-/graphql-type-json-0.3.2.tgz", - "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==" + "integrity": "sha512-J+vjof74oMlCWXSvt0DOf2APEdZOCdubEvGDUAlqH//VBYcOYsGgRW7Xzorr44LvkjiuvecWc8fChxuZZbChtg==", + "requires": {} }, "graphql-upload": { "version": "11.0.0", @@ -70692,7 +70727,8 @@ "graphql-ws": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.9.0.tgz", - "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==" + "integrity": "sha512-sHkK9+lUm20/BGawNEWNtVAeJzhZeBg21VmvmLoT5NdGVeZWv5PdIhkcayQIAgjSyyQ17WMKmbDijIPG2On+Ag==", + "requires": {} }, "gray-percentage": { "version": "2.0.0", @@ -71295,11 +71331,11 @@ } }, "http-proxy-middleware": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-1.1.2.tgz", - "integrity": "sha512-YRFUeOG3q85FJjAaYVJUoNRW9a73SDlOtAyQOS5PHLr18QeZ/vEhxywNoOPiEO8BxCegz4RXzTHcvyLEGB78UA==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", "requires": { - "@types/http-proxy": "^1.17.5", + "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", "is-glob": "^4.0.1", "is-plain-obj": "^3.0.0", @@ -72202,7 +72238,8 @@ "isomorphic-ws": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", - "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==" + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "requires": {} }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -73616,7 +73653,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "26.0.0", @@ -74800,7 +74838,8 @@ "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -76025,7 +76064,8 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.7.tgz", "integrity": "sha512-VI3TyyHlGkO8uFle0IOibzpO1c1iJDcXcS/zBrQrXQQvJ2tpdwVzVZ7XdKsyRz1NdRmre4dqQkMZzUHaKIG/1w==", - "dev": true + "dev": true, + "requires": {} }, "match-sorter": { "version": "6.1.0", @@ -76334,7 +76374,8 @@ "meros": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/meros/-/meros-1.1.4.tgz", - "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==" + "integrity": "sha512-E9ZXfK9iQfG9s73ars9qvvvbSIkJZF5yOo9j4tcwM5tN8mUKfj/EKN5PzOr3ZH0y5wL7dLAHw3RVEfpQV9Q7VQ==", + "requires": {} }, "mersenne-twister": { "version": "1.1.0", @@ -76762,7 +76803,8 @@ "mongoose-legacy-pluralize": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", - "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==", + "requires": {} }, "move-concurrently": { "version": "1.0.1", @@ -77338,7 +77380,8 @@ "@next/react-refresh-utils": { "version": "9.5.5", "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-9.5.5.tgz", - "integrity": "sha512-Gz5z0+ID+KAGto6Tkgv1a340damEw3HG6ANLKwNi5/QSHqQ3JUAVxMuhz3qnL54505I777evpzL89ofWEMIWKw==" + "integrity": "sha512-Gz5z0+ID+KAGto6Tkgv1a340damEw3HG6ANLKwNi5/QSHqQ3JUAVxMuhz3qnL54505I777evpzL89ofWEMIWKw==", + "requires": {} }, "acorn": { "version": "6.4.2", @@ -79693,7 +79736,8 @@ "pg-pool": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.1.tgz", - "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==" + "integrity": "sha512-6iCR0wVrro6OOHFsyavV+i6KYL4lVNyYAB9RD18w66xSzN+d8b66HiwuP30Gp1SH5O9T82fckkzsRjlrhD0ioQ==", + "requires": {} }, "pg-protocol": { "version": "1.5.0", @@ -80583,7 +80627,8 @@ "version": "5.5.1", "resolved": "https://registry.npmjs.org/react-colorful/-/react-colorful-5.5.1.tgz", "integrity": "sha512-M1TJH2X3RXEt12sWkpa6hLc/bbYS0H6F4rIqjQZ+RxNBstpY67d9TrFXtqdZwhpmBXcCwEi7stKqFue3ZRkiOg==", - "dev": true + "dev": true, + "requires": {} }, "react-copy-to-clipboard": { "version": "5.0.4", @@ -80597,7 +80642,8 @@ "react-day-picker": { "version": "8.0.0-beta.3", "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.0.0-beta.3.tgz", - "integrity": "sha512-qvasG5iH6M+Ix3HAtCiSum6xbngG5re5F2bZN3VRof7FAl3LJbNcq+5geEITSLRt5uo3IsXF5eFvG6Ggpzw8og==" + "integrity": "sha512-qvasG5iH6M+Ix3HAtCiSum6xbngG5re5F2bZN3VRof7FAl3LJbNcq+5geEITSLRt5uo3IsXF5eFvG6Ggpzw8og==", + "requires": {} }, "react-display-name": { "version": "0.2.5", @@ -80729,12 +80775,14 @@ "react-icons": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", - "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==", + "requires": {} }, "react-image": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/react-image/-/react-image-4.0.3.tgz", - "integrity": "sha512-19MUK9u1qaw9xys8XEsVkSpVhHctEBUeYFvrLTe1PN+4w5Co13AN2WA7xtBshPM6SthsOj77SlDrEAeOaJpf7g==" + "integrity": "sha512-19MUK9u1qaw9xys8XEsVkSpVhHctEBUeYFvrLTe1PN+4w5Co13AN2WA7xtBshPM6SthsOj77SlDrEAeOaJpf7g==", + "requires": {} }, "react-immutable-proptypes": { "version": "2.2.0", @@ -80766,7 +80814,8 @@ "react-intersection-observer": { "version": "8.31.0", "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-8.31.0.tgz", - "integrity": "sha512-XraIC/tkrD9JtrmVA7ypEN1QIpKc52mXBH1u/bz/aicRLo8QQEJQAMUTb8mz4B6dqpPwyzgjrr7Ljv/2ACDtqw==" + "integrity": "sha512-XraIC/tkrD9JtrmVA7ypEN1QIpKc52mXBH1u/bz/aicRLo8QQEJQAMUTb8mz4B6dqpPwyzgjrr7Ljv/2ACDtqw==", + "requires": {} }, "react-is": { "version": "17.0.2", @@ -80852,12 +80901,14 @@ "react-prop-toggle": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/react-prop-toggle/-/react-prop-toggle-1.0.2.tgz", - "integrity": "sha512-JmerjAXs7qJ959+d0Ygt7Cb2+4fG+n3I2VXO6JO0AcAY1vkRN/JpZKAN67CMXY889xEJcfylmMPhzvf6nWO68Q==" + "integrity": "sha512-JmerjAXs7qJ959+d0Ygt7Cb2+4fG+n3I2VXO6JO0AcAY1vkRN/JpZKAN67CMXY889xEJcfylmMPhzvf6nWO68Q==", + "requires": {} }, "react-pseudo-state": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/react-pseudo-state/-/react-pseudo-state-2.2.2.tgz", - "integrity": "sha512-czJNP0MpcqJISnFwxYz8IlJklsUbWyuSqWJWLbXi/MUQWIEu2t6PbOGHIVvtpw9i0N3hHZ50nSNCDMLp4fpenQ==" + "integrity": "sha512-czJNP0MpcqJISnFwxYz8IlJklsUbWyuSqWJWLbXi/MUQWIEu2t6PbOGHIVvtpw9i0N3hHZ50nSNCDMLp4fpenQ==", + "requires": {} }, "react-query": { "version": "3.5.11", @@ -81423,7 +81474,8 @@ "redux-immutable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", - "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM= sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==" + "integrity": "sha1-Ohoy32Y2ZGK2NpHw4dw15HK7yfM= sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "requires": {} }, "redux-localstorage": { "version": "1.0.0-rc5", @@ -83077,12 +83129,14 @@ "slate-plain-serializer": { "version": "0.7.13", "resolved": "https://registry.npmjs.org/slate-plain-serializer/-/slate-plain-serializer-0.7.13.tgz", - "integrity": "sha512-TtrlaslxQBEMV0LYdf3s7VAbTxRPe1xaW10WNNGAzGA855/0RhkaHjKkQiRjHv5rvbRleVf7Nxr9fH+4uErfxQ==" + "integrity": "sha512-TtrlaslxQBEMV0LYdf3s7VAbTxRPe1xaW10WNNGAzGA855/0RhkaHjKkQiRjHv5rvbRleVf7Nxr9fH+4uErfxQ==", + "requires": {} }, "slate-prop-types": { "version": "0.5.44", "resolved": "https://registry.npmjs.org/slate-prop-types/-/slate-prop-types-0.5.44.tgz", - "integrity": "sha512-JS0iW7uaciE/W3ADuzeN1HOnSjncQhHPXJ65nZNQzB0DF7mXVmbwQKI6cmCo/xKni7XRJT0JbWSpXFhEdPiBUA==" + "integrity": "sha512-JS0iW7uaciE/W3ADuzeN1HOnSjncQhHPXJ65nZNQzB0DF7mXVmbwQKI6cmCo/xKni7XRJT0JbWSpXFhEdPiBUA==", + "requires": {} }, "slice-ansi": { "version": "3.0.0", @@ -83940,7 +83994,8 @@ "stylis-rule-sheet": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", + "requires": {} } } }, @@ -84005,7 +84060,8 @@ "stylis-rule-sheet": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stylis-rule-sheet/-/stylis-rule-sheet-0.0.10.tgz", - "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==" + "integrity": "sha512-nTbZoaqoBnmK+ptANthb10ZRZOGC+EmTLLUxeYIuHNkEKcmKgXX1XWKkUBT2Ac4es3NybooPe0SmvKdhKJZAuw==", + "requires": {} } } }, @@ -85724,19 +85780,22 @@ "use-callback-ref": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.2.4.tgz", - "integrity": "sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==" + "integrity": "sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==", + "requires": {} }, "use-composed-ref": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.2.1.tgz", "integrity": "sha512-6+X1FLlIcjvFMAeAD/hcxDT8tmyrWnbSPMU0EnxQuDLIxokuFzWliXBiYZuGIx+mrAMLBw0WFfCkaPw8ebzAhw==", - "dev": true + "dev": true, + "requires": {} }, "use-isomorphic-layout-effect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz", "integrity": "sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==", - "dev": true + "dev": true, + "requires": {} }, "use-latest": { "version": "1.2.0", @@ -86715,7 +86774,8 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz", "integrity": "sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg==", - "dev": true + "dev": true, + "requires": {} }, "webpack-hot-middleware": { "version": "2.25.1", @@ -86974,7 +87034,8 @@ "ws": { "version": "7.4.5", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz", - "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==" + "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==", + "requires": {} }, "xdg-basedir": { "version": "4.0.0", diff --git a/src/package.json b/src/package.json index a27c830bb..d8f6d2ba5 100644 --- a/src/package.json +++ b/src/package.json @@ -96,10 +96,11 @@ "graphql": "^15.4.0", "graphql-request": "^3.4.0", "graphql-tools": "^7.0.2", - "http-proxy-middleware": "^1.1.2", + "http-proxy-middleware": "^2.0.6", "isomorphic-unfetch": "^3.1.0", - "json-stable-stringify": "^1.0.2", "js-yaml": "^4.1.0", + "json-stable-stringify": "^1.0.2", + "just-kebab-case": "^4.2.0", "jwks-rsa": "^2.0.5", "jwt-decode": "^3.1.2", "keycloak-connect": "^17.0.1", diff --git a/src/server.ts b/src/server.ts index fa44b6009..5ad25937c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -242,6 +242,8 @@ const apps = [ new ApiHealthApp(state), new ApiOpenapiApp(), new MaintenanceApp(), + new ApiDSProxyApp({ url: process.env.SSR_API_ROOT }), + new ApiProxyApp({ gwaApiUrl: process.env.GWA_API_URL }), new ApiGraphqlWhitelistApp({ apiPath, apollo: { @@ -270,8 +272,6 @@ const apps = [ pages: pages, enableDefaultRoute: false, }), - new ApiDSProxyApp({ url: process.env.SSR_API_ROOT }), - new ApiProxyApp({ gwaApiUrl: process.env.GWA_API_URL }), new NextApp({ dir: 'nextapp' }), ]; diff --git a/src/services/identifiers.ts b/src/services/identifiers.ts index ab3f01f97..24af1f39b 100644 --- a/src/services/identifiers.ts +++ b/src/services/identifiers.ts @@ -11,11 +11,15 @@ export function isEnvironmentID(id: string): boolean { } export function newProductID(): string { - return uuidv4().replace(/-/g, '').toUpperCase().substr(0, 12); + return uuidv4().replace(/-/g, '').toUpperCase().substring(0, 12); } export function newApplicationID(): string { - return uuidv4().replace(/-/g, '').toUpperCase().substr(0, 11); + return uuidv4().replace(/-/g, '').toUpperCase().substring(0, 11); } export function newEnvironmentID(): string { - return uuidv4().replace(/-/g, '').toUpperCase().substr(0, 8); + return uuidv4().replace(/-/g, '').toUpperCase().substring(0, 8); +} + +export function newNamespaceID(): string { + return 'gw-' + uuidv4().replace(/-/g, '').toLowerCase().substring(0, 5); } diff --git a/src/services/keycloak/client-service.ts b/src/services/keycloak/client-service.ts index 0c8f1133b..353b44d0e 100644 --- a/src/services/keycloak/client-service.ts +++ b/src/services/keycloak/client-service.ts @@ -103,6 +103,11 @@ export class KeycloakClientService { return lkup[0]; } + public async deleteClient(id: string): Promise { + await this.kcAdminClient.clients.del({ id }); + logger.debug('[deleteClient] CID=%s SUCCESS', id); + } + public async regenerateSecret(id: string): Promise { const cred = await this.kcAdminClient.clients.generateNewClientSecret({ id, diff --git a/src/services/keycloak/group-service.ts b/src/services/keycloak/group-service.ts index db8b45b17..3e9127369 100644 --- a/src/services/keycloak/group-service.ts +++ b/src/services/keycloak/group-service.ts @@ -175,7 +175,7 @@ export class KeycloakGroupService { (group: GroupRepresentation) => group.name == groupName ).length == 0 ) { - logger.debug('[getGroup] MISSING %s', groupName); + logger.error('[getGroup] MISSING %s', groupName); return null; } else { logger.debug('[getGroup] FOUND %s', groupName); diff --git a/src/services/keycloak/namespace-details.ts b/src/services/keycloak/namespace-details.ts index 7823d4ad2..d7afeafd6 100644 --- a/src/services/keycloak/namespace-details.ts +++ b/src/services/keycloak/namespace-details.ts @@ -12,6 +12,7 @@ import { getOrgPoliciesForResource, } from '../../lists/extensions/Common'; import { GWAService } from '../gwaapi'; +import { strict as assert } from 'assert'; const logger = Logger('kc.nsdetails'); @@ -35,16 +36,20 @@ export async function getAllNamespaces(envCtx: EnvironmentContext) { const client = new GWAService(process.env.GWA_API_URL); const defaultSettings = await client.getDefaultNamespaceSettings(); - return await Promise.all( - nsList.map(async (nsdata: any) => { - return backfillGroupAttributes( - nsdata.name, - nsdata, - defaultSettings, - kcGroupService - ); + return ( + await Promise.all( + nsList.map(async (nsdata: any) => { + return backfillGroupAttributes( + nsdata.name, + nsdata, + defaultSettings, + kcGroupService + ); + }) + ).catch((err: any) => { + throw err; }) - ); + ).filter((x) => Boolean(x)); } export async function getKeycloakGroupApi( @@ -67,7 +72,10 @@ export async function backfillGroupAttributes( ): Promise { const nsPermissions = await kcGroupService.getGroup('ns', ns); + assert.strictEqual(Boolean(nsPermissions), true, 'Invalid namespace'); + transformSingleValueAttributes(nsPermissions.attributes, [ + 'description', 'perm-data-plane', 'perm-protected-ns', 'org', @@ -161,6 +169,7 @@ export async function getResource( .map((ns: ResourceSet) => ({ id: ns.id, name: ns.name, + displayName: ns.displayName, scopes: ns.resource_scopes, })) .pop(); diff --git a/src/services/keystone/types.ts b/src/services/keystone/types.ts index d34a32302..5d5de8201 100644 --- a/src/services/keystone/types.ts +++ b/src/services/keystone/types.ts @@ -5326,7 +5326,8 @@ export type MutationUpdateCurrentNamespaceArgs = { export type MutationCreateNamespaceArgs = { - namespace: Scalars['String']; + name?: Maybe; + displayName?: Maybe; }; @@ -5415,6 +5416,7 @@ export type Namespace = { __typename?: 'Namespace'; id?: Maybe; name: Scalars['String']; + displayName?: Maybe; scopes?: Maybe>>; prodEnvId?: Maybe; permDomains?: Maybe>>; @@ -5429,7 +5431,8 @@ export type Namespace = { }; export type NamespaceInput = { - name: Scalars['String']; + name?: Maybe; + displayName?: Maybe; }; /** A keystone list */ diff --git a/src/services/report/data/namespaces.ts b/src/services/report/data/namespaces.ts index c5d10c914..ab0e9b1fa 100644 --- a/src/services/report/data/namespaces.ts +++ b/src/services/report/data/namespaces.ts @@ -13,6 +13,7 @@ import { GWAService } from '../../gwaapi'; export interface ReportOfNamespaces { resource_id: string; name: string; + displayName?: string; permProtectedNs?: string; permDomains?: string[]; permDataPlane?: string; diff --git a/src/services/report/output/structure.ts b/src/services/report/output/structure.ts index bc289503c..1fa93e723 100644 --- a/src/services/report/output/structure.ts +++ b/src/services/report/output/structure.ts @@ -18,6 +18,11 @@ export const reportStructure: any = { key: 'name', width: 32, }, + { + header: 'Display Name', + key: 'displayName', + width: 32, + }, { header: 'Privileged', key: 'permProtectedNs', diff --git a/src/services/uma2/resource-registration-service.ts b/src/services/uma2/resource-registration-service.ts index 4fdb76b6a..eb7b20ae8 100644 --- a/src/services/uma2/resource-registration-service.ts +++ b/src/services/uma2/resource-registration-service.ts @@ -14,6 +14,7 @@ export interface ResourceSetQuery { scope?: string; first?: number; max?: number; + deep?: boolean; } export interface ResourceScope { @@ -27,6 +28,7 @@ export interface ResourceOwner { export interface ResourceSet { id: string; name: string; + displayName?: string; type: string; uris?: string[]; icon_uri?: string; @@ -37,6 +39,7 @@ export interface ResourceSet { export interface ResourceSetInput { name: string; + displayName?: string; type: string; uris?: string[]; icon_uri?: string; diff --git a/src/services/utils.ts b/src/services/utils.ts index 278f42367..ee8b8e195 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -57,7 +57,6 @@ export function regExprValidation( value: string, errorMessage: string ) { - const namespaceValidationRule = '^[a-z][a-z0-9-]{4,14}$'; const re = new RegExp(rule); assert.strictEqual(re.test(value), true, errorMessage); } diff --git a/src/services/workflow/client-shared-idp.ts b/src/services/workflow/client-shared-idp.ts index ac3c97138..984ee1b1e 100644 --- a/src/services/workflow/client-shared-idp.ts +++ b/src/services/workflow/client-shared-idp.ts @@ -96,10 +96,7 @@ async function addClientToSharedIdP( const environment = issuerEnvConfig.environment; - const clientId = - environment == 'prod' - ? `ap-${profileClientId}` - : `ap-${profileClientId}-${environment}`; + const clientId = genClientId(environment, profileClientId); // If there are any custom client Mappers, then include them const clientMappers: any[] = []; @@ -137,6 +134,77 @@ async function addClientToSharedIdP( }; } +/** + * When a CredentialIssuer is deleted where the inheritFrom is set + * then call this to delete the client ID on the shared IdP + * + * @param context + * @param credentialIssuerPK + */ +export async function DeleteClientsFromSharedIdP( + context: any, + profileClientId: string, + inheritFromIssuerPK: string +) { + // Find the credential issuer and based on its type, go do the appropriate action + const inheritFromIssuer: CredentialIssuer = await lookupCredentialIssuerById( + context, + inheritFromIssuerPK + ); + + assert.strictEqual( + inheritFromIssuer.isShared, + true, + 'Invalid IdP for Sharing' + ); + + const envConfigs = getAllIssuerEnvironmentConfigs(inheritFromIssuer); + + for (const issuerEnvConfig of envConfigs) { + await deleteClientFromSharedIdP(profileClientId, issuerEnvConfig); + } +} + +async function deleteClientFromSharedIdP( + profileClientId: string, + issuerEnvConfig: IssuerEnvironmentConfig +): Promise { + const openid = await getOpenidFromIssuer(issuerEnvConfig.issuerUrl); + + // token is NULL if 'iat' + // token is retrieved from doing a /token login using the provided client ID and secret if 'managed' + // issuer.initialAccessToken if 'iat' + const kctoksvc = new KeycloakTokenService(openid.token_endpoint); + + const token = + issuerEnvConfig.clientRegistration == 'anonymous' + ? null + : issuerEnvConfig.clientRegistration == 'managed' + ? await kctoksvc.getKeycloakSession( + issuerEnvConfig.clientId, + issuerEnvConfig.clientSecret + ) + : issuerEnvConfig.initialAccessToken; + + const environment = issuerEnvConfig.environment; + + const clientId = genClientId(environment, profileClientId); + + // Find the Client ID for the ProductEnvironment - that will be used to associated the clientRoles + + const cliApi = await new KeycloakClientService(issuerEnvConfig.issuerUrl); + await cliApi.login(issuerEnvConfig.clientId, issuerEnvConfig.clientSecret); + + const exists = await cliApi.isClient(clientId); + + if (exists) { + const client = await cliApi.findByClientId(clientId); + assert.strictEqual(client.clientId, clientId); + + await cliApi.deleteClient(client.id); + } +} + /** * Whenever the CredentialIssuer is updated, and inheritFrom is set, * then sync the Roles and Authz settings in the Client Registration diff --git a/src/services/workflow/delete-namespace.ts b/src/services/workflow/delete-namespace.ts index 0bfc895fd..0d63d9704 100644 --- a/src/services/workflow/delete-namespace.ts +++ b/src/services/workflow/delete-namespace.ts @@ -146,5 +146,9 @@ export const DeleteNamespace = async ( await deleteRecords(context, 'Product', { namespace: ns }, true, ['id']); + await deleteRecords(context, 'CredentialIssuer', { namespace: ns }, true, [ + 'id', + ]); + await updateActivity(context.sudo(), activity.id, 'success', undefined); }; diff --git a/src/services/workflow/get-namespaces.ts b/src/services/workflow/get-namespaces.ts index d43a83ac6..4132462b9 100644 --- a/src/services/workflow/get-namespaces.ts +++ b/src/services/workflow/get-namespaces.ts @@ -59,6 +59,7 @@ export async function getMyNamespaces( return namespaces.map((ns: ResourceSet) => ({ id: ns.id, name: ns.name, + displayName: ns.displayName, scopes: ns.resource_scopes, prodEnvId: envCtx.prodEnv.id, })); @@ -269,6 +270,7 @@ export interface ResourceServerContext { export interface NamespaceSummary { id: string; name: string; + displayName: string; scopes: ResourceScope[]; prodEnvId: string; } diff --git a/src/services/workflow/index.ts b/src/services/workflow/index.ts index fb2335ac4..bb5b8627b 100644 --- a/src/services/workflow/index.ts +++ b/src/services/workflow/index.ts @@ -2,7 +2,11 @@ export { Apply } from './apply'; export { CreateServiceAccount } from './create-service-account'; -export { addClientsToSharedIdP, syncSharedIdp } from './client-shared-idp'; +export { + addClientsToSharedIdP, + DeleteClientsFromSharedIdP, + syncSharedIdp, +} from './client-shared-idp'; export { allConsumerGroupLabels, diff --git a/src/test/integrated/uma2/resource.ts b/src/test/integrated/uma2/resource.ts index 170dc1bda..f7f81d3bc 100644 --- a/src/test/integrated/uma2/resource.ts +++ b/src/test/integrated/uma2/resource.ts @@ -32,15 +32,18 @@ import { token ); - if (false) { + if (true) { await svc.createResourceSet({ name: 'sample2', - type: 'organization', + displayName: 'Sample Number 2', + type: 'integration_test', resource_scopes: ['Organization.Manage'], ownerManagedAccess: true, }); } - console.log(await svc.listResources({ type: 'organization' })); + console.log( + await svc.listResources({ type: 'integration_test', deep: true }) + ); - console.log(await svc.findResourceByName('sample33')); + console.log(await svc.findResourceByName('sample2')); })(); diff --git a/src/test/services/identifiers.test.js b/src/test/services/identifiers.test.js new file mode 100644 index 000000000..c288d9c84 --- /dev/null +++ b/src/test/services/identifiers.test.js @@ -0,0 +1,11 @@ +import fetch from 'node-fetch'; +import { newNamespaceID } from '../../services/identifiers'; + +describe('Identifiers', function () { + it('it should be a valid namespace', async function () { + const result = newNamespaceID(); + expect(result).toHaveLength(8); + expect(result.startsWith('gw-')).toBeTruthy(); + console.log(result); + }); +}); diff --git a/src/tsoa-v2.json b/src/tsoa-v2.json index f121dff89..5046af3f3 100644 --- a/src/tsoa-v2.json +++ b/src/tsoa-v2.json @@ -24,6 +24,11 @@ "description": "Authz Portal Login", "scheme": "bearer", "bearerFormat": "JWT" + }, + "openid": { + "type": "openIdConnect", + "description": "OIDC Login", + "openIdConnectUrl": "https://well_known_endpoint" } }, "tags": [