diff --git a/.github/workflows/e2e-keycloak.yaml b/.github/workflows/e2e-keycloak.yaml
index 2e767d376..152d59273 100644
--- a/.github/workflows/e2e-keycloak.yaml
+++ b/.github/workflows/e2e-keycloak.yaml
@@ -35,14 +35,25 @@ jobs:
summary-title: 'Pre-reqs'
wait-on: https://bcgov.github.io/sso-requests-sandbox/
wait-on-timeout: 120
- record: true
install-command: yarn
working-directory: app
spec: |
cypress/e2e/**/integration-990-deleteAllIntegrations.cy.ts
browser: electron
- # project: ./e2e
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
idp-stopper:
runs-on: ubuntu-latest
@@ -60,7 +71,6 @@ jobs:
continue-on-error: false
with:
summary-title: 'E2E tests'
- record: true
wait-on: 'https://bcgov.github.io/sso-requests-sandbox/'
wait-on-timeout: 360
install-command: yarn
@@ -68,7 +78,20 @@ jobs:
spec: |
cypress/e2e/external/idpstopper-*.cy.ts
browser: electron
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
search-users:
runs-on: ubuntu-latest
@@ -83,7 +106,6 @@ jobs:
continue-on-error: false
with:
summary-title: 'E2E tests'
- record: true
wait-on: 'https://bcgov.github.io/sso-requests-sandbox/'
wait-on-timeout: 360
install-command: yarn
@@ -91,7 +113,20 @@ jobs:
spec: |
cypress/e2e/external/search-users.cy.ts
browser: electron
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
roles-tests:
runs-on: ubuntu-latest
@@ -106,7 +141,6 @@ jobs:
continue-on-error: false
with:
summary-title: 'E2E tests'
- record: true
wait-on: 'https://bcgov.github.io/sso-requests-sandbox/'
wait-on-timeout: 360
install-command: yarn
@@ -114,7 +148,20 @@ jobs:
spec: |
cypress/e2e/external/integration-roles.cy.ts
browser: electron
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
integration-tests:
runs-on: ubuntu-latest
@@ -129,7 +176,6 @@ jobs:
continue-on-error: false
with:
summary-title: 'E2E tests'
- record: true
wait-on: 'https://bcgov.github.io/sso-requests-sandbox/'
wait-on-timeout: 360
install-command: yarn
@@ -137,4 +183,17 @@ jobs:
spec: |
cypress/e2e/ci/integrations-crud.cy.ts
browser: electron
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml
index 0d6de0ba1..4ff512b8d 100644
--- a/.github/workflows/e2e.yaml
+++ b/.github/workflows/e2e.yaml
@@ -16,7 +16,6 @@ env:
CYPRESS_guid: ${{ secrets.CYPRESS_GUID }}
CYPRESS_loginproxy: ${{ secrets.CYPRESS_LOGINPROXY }}
CYPRESS_siteminder: ${{ secrets.CYPRESS_SITEMINDER }}
- CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CYPRESS_host: 'http://localhost:3000'
CYPRESS_smoketest: true
@@ -39,14 +38,25 @@ jobs:
continue-on-error: false
with:
summary-title: 'E2E tests'
- record: true
wait-on: 'http://localhost:3000'
- parallel: true
wait-on-timeout: 360
install-command: yarn
working-directory: app
spec: |
- cypress/e2e/smoke/smoke-10-brokenlinks.cy.ts
+ cypress/e2e/ci/smoke-10-brokenlinks.cy.ts
cypress/e2e/ci/*.cy.ts
browser: electron
- ci-build-id: ${{ github.event.number }}
+
+ - uses: actions/upload-artifact@v4
+ if: failure()
+ with:
+ name: cypress-screenshots
+ path: app/cypress/screenshots/**/*
+ if-no-files-found: ignore
+
+ # - uses: actions/upload-artifact@v4
+ # if: failure()
+ # with:
+ # name: cypress-videos
+ # path: app/cypress/videos/**/*
+ # if-no-files-found: ignore
diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml
index 9e1997b33..d2de6b827 100644
--- a/.github/workflows/terraform.yml
+++ b/.github/workflows/terraform.yml
@@ -64,7 +64,8 @@ jobs:
UPTIME_STATUS_DOMAIN_NAME=status.sandbox.loginproxy.gov.bc.ca
AWS_ECR_URI=${{secrets.DEV_AWS_ECR_URI}}
INCLUDE_DIGITAL_CREDENTIAL=true
- INSTALL_SSO_CSS_GRAFANA=true
+ INSTALL_GRAFANA=true
+ INSTALL_REDIS=true
INCLUDE_BC_SERVICES_CARD=true
ALLOW_BC_SERVICES_CARD_PROD=true
@@ -116,7 +117,8 @@ jobs:
UPTIME_STATUS_DOMAIN_NAME=status.sandbox.loginproxy.gov.bc.ca
AWS_ECR_URI=${{secrets.DEV_AWS_ECR_URI}}
INCLUDE_DIGITAL_CREDENTIAL=true
- INSTALL_SSO_CSS_GRAFANA=false
+ INSTALL_GRAFANA=false
+ INSTALL_REDIS=true
INCLUDE_BC_SERVICES_CARD=true
ALLOW_BC_SERVICES_CARD_PROD=false
@@ -167,7 +169,8 @@ jobs:
CUSTOM_DOMAIN_NAME=api.loginproxy.gov.bc.ca
UPTIME_STATUS_DOMAIN_NAME=status.loginproxy.gov.bc.ca
AWS_ECR_URI=${{secrets.PROD_AWS_ECR_URI}}
- INSTALL_SSO_CSS_GRAFANA=true
+ INSTALL_GRAFANA=true
+ INSTALL_REDIS=true
INCLUDE_DIGITAL_CREDENTIAL=true
INCLUDE_BC_SERVICES_CARD=true
@@ -309,7 +312,8 @@ jobs:
ms_graph_api_authority="${{secrets.MS_GRAPH_API_AUTHORITY}}"
ms_graph_api_client_id="${{secrets.MS_GRAPH_API_CLIENT_ID}}"
ms_graph_api_client_secret="${{secrets.MS_GRAPH_API_CLIENT_SECRET}}"
- install_sso_css_grafana="${{ env.INSTALL_SSO_CSS_GRAFANA == 'true' && 1 || 0 }}"
+ install_grafana="${{ env.INSTALL_GRAFANA == 'true' && 1 || 0 }}"
+ install_redis="${{ env.INSTALL_REDIS == 'true' && 1 || 0 }}"
allow_bc_services_card_prod="${{env.ALLOW_BC_SERVICES_CARD_PROD}}"
include_bc_services_card="${{env.INCLUDE_BC_SERVICES_CARD}}"
@@ -319,7 +323,6 @@ jobs:
bcsc_registration_base_url_dev="${{env.BCSC_REGISTRATION_BASE_URL_DEV}}"
bcsc_registration_base_url_test="${{env.BCSC_REGISTRATION_BASE_URL_TEST}}"
bcsc_registration_base_url_prod="${{env.BCSC_REGISTRATION_BASE_URL_PROD}}"
-
EOF
working-directory: ./terraform
diff --git a/.tool-versions b/.tool-versions
index bd9cd9dd9..973e8d441 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -7,3 +7,4 @@ terraform-docs 0.12.1
tflint 0.41.0
k6 0.34.1
helm 3.10.2
+bun 1.1.34
diff --git a/Dockerfile b/Dockerfile
index 42eebb256..5a42977a7 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,6 @@
-FROM node:16.14.2-slim
+FROM node:20.17.0-slim
+RUN npm i -g bun
RUN apt-get update && apt-get install curl make -y \
&& apt-get install libsqlite3-dev bzip2 icu-devtools uuid-dev -y
diff --git a/Dockerfile.tf-modules b/Dockerfile.tf-modules
index 1e4933186..4b3ac9d94 100644
--- a/Dockerfile.tf-modules
+++ b/Dockerfile.tf-modules
@@ -1,5 +1,7 @@
-FROM docker.io/hashicorp/terraform:latest
+FROM docker.io/hashicorp/terraform:1.1.4
+RUN apk update
+RUN apk upgrade
RUN apk add --no-cache curl
# Set working directory
diff --git a/app/cypress.config.ts b/app/cypress.config.ts
index 34d1b8107..e64fa0bce 100644
--- a/app/cypress.config.ts
+++ b/app/cypress.config.ts
@@ -20,7 +20,7 @@ export default defineConfig({
json: true,
},
e2e: {
- baseUrl: 'http://localhost:3000',
+ baseUrl: 'https://bcgov.github.io/sso-requests-sandbox',
projectId: 'gctfmh',
setupNodeEvents(on, config) {
on('before:browser:launch', (browser, launchOptions) => {
diff --git a/app/jest/ssoDashboard.test.tsx b/app/jest/ssoDashboard.test.tsx
index 9b95815f7..d12a354c8 100644
--- a/app/jest/ssoDashboard.test.tsx
+++ b/app/jest/ssoDashboard.test.tsx
@@ -3,6 +3,8 @@ import AdminDashboard from 'pages/admin-dashboard';
import { Integration } from 'interfaces/Request';
import { sampleRequest } from './samples/integrations';
import { deleteRequest, updateRequestMetadata, updateRequest, restoreRequest, getRequestAll } from 'services/request';
+import BcServicesCardTabContent from 'page-partials/admin-dashboard/AdminTabs/BcServicesCardTabContent';
+
import { getCompositeClientRoles } from '@app/services/keycloak';
import { debug } from 'jest-preview';
@@ -479,4 +481,31 @@ describe('SSO Dashboard', () => {
expect(screen.queryByText('Remove Service Account')).not.toBeInTheDocument();
});
+
+ it('BCSC Tab can approve submitted bcsc integrations', () => {
+ const bcServicesCardIntegration: Integration = {
+ ...sampleRequest,
+ devIdps: ['bcservicescard'],
+ status: 'applied',
+ publicAccess: false,
+ };
+
+ render(
Cannot approve deleted/archived integrations.
+ > + ); + } else if (canApproveProd) { content = ( <>{`To begin the ${displayType} integration in production, Click Below.`}
diff --git a/app/page-partials/my-dashboard/IntegrationInfoTabs/LogsPanel.tsx b/app/page-partials/my-dashboard/IntegrationInfoTabs/LogsPanel.tsx index 9e90d46b8..0d35d184c 100644 --- a/app/page-partials/my-dashboard/IntegrationInfoTabs/LogsPanel.tsx +++ b/app/page-partials/my-dashboard/IntegrationInfoTabs/LogsPanel.tsx @@ -99,11 +99,10 @@ interface Props { * @param filename Name to give to the downloaded file * @param dataObjToWrite The data to include in the file */ -const saveTemplateAsFile = (filename: string, dataObjToWrite: any) => { - const blob = new Blob([JSON.stringify(dataObjToWrite)], { type: 'text/json' }); +const saveTemplateAsFile = (filename: string, dataObjToWrite: Blob) => { const link = document.createElement('a'); link.download = filename; - link.href = window.URL.createObjectURL(blob); + link.href = window.URL.createObjectURL(dataObjToWrite); link.dataset.downloadurl = ['text/json', link.download, link.href].join(':'); const evt = new MouseEvent('click', { @@ -174,6 +173,8 @@ const LogsPanel = ({ integration, alert }: Props) => { }; const handleFileProgress = (progressEvent: AxiosProgressEvent) => { + // Ignore progress indicator if unsupported in browser. Some browsers ignore content-length for zipped responses. + if (progressEvent.total === undefined || progressEvent.loaded === undefined) return; const percentComplete = Math.floor((progressEvent.loaded / Number(progressEvent.total)) * 100); if (percentComplete !== fileProgress) { setFileProgress(percentComplete); @@ -222,11 +223,13 @@ const LogsPanel = ({ integration, alert }: Props) => { if (err) { // Ignore error if request cancelled if (err.code === 'ERR_CANCELED') return; + let content = 'Error fetching logs.'; + if (err?.status === 429) content = 'Too many requests'; alert.show({ variant: 'danger', fadeOut: 10000, closable: true, - content: err?.response?.data?.message ?? 'Error fetching logs.', + content, }); } else { alert.show({ @@ -235,10 +238,9 @@ const LogsPanel = ({ integration, alert }: Props) => { closable: true, content: result?.message ?? 'Downloaded logs.', }); - const resultJSON = await result.text(); saveTemplateAsFile( `${integration.clientId}-${fromDate.toLocaleString()}-${toDate.toLocaleString()}.json`, - JSON.parse(resultJSON), + result.data, ); surveyContext?.setShowSurvey(true, 'downloadLogs'); } diff --git a/app/page-partials/my-dashboard/TeamInfoTabs/ServiceAccountsList.tsx b/app/page-partials/my-dashboard/TeamInfoTabs/ServiceAccountsList.tsx index e25a9bb26..af1ca7f17 100644 --- a/app/page-partials/my-dashboard/TeamInfoTabs/ServiceAccountsList.tsx +++ b/app/page-partials/my-dashboard/TeamInfoTabs/ServiceAccountsList.tsx @@ -111,7 +111,7 @@ function ServiceAccountsList({ data = data || {}; const text = { - tokenUrl: `${data['auth-server-url']}/realms/${data.realm}/protocol/openid-connect/token`, + tokenUrl: `${data['token-url']}`, clientId: `${data.resource}`, clientSecret: `${data.credentials?.secret}`, }; diff --git a/app/services/grafana.ts b/app/services/grafana.ts index c79e33e51..1b5c414bc 100644 --- a/app/services/grafana.ts +++ b/app/services/grafana.ts @@ -1,4 +1,4 @@ -import { AxiosProgressEvent } from 'axios'; +import { AxiosError, AxiosProgressEvent } from 'axios'; import { instance } from './axios'; export const getMetrics = async (id: number, env: string, fromDate?: string, toDate?: string) => { @@ -21,18 +21,16 @@ export const getLogs = async ( toDate: Date, onProgress: (progressEvent: AxiosProgressEvent) => void, controller?: AbortController, -) => { +): Promise<[{ data: Blob; message: string }, null] | [null, AxiosErrorThe IDIR user with the idir username test-user has an inactive guid in our system. They have been removed from - client test-client and associated roles test-role + client test-client in the dev environment and associated roles test-role
This user was also a Team Admin for your integration, to help ensure a transition to the appropriate admin, please
diff --git a/lambda/__tests__/helpers/common-mocks.js b/lambda/__tests__/helpers/common-mocks.js
new file mode 100644
index 000000000..f7c4fce91
--- /dev/null
+++ b/lambda/__tests__/helpers/common-mocks.js
@@ -0,0 +1,15 @@
+jest.mock('../../app/src/authenticate');
+
+jest.mock('../../app/src/utils/rate-limiters', () => {
+ return {
+ logsRateLimiter: jest.fn((req, res, next) => {
+ next();
+ }),
+ };
+});
+
+jest.mock('../../shared/utils/ches', () => {
+ return {
+ sendEmail: jest.fn(),
+ };
+});
diff --git a/lambda/__tests__/helpers/modules/integrations.ts b/lambda/__tests__/helpers/modules/integrations.ts
index e0ede4362..4ecb2b612 100644
--- a/lambda/__tests__/helpers/modules/integrations.ts
+++ b/lambda/__tests__/helpers/modules/integrations.ts
@@ -73,8 +73,9 @@ export const createRequestQueueItem = async (
requestData: RequestData,
action: QUEUE_ACTION,
ageSeconds?: number,
+ attempts: number = 0,
) => {
- const queueItem: any = { type: 'request', action, requestId, request: requestData };
+ const queueItem: any = { type: 'request', action, requestId, request: requestData, attempts };
if (ageSeconds) {
const currentTime = new Date();
const secondsAgoTime = currentTime.getTime() - ageSeconds * 1000;
diff --git a/lambda/__tests__/jest.setup.js b/lambda/__tests__/jest.setup.js
index cfc4d58f5..77c4e0ddb 100644
--- a/lambda/__tests__/jest.setup.js
+++ b/lambda/__tests__/jest.setup.js
@@ -1,5 +1,7 @@
const { createImportSpecifier } = require('typescript');
const { sequelize, models } = require('../shared/sequelize/models/models');
+require('./helpers/common-mocks');
+
process.env.GH_SECRET = 'test';
process.env.API_AUTH_SECRET = 'test';
process.env.NODE_ENV = 'development';
diff --git a/lambda/app/package-lock.json b/lambda/app/package-lock.json
index 2698efa8e..63ade22e5 100644
--- a/lambda/app/package-lock.json
+++ b/lambda/app/package-lock.json
@@ -10,16 +10,19 @@
"license": "Apache-2.0",
"dependencies": {
"@azure/msal-node": "^2.6.4",
- "@keycloak/keycloak-admin-client": "18.0.1",
+ "@keycloak/keycloak-admin-client": "^24.0.5",
"cors": "^2.8.5",
"deep-diff": "^1.0.2",
"express": "^4.21.0",
+ "express-rate-limit": "^7.4.1",
"http-errors": "^2.0.0",
+ "ioredis": "^5.4.1",
"json-schema": "^0.4.0",
"jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.5",
"jws": "^4.0.0",
"lambda-api-router": "^1.0.6",
+ "rate-limit-redis": "^4.2.0",
"react": "^18.1.0",
"react-jsonschema-form": "^1.8.1",
"serverless-http": "^3.0.3",
@@ -68,19 +71,24 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@ioredis/commands": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
+ "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==",
+ "license": "MIT"
+ },
"node_modules/@keycloak/keycloak-admin-client": {
- "version": "18.0.1",
- "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-18.0.1.tgz",
- "integrity": "sha512-gmKjWbeLv5FvwlqDl+f6ZPFsgCOjFObYOPpmXj+v9itQd1PrSq6d1l1b7xIYyhAZnZ1pkKnDmzqtVrk+XrkAEg==",
+ "version": "24.0.5",
+ "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-24.0.5.tgz",
+ "integrity": "sha512-SXDVtQ3ov7GQbxXq51Uq8lzhwzQwNg6XiY50ZA9whuUe2t/0zPT4Zd/LcULcjweIjSNWWgfbDyN1E3yRSL8Qqw==",
"license": "Apache-2.0",
"dependencies": {
- "axios": "^0.26.1",
- "camelize-ts": "^1.0.8",
- "keycloak-js": "^17.0.1",
- "lodash": "^4.17.21",
- "query-string": "^7.0.1",
- "url-join": "^4.0.0",
- "url-template": "^2.0.8"
+ "camelize-ts": "^3.0.0",
+ "url-join": "^5.0.0",
+ "url-template": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
}
},
"node_modules/@types/body-parser": {
@@ -242,35 +250,6 @@
"safer-buffer": "^2.1.0"
}
},
- "node_modules/axios": {
- "version": "0.26.1",
- "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
- "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.14.8"
- }
- },
- "node_modules/base64-js": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
- "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
@@ -339,10 +318,22 @@
}
},
"node_modules/camelize-ts": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-1.0.9.tgz",
- "integrity": "sha512-ePOW3V2qrQ0qtRlcTM6Qe3nXremdydIwsMKI1Vl2NBGM0tOo8n2xzJ7YOQpV1GIKHhs3p+F40ThI8/DoYWbYKQ==",
- "license": "MIT"
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-3.0.0.tgz",
+ "integrity": "sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ }
},
"node_modules/content-disposition": {
"version": "0.5.4",
@@ -411,15 +402,6 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
- "node_modules/decode-uri-component": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
- "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10"
- }
- },
"node_modules/deep-diff": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz",
@@ -442,6 +424,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -570,6 +561,21 @@
"node": ">= 0.10.0"
}
},
+ "node_modules/express-rate-limit": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz",
+ "integrity": "sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": "4 || 5 || ^5.0.0-beta.1"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -582,15 +588,6 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT"
},
- "node_modules/filter-obj": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
- "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/finalhandler": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
@@ -608,26 +605,6 @@
"node": ">= 0.8"
}
},
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
"node_modules/forwarded": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
@@ -780,6 +757,47 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
+ "node_modules/ioredis": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz",
+ "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==",
+ "license": "MIT",
+ "dependencies": {
+ "@ioredis/commands": "^1.1.1",
+ "cluster-key-slot": "^1.1.0",
+ "debug": "^4.3.4",
+ "denque": "^2.1.0",
+ "lodash.defaults": "^4.2.0",
+ "lodash.isarguments": "^3.1.0",
+ "redis-errors": "^1.2.0",
+ "redis-parser": "^3.0.0",
+ "standard-as-callback": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=12.22.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/ioredis"
+ }
+ },
+ "node_modules/ioredis/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -789,12 +807,6 @@
"node": ">= 0.10"
}
},
- "node_modules/js-sha256": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz",
- "integrity": "sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==",
- "license": "MIT"
- },
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -887,16 +899,6 @@
"safe-buffer": "^5.0.1"
}
},
- "node_modules/keycloak-js": {
- "version": "17.0.1",
- "resolved": "https://registry.npmjs.org/keycloak-js/-/keycloak-js-17.0.1.tgz",
- "integrity": "sha512-mbLBSoogCBX5VYeKCdEz8BaRWVL9twzSqArRU3Mo3Z7vEO1mghGZJ5IzREfiMEi7kTUZtk5i9mu+Yc0koGkK6g==",
- "license": "Apache-2.0",
- "dependencies": {
- "base64-js": "^1.5.1",
- "js-sha256": "^0.9.0"
- }
- },
"node_modules/lambda-api-router": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/lambda-api-router/-/lambda-api-router-1.0.6.tgz",
@@ -920,12 +922,24 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"license": "MIT"
},
+ "node_modules/lodash.defaults": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
+ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
+ "license": "MIT"
+ },
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
"license": "MIT"
},
+ "node_modules/lodash.isarguments": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
+ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
+ "license": "MIT"
+ },
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
@@ -1164,24 +1178,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/query-string": {
- "version": "7.1.3",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
- "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
- "license": "MIT",
- "dependencies": {
- "decode-uri-component": "^0.2.2",
- "filter-obj": "^1.1.0",
- "split-on-first": "^1.0.0",
- "strict-uri-encode": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -1190,6 +1186,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/rate-limit-redis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.0.tgz",
+ "integrity": "sha512-wV450NQyKC24NmPosJb2131RoczLdfIJdKCReNwtVpm5998U8SgKrAZrIHaN/NfQgqOHaan8Uq++B4sa5REwjA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "peerDependencies": {
+ "express-rate-limit": ">= 6"
+ }
+ },
"node_modules/raw-body": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
@@ -1251,6 +1259,27 @@
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
"license": "MIT"
},
+ "node_modules/redis-errors": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
+ "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/redis-parser": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
+ "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
+ "license": "MIT",
+ "dependencies": {
+ "redis-errors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/regenerator-runtime": {
"version": "0.13.10",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz",
@@ -1402,14 +1431,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/split-on-first": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
- "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
+ "node_modules/standard-as-callback": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
+ "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
+ "license": "MIT"
},
"node_modules/statuses": {
"version": "2.0.1",
@@ -1420,15 +1446,6 @@
"node": ">= 0.8"
}
},
- "node_modules/strict-uri-encode": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
- "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -1468,16 +1485,22 @@
}
},
"node_modules/url-join": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
- "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
- "license": "MIT"
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
+ "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
},
"node_modules/url-template": {
- "version": "2.0.8",
- "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz",
- "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==",
- "license": "BSD"
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/url-template/-/url-template-3.1.1.tgz",
+ "integrity": "sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
},
"node_modules/utils-merge": {
"version": "1.0.1",
diff --git a/lambda/app/package.json b/lambda/app/package.json
index bd512a4a5..6e1496e3e 100644
--- a/lambda/app/package.json
+++ b/lambda/app/package.json
@@ -18,16 +18,19 @@
},
"dependencies": {
"@azure/msal-node": "^2.6.4",
- "@keycloak/keycloak-admin-client": "18.0.1",
+ "@keycloak/keycloak-admin-client": "^24.0.5",
"cors": "^2.8.5",
"deep-diff": "^1.0.2",
"express": "^4.21.0",
+ "express-rate-limit": "^7.4.1",
"http-errors": "^2.0.0",
+ "ioredis": "^5.4.1",
"json-schema": "^0.4.0",
"jsonwebtoken": "^9.0.2",
"jwk-to-pem": "^2.0.5",
"jws": "^4.0.0",
"lambda-api-router": "^1.0.6",
+ "rate-limit-redis": "^4.2.0",
"react": "^18.1.0",
"react-jsonschema-form": "^1.8.1",
"serverless-http": "^3.0.3",
diff --git a/lambda/app/src/controllers/logs.ts b/lambda/app/src/controllers/logs.ts
index ea02e38b0..6bb783d8c 100644
--- a/lambda/app/src/controllers/logs.ts
+++ b/lambda/app/src/controllers/logs.ts
@@ -10,20 +10,21 @@ const MAX_DAYS = 3;
const allowedEnvs = ['dev', 'test', 'prod'];
-export const fetchLogs = async (session: Session, env: string, id: number, start: string, end: string) => {
- // Check user owns requested logs
- const userRequest = await getAllowedRequest(session, id);
- if (!userRequest) return { status: 401, message: "You are not authorized to view this integration's logs" };
-
- const { clientId } = userRequest;
- // Validate user supplied inputs
- const hasRequiredQueryParams = start && end && env;
+export const fetchLogs = async (
+ env: string,
+ clientId: string,
+ requestId: string,
+ start: string,
+ end: string,
+ userId?: string,
+) => {
+ const hasRequiredParams = start && end && env;
- if (!hasRequiredQueryParams) {
- return { status: 400, message: 'Not all query params sent. Please include start, end and env.' };
+ if (!hasRequiredParams) {
+ return { status: 400, message: 'Not all parameters sent. Please include start, end and env.' };
}
if (!allowedEnvs.includes(env)) {
- return { status: 400, message: `The env query param must be one of ${allowedEnvs.join(', ')}.` };
+ return { status: 400, message: `The env parameter must be one of ${allowedEnvs.join(', ')}.` };
}
const unixStartTime = new Date(start).getTime();
@@ -38,13 +39,17 @@ export const fetchLogs = async (session: Session, env: string, id: number, start
};
}
+ if (unixStartTime > unixEndTime) {
+ return { status: 400, message: `End date must be later than start date.` };
+ }
+
if (unixEndTime - unixStartTime > MAX_DAYS * 60 * 60 * 24 * 1000) {
return { status: 400, message: `Date range must be less ${MAX_DAYS} days.` };
}
const eventMeta = {
- requestId: userRequest.id,
- idirUserid: session.idir_userid,
+ requestId,
+ idirUserid: userId,
details: {
environment: env,
clientId,
diff --git a/lambda/app/src/controllers/roles.ts b/lambda/app/src/controllers/roles.ts
index cc70e652d..db6d46d44 100644
--- a/lambda/app/src/controllers/roles.ts
+++ b/lambda/app/src/controllers/roles.ts
@@ -11,7 +11,7 @@ import {
getCompositeClientRoles,
} from '../keycloak/users';
import { models } from '@lambda-shared/sequelize/models/models';
-import { destroyRequestRole, updateCompositeRoles } from '@lambda-app/queries/roles';
+import { destroyRequestRole, createCompositeRolesDB } from '@lambda-app/queries/roles';
import createHttpError from 'http-errors';
import { isAdmin } from '@lambda-app/utils/helpers';
import { Session } from '@lambda-shared/interfaces';
@@ -129,8 +129,7 @@ export const setCompositeRoles = async (
compositeRoleNames,
});
- await updateCompositeRoles(result?.name, result?.composites, integration?.id, environment);
-
+ await createCompositeRolesDB(result?.name, result?.composites, integration?.id, environment);
return result;
};
diff --git a/lambda/app/src/controllers/user.ts b/lambda/app/src/controllers/user.ts
index 45aab4c06..ae14bf513 100644
--- a/lambda/app/src/controllers/user.ts
+++ b/lambda/app/src/controllers/user.ts
@@ -174,38 +174,48 @@ export const isAllowedToManageRoles = async (session: Session, integrationId: nu
};
export const deleteStaleUsers = async (
- user: UserRepresentation & { clientData: { client: string; roles: string[] }[] },
+ user: UserRepresentation & { clientData: { client: string; roles: string[] }[]; env: string },
) => {
try {
+ const deletedFromProduction = user.env === 'prod';
const userHadRoles = user?.clientData && user?.clientData?.length > 0;
// Send formatted email with roles information to all team members if the deleted user had roles.
if (userHadRoles) {
- user.clientData.map(async (cl: { client: string; roles: string[] }) => {
- const integration = await models.request.findOne({
- where: {
- clientId: cl.client,
- },
- raw: true,
- });
- if (integration?.teamId) {
- const userEmails = await getAllEmailsOfTeam(integration.teamId);
- let isTeamAdmin = false;
- userEmails.map((u: any) => {
- if (u.idir_email === user.email && u.role === 'admin') {
- isTeamAdmin = true;
- }
- });
- await sendTemplate(EMAILS.DELETE_INACTIVE_IDIR_USER, {
- teamId: integration.teamId,
- username: user.attributes.idir_username || user.username,
- clientId: cl.client,
- roles: cl.roles,
- teamAdmin: isTeamAdmin,
+ await Promise.all(
+ user.clientData.map(async (cl: { client: string; roles: string[] }) => {
+ const integration = await models.request.findOne({
+ where: {
+ clientId: cl.client,
+ },
+ raw: true,
});
- }
- });
+ if (integration?.teamId && !integration.archived) {
+ const userEmails = await getAllEmailsOfTeam(integration.teamId);
+ let isTeamAdmin = false;
+ // Only production users affect team management
+ if (deletedFromProduction) {
+ userEmails.map((u) => {
+ if (u.idir_email === user.email && u.role === 'admin') {
+ isTeamAdmin = true;
+ }
+ });
+ }
+ await sendTemplate(EMAILS.DELETE_INACTIVE_IDIR_USER, {
+ teamId: integration.teamId,
+ username: user.attributes.idir_username || user.username,
+ clientId: cl.client,
+ roles: cl.roles,
+ env: user.env,
+ teamAdmin: isTeamAdmin,
+ });
+ }
+ }),
+ );
}
+ // User management handling only applies to production user deletions.
+ if (!deletedFromProduction) return true;
+
if (!user.attributes.idir_user_guid) throw new createHttpError.BadRequest('user guid is required');
const existingUser = await models.user.findOne({ where: { idir_userid: user.attributes.idir_user_guid } });
@@ -254,13 +264,14 @@ export const deleteStaleUsers = async (
rqst.userId = ssoUser.id;
await rqst.save();
// Notification was already sent above if roles were included.
- if (!userHadRoles) {
+ if (!userHadRoles && !rqst.archived) {
await sendTemplate(EMAILS.DELETE_INACTIVE_IDIR_USER, {
teamId: rqst.teamId,
username: user.attributes.idir_username || user.username,
clientId: rqst.id,
teamAdmin: team.role === 'admin',
roles: [],
+ env: user.env,
});
}
}
diff --git a/lambda/app/src/main.ts b/lambda/app/src/main.ts
index ac2a27c0e..eeb4668a3 100644
--- a/lambda/app/src/main.ts
+++ b/lambda/app/src/main.ts
@@ -28,6 +28,7 @@ router.use(
origin: process.env.LOCAL_DEV === 'true' ? '*' : 'https://bcgov.github.io',
methods: ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
+ exposedHeaders: ['X-Message'],
credentials: true,
}),
);
diff --git a/lambda/app/src/queries/roles.ts b/lambda/app/src/queries/roles.ts
index 311103a72..99916a88c 100644
--- a/lambda/app/src/queries/roles.ts
+++ b/lambda/app/src/queries/roles.ts
@@ -14,21 +14,26 @@ export const getRolesWithEnvironments = async (integrationId: number) => {
return results;
};
-export const updateCompositeRoles = async (
+export const createCompositeRolesDB = async (
roleName: string,
compositeRoleNames: string[],
integrationId: number,
environment: string,
) => {
- const dbRole = await models.requestRole.findOne({
+ let dbRole = await models.requestRole.findOne({
where: {
name: roleName,
requestId: integrationId,
environment: environment,
},
});
+
if (!dbRole) {
- throw new createHttpError.NotFound(`role ${roleName} not found`);
+ dbRole = await models.requestRole.create({
+ name: roleName,
+ environment: environment,
+ requestId: integrationId,
+ });
}
const dbCompositeRoles = await models.requestRole.findAll({
where: {
@@ -40,16 +45,52 @@ export const updateCompositeRoles = async (
},
raw: true,
});
- if (dbCompositeRoles.length > 0) {
- dbRole.composite = true;
- dbRole.compositeRoles = dbCompositeRoles.map((cr) => cr.id);
- } else {
- dbRole.composite = false;
- dbRole.compositeRoles = [];
+ for (const compRole of compositeRoleNames) {
+ const compRoleId = dbCompositeRoles.find((cr) => cr.name === compRole).id;
+ if (!dbRole.compositeRoles.includes(compRoleId)) dbRole.compositeRoles = dbRole.compositeRoles.concat(compRoleId);
+ if (!dbRole.composite) dbRole.composite = true;
}
+
return await dbRole.save();
};
+export const deleteCompositeRolesDB = async (
+ roleName: string,
+ compositeRoleName: string,
+ integrationId: number,
+ environment: string,
+) => {
+ const dbRole = await models.requestRole.findOne({
+ where: {
+ name: roleName,
+ requestId: integrationId,
+ environment: environment,
+ },
+ });
+
+ if (dbRole) {
+ const dbCompositeRole = await models.requestRole.findOne({
+ where: {
+ name: compositeRoleName,
+ requestId: integrationId,
+ environment: environment,
+ },
+ raw: true,
+ });
+
+ if (dbCompositeRole) {
+ const updatedComposites = dbRole.compositeRoles.filter((cr) => cr !== dbCompositeRole.id);
+
+ dbRole.compositeRoles = updatedComposites;
+
+ if (updatedComposites.length === 0) {
+ dbRole.composite = false;
+ }
+ return await dbRole.save();
+ }
+ }
+};
+
export const getCompositeParentRoles = async (roleName: string, integrationId: number, environment: string) => {
const [results] = await sequelize.query(
'select * from request_roles where (select id from request_roles where name = :roleName and environment = :environment and request_id = :integrationId) = ANY(composite_roles) and request_id = :integrationId and environment = :environment;',
diff --git a/lambda/app/src/queries/team.ts b/lambda/app/src/queries/team.ts
index f06ae24b4..4ec2a3a62 100644
--- a/lambda/app/src/queries/team.ts
+++ b/lambda/app/src/queries/team.ts
@@ -166,7 +166,7 @@ export const findAllowedTeamUsers = async (teamId: number, userId: number, optio
});
};
-export const getAllEmailsOfTeam = async (teamId: number) => {
+export const getAllEmailsOfTeam = async (teamId: number): Promise<{ idir_email: string; role: string }[]> => {
const [userEmails] = await sequelize.query(
'SELECT a.idir_email, b.role FROM users a join users_teams b ON a.id = b.user_id AND b.team_id = :teamId',
{
diff --git a/lambda/app/src/routes.ts b/lambda/app/src/routes.ts
index e8248222f..a8feabf75 100644
--- a/lambda/app/src/routes.ts
+++ b/lambda/app/src/routes.ts
@@ -51,7 +51,7 @@ import { Session, User } from '../../shared/interfaces';
import { inviteTeamMembers } from '../src/utils/helpers';
import { getAllowedTeam, getAllowedTeams } from '@lambda-app/queries/team';
import { parseInvitationToken } from '@lambda-app/helpers/token';
-import { findMyOrTeamIntegrationsByService } from '@lambda-app/queries/request';
+import { findMyOrTeamIntegrationsByService, getAllowedRequest } from '@lambda-app/queries/request';
import { isAdmin } from './utils/helpers';
import {
createClientRole,
@@ -75,6 +75,7 @@ import { EMAILS } from '@lambda-shared/enums';
import { fetchLogs, fetchMetrics } from '@lambda-app/controllers/logs';
import { getPrivacyZones, getAttributes } from './controllers/bc-services-card';
import createHttpError from 'http-errors';
+import { logsRateLimiter } from './utils/rate-limiters';
const APP_URL = process.env.APP_URL || '';
@@ -96,690 +97,706 @@ const handleError = (res, err) => {
};
export const setRoutes = (app: any) => {
- app.options(`/*`, async (req, res) => {
- res.status(200).json(null);
- });
-
- app.get(`/heartbeat`, async (req, res) => {
- try {
- const result = await wakeUpAll();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/github/discussions`, async (req, res) => {
- try {
- const result = await fetchDiscussions();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/verify-token`, async (req, res) => {
- try {
+ try {
+ app.options(`/*`, async (req, res) => {
+ res.status(200).json(null);
+ });
+
+ app.get(`/heartbeat`, async (req, res) => {
+ try {
+ const result = await wakeUpAll();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/github/discussions`, async (req, res) => {
+ try {
+ const result = await fetchDiscussions();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/verify-token`, async (req, res) => {
+ try {
+ const session = (await authenticate(req.headers)) as Session;
+ res.status(200).json(session);
+ return session;
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/verify`, async (req, res) => {
+ try {
+ const token = req.query.token;
+ if (!token) return res.redirect(`${APP_URL}/verify-user?message=notoken`);
+ else {
+ const { error, message, userId, teamId } = parseInvitationToken(token);
+
+ if (error) return res.redirect(`${APP_URL}/verify-user?message=${message}`);
+
+ const verified = await verifyTeamMember(userId, teamId);
+ if (!verified) return res.redirect(`${APP_URL}/verify-user?message=notfound`);
+
+ return res.redirect(`${APP_URL}/verify-user?message=success&teamId=${teamId}`);
+ }
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/delete-inactive-idir-users`, async (req, res) => {
+ try {
+ const { Authorization, authorization } = req.headers || {};
+ const authHeader = Authorization || authorization;
+ if (!authHeader || authHeader !== process.env.API_AUTH_SECRET) {
+ res.status(401).json({ success: false, message: 'not authorized' });
+ return false;
+ }
+ const result = await deleteStaleUsers(req.body);
+ if (result) res.status(200).json({ success: true });
+ else res.status(404).json({ success: false, message: 'user not found' });
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.use(async (req, res, next) => {
const session = (await authenticate(req.headers)) as Session;
- res.status(200).json(session);
- return session;
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/verify`, async (req, res) => {
- try {
- const token = req.query.token;
- if (!token) return res.redirect(`${APP_URL}/verify-user?message=notoken`);
- else {
- const { error, message, userId, teamId } = parseInvitationToken(token);
-
- if (error) return res.redirect(`${APP_URL}/verify-user?message=${message}`);
-
- const verified = await verifyTeamMember(userId, teamId);
- if (!verified) return res.redirect(`${APP_URL}/verify-user?message=notfound`);
-
- return res.redirect(`${APP_URL}/verify-user?message=success&teamId=${teamId}`);
- }
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/delete-inactive-idir-users`, async (req, res) => {
- try {
- const { Authorization, authorization } = req.headers || {};
- const authHeader = Authorization || authorization;
- if (!authHeader || authHeader !== process.env.API_AUTH_SECRET) {
+ if (!session) {
res.status(401).json({ success: false, message: 'not authorized' });
return false;
}
- const result = await deleteStaleUsers(req.body);
- if (result) res.status(200).json({ success: true });
- else res.status(404).json({ success: false, message: 'user not found' });
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.use(async (req, res, next) => {
- const session = (await authenticate(req.headers)) as Session;
- if (!session) {
- res.status(401).json({ success: false, message: 'not authorized' });
- return false;
- }
-
- try {
- const user: User = await findOrCreateUser(session);
- user.isAdmin = isAdmin(session);
- session.user = user;
- req.user = user;
- req.session = session;
- } catch (err) {
- handleError(res, err);
- return false;
- }
-
- if (next) next();
- });
-
- app.get(`/me`, async (req, res) => {
- try {
- const integrations = await findMyOrTeamIntegrationsByService(req.user.id);
- res.status(200).json({ ...req.user, integrations });
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/me`, async (req, res) => {
- try {
- const result = await updateProfile(req.session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post('/surveys', async (req, res) => {
- try {
- const { rating, message, triggerEvent } = req.body;
-
- if (!rating || !triggerEvent) {
- return res.status(422).json({ message: 'Please include the keys "rating" and "triggerEvent" in the body.' });
- }
-
- // awaiting so email won't send if db save errors
- await createSurvey(req.session, req.body);
- await sendTemplate(EMAILS.SURVEY_COMPLETED, { user: req.session.user, rating, message, triggerEvent });
-
- res.status(200).send();
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/requests-all`, async (req, res) => {
- try {
- const result = await getRequestAll(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/requests`, async (req, res) => {
- try {
- const { include } = req.query || {};
- const result = await getRequests(req.session as Session, req.user, include);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/team-integrations/:teamId`, async (req, res) => {
- try {
- const { teamId } = req.params;
- const result = await getIntegrations(req.session as Session, teamId, req.user);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/requests`, async (req, res) => {
- try {
- const result = await createRequest(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/requests`, async (req, res) => {
- try {
- const { submit } = req.query || {};
- const result = await updateRequest(req.session as Session, req.body, req.user, submit);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/requests/:id/resubmit`, async (req, res) => {
- try {
- const { id } = req.params || {};
- if (!id) {
- throw new createHttpError.NotFound('integration ID not found');
- }
- const result = await resubmitRequest(req.session as Session, Number(id));
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/requests/:id/restore`, async (req, res) => {
- try {
- assertSessionRole(req.session, 'sso-admin');
- const { id } = req.params || {};
- let { email } = req.body || {};
- if (typeof email === 'string') {
- email = email.toLowerCase();
- }
- if (!id) {
- throw new createHttpError.NotFound('integration ID not found');
- }
- const result = await restoreRequest(req.session as Session, Number(id), email);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/requests/:id/logs`, async (req, res) => {
- try {
- const { id } = req.params || {};
- const { start, end, env } = req.query || {};
- const { status, message, data } = await fetchLogs(req.session, env, id, start, end);
- if (status === 200) {
- res.setHeader('Content-Length', JSON.stringify(data).length);
- res.setHeader('Content-Type', 'application/json');
- res.setHeader('Content-Disposition', `attachment`);
- res.status(status).send(data);
- } else {
- res.status(status).send({ message });
- }
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/requests/:id/metrics`, async (req, res) => {
- try {
- const { id } = req.params || {};
- const { fromDate, toDate, env } = req.query || {};
- const { status, message, data } = await fetchMetrics(req.session, id, env, fromDate, toDate);
- if (status === 200) res.status(status).send(data);
- else res.status(status).send({ message });
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.delete(`/requests`, async (req, res) => {
- try {
- const { id } = req.query || {};
-
- const authorized = await isAllowedToDeleteIntegration(req.session as Session, id);
-
- if (!authorized)
- return res.status(401).json({ success: false, message: 'You are not authorized to delete this integration' });
-
- const result = await deleteRequest(req.session as Session, req.user, Number(id));
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/request`, async (req, res) => {
- try {
- const result = await getRequest(req.session as Session, req.user, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/request-metadata`, async (req, res) => {
- try {
- const result = await updateRequestMetadata(req.session as Session, req.user, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/installation`, async (req, res) => {
- try {
- const result = await getInstallation(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/installation`, async (req, res) => {
- try {
- const result = await changeSecret(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/users`, async (req, res) => {
- try {
- const result = await searchKeycloakUsers(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/roles`, async (req, res) => {
- try {
- const result = await listRoles(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/user-roles`, async (req, res) => {
- try {
- const result = await listClientRolesByUsers((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/keycloak/user-role`, async (req, res) => {
- try {
- const result = await updateUserRoleMapping((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/keycloak/user-roles`, async (req, res) => {
- try {
- const result = await updateUserRoleMappings((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/role-users`, async (req, res) => {
- try {
- const result = await listUsersByRole(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/set-composite-roles`, async (req, res) => {
- try {
- const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
-
- if (!authorized)
- return res.status(401).json({ success: false, message: 'You are not authorized to update composite roles' });
-
- const result = await setCompositeRoles((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/get-composite-roles`, async (req, res) => {
- try {
- const result = await listCompositeRoles(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/roles`, async (req, res) => {
- try {
- const result = await createClientRole((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/role`, async (req, res) => {
- try {
- const result = await getClientRole((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/bulk-roles`, async (req, res) => {
- try {
- const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
-
- if (!authorized)
- return res.status(401).json({ success: false, message: 'You are not authorized to create role' });
-
- const result = await bulkCreateClientRoles((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/keycloak/delete-role`, async (req, res) => {
- try {
- const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
-
- if (!authorized)
- return res.status(401).json({ success: false, message: 'You are not authorized to delete role' });
-
- const result = await deleteRoles((req.session as Session).user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/bceid-webservice/idir/search`, async (req, res) => {
- try {
- const result = await searchIdirUsers(req.body);
- if (!result) res.status(404).send();
- else res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/bceid-webservice/idir/import`, async (req, res) => {
- try {
- const result = await importIdirUser(req.body);
- if (!result) res.status(404).send();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get('/idir-users', async (req, res) => {
- try {
- const { email } = req.query;
- if (!email) {
- res.status(400).send('Must include email query parameter');
- return;
- }
- const result = await searchIdirEmail(email);
- res.status(200).send(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/events`, async (req, res) => {
- try {
- const result = await getEvents(req.session as Session, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams`, async (req, res) => {
- try {
- const result = await listTeams(req.user);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/allowed-teams`, async (req, res) => {
- try {
- const result = await getAllowedTeams(req.user, { raw: true });
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/allowed-teams/:id`, async (req, res) => {
- try {
- const { id } = req.params;
- const result = await getAllowedTeam(id, req.user, { raw: true });
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/teams`, async (req, res) => {
- try {
- const result = await createTeam(req.user, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/teams/:id`, async (req, res) => {
- try {
- const { id } = req.params;
- const result = await updateTeam(req.user, id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/teams/:id/members`, async (req, res) => {
- try {
- const { id } = req.params;
- const result = await addUsersToTeam(id, req.user.id, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/teams/:id/members/:memberId`, async (req, res) => {
- try {
- const { id, memberId } = req.params;
- const result = await updateMemberInTeam(req.user.id, id, memberId, req.body);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.delete(`/teams/:id/members/:memberId`, async (req, res) => {
- try {
- const { id, memberId } = req.params;
- const result = await removeUserFromTeam(req.user.id, memberId, id);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/:id/members`, async (req, res) => {
- try {
- const { id } = req.params;
- const result = await findAllowedTeamUsers(id, req.user.id);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/teams/:id/invite`, async (req, res) => {
- try {
- const { id } = req.params;
- await inviteTeamMembers(req.user.id, [req.body], id);
- res.status(200).send();
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.delete(`/teams/:id`, async (req, res) => {
- try {
- const { id } = req.params;
- const result = await deleteTeam(req.session as Session, id);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.post(`/teams/:id/service-accounts`, async (req, res) => {
- try {
- const { id: teamId } = req.params;
- const result = await requestServiceAccount(req.session as Session, req.user.id, teamId, req.user.displayName);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/:id/service-accounts`, async (req, res) => {
- try {
- const { id: teamId } = req.params;
- const result = await getServiceAccounts(req.user.id, teamId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/:id/service-accounts/:saId`, async (req, res) => {
- try {
- const { id: teamId, saId } = req.params;
- const result = await getServiceAccount(req.user.id, teamId, saId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/:id/service-accounts/:saId/restore`, async (req, res) => {
- try {
- const { id: teamId, saId } = req.params;
- assertSessionRole(req.session, 'sso-admin');
- const result = await restoreTeamServiceAccount(req.session as Session, req.user.id, teamId, saId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/teams/:id/service-accounts/:saId/credentials`, async (req, res) => {
- try {
- const { id: teamId, saId } = req.params;
- const result = await getServiceAccountCredentials(req.user.id, teamId, saId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.put(`/teams/:id/service-accounts/:saId/credentials`, async (req, res) => {
- try {
- const { id: teamId, saId } = req.params;
- const result = await updateServiceAccountSecret(req.user.id, teamId, saId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.delete(`/teams/:id/service-accounts/:saId`, async (req, res) => {
- try {
- const { id: teamId, saId } = req.params;
- const result = await deleteServiceAccount(req.session as Session, req.user.id, teamId, saId);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/reports/all-standard-integrations`, async (req, res) => {
- try {
- assertSessionRole(req.session, 'sso-admin');
- const result = await getAllStandardIntegrations();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/reports/database-tables`, async (req, res) => {
- try {
- assertSessionRole(req.session, 'sso-admin');
- const result = await getDatabaseTable(req.query.type, req.query.orderBy);
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/reports/all-bceid-approved-requests-and-events`, async (req, res) => {
- try {
- assertSessionRole(req.session, 'sso-admin');
- const result = await getBceidApprovedRequestsAndEvents();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get(`/reports/data-integrity`, async (req, res) => {
- try {
- assertSessionRole(req.session, 'sso-admin');
- const result = await getDataIntegrityReport();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get('/bc-services-card/privacy-zones', async (req, res) => {
- try {
- const result = await getPrivacyZones();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
-
- app.get('/bc-services-card/claim-types', async (req, res) => {
- try {
- const result = await getAttributes();
- res.status(200).json(result);
- } catch (err) {
- handleError(res, err);
- }
- });
+
+ try {
+ const user: User = await findOrCreateUser(session);
+ user.isAdmin = isAdmin(session);
+ session.user = user;
+ req.user = user;
+ req.session = session;
+ } catch (err) {
+ handleError(res, err);
+ return false;
+ }
+
+ if (next) next();
+ });
+
+ app.get(`/me`, async (req, res) => {
+ try {
+ const integrations = await findMyOrTeamIntegrationsByService(req.user.id);
+ res.status(200).json({ ...req.user, integrations });
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/me`, async (req, res) => {
+ try {
+ const result = await updateProfile(req.session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post('/surveys', async (req, res) => {
+ try {
+ const { rating, message, triggerEvent } = req.body;
+
+ if (!rating || !triggerEvent) {
+ return res.status(422).json({ message: 'Please include the keys "rating" and "triggerEvent" in the body.' });
+ }
+
+ // awaiting so email won't send if db save errors
+ await createSurvey(req.session, req.body);
+ await sendTemplate(EMAILS.SURVEY_COMPLETED, { user: req.session.user, rating, message, triggerEvent });
+
+ res.status(200).send();
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/requests-all`, async (req, res) => {
+ try {
+ const result = await getRequestAll(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/requests`, async (req, res) => {
+ try {
+ const { include } = req.query || {};
+ const result = await getRequests(req.session as Session, req.user, include);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/team-integrations/:teamId`, async (req, res) => {
+ try {
+ const { teamId } = req.params;
+ const result = await getIntegrations(req.session as Session, teamId, req.user);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/requests`, async (req, res) => {
+ try {
+ const result = await createRequest(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/requests`, async (req, res) => {
+ try {
+ const { submit } = req.query || {};
+ const result = await updateRequest(req.session as Session, req.body, req.user, submit);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/requests/:id/resubmit`, async (req, res) => {
+ try {
+ const { id } = req.params || {};
+ if (!id) {
+ throw new createHttpError.NotFound('integration ID not found');
+ }
+ const result = await resubmitRequest(req.session as Session, Number(id));
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/requests/:id/restore`, async (req, res) => {
+ try {
+ assertSessionRole(req.session, 'sso-admin');
+ const { id } = req.params || {};
+ let { email } = req.body || {};
+ if (typeof email === 'string') {
+ email = email.toLowerCase();
+ }
+ if (!id) {
+ throw new createHttpError.NotFound('integration ID not found');
+ }
+ const result = await restoreRequest(req.session as Session, Number(id), email);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/requests/:id/logs`, logsRateLimiter, async (req, res) => {
+ try {
+ const { id } = req.params || {};
+ const { start, end, env } = req.query || {};
+ const userRequest = await getAllowedRequest(req.session, id);
+ if (!userRequest) {
+ return res.status(403).send('forbidden');
+ }
+ const { status, message, data } = await fetchLogs(
+ env,
+ userRequest.clientId,
+ userRequest.id,
+ start,
+ end,
+ req.session.idir_userid,
+ );
+ if (status === 200) {
+ res.setHeader('X-Message', message);
+ res.setHeader('Content-Length', JSON.stringify(data).length);
+ res.setHeader('Content-Type', 'application/json');
+ res.setHeader('Content-Disposition', `attachment`);
+ res.status(status).send(data);
+ } else {
+ res.status(status).send({ message });
+ }
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/requests/:id/metrics`, async (req, res) => {
+ try {
+ const { id } = req.params || {};
+ const { fromDate, toDate, env } = req.query || {};
+ const { status, message, data } = await fetchMetrics(req.session, id, env, fromDate, toDate);
+ if (status === 200) res.status(status).send(data);
+ else res.status(status).send({ message });
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.delete(`/requests`, async (req, res) => {
+ try {
+ const { id } = req.query || {};
+
+ const authorized = await isAllowedToDeleteIntegration(req.session as Session, id);
+
+ if (!authorized)
+ return res.status(401).json({ success: false, message: 'You are not authorized to delete this integration' });
+
+ const result = await deleteRequest(req.session as Session, req.user, Number(id));
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/request`, async (req, res) => {
+ try {
+ const result = await getRequest(req.session as Session, req.user, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/request-metadata`, async (req, res) => {
+ try {
+ const result = await updateRequestMetadata(req.session as Session, req.user, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/installation`, async (req, res) => {
+ try {
+ const result = await getInstallation(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/installation`, async (req, res) => {
+ try {
+ const result = await changeSecret(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/users`, async (req, res) => {
+ try {
+ const result = await searchKeycloakUsers(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/roles`, async (req, res) => {
+ try {
+ const result = await listRoles(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/user-roles`, async (req, res) => {
+ try {
+ const result = await listClientRolesByUsers((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/keycloak/user-role`, async (req, res) => {
+ try {
+ const result = await updateUserRoleMapping((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/keycloak/user-roles`, async (req, res) => {
+ try {
+ const result = await updateUserRoleMappings((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/role-users`, async (req, res) => {
+ try {
+ const result = await listUsersByRole(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/set-composite-roles`, async (req, res) => {
+ try {
+ const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
+
+ if (!authorized)
+ return res.status(401).json({ success: false, message: 'You are not authorized to update composite roles' });
+
+ const result = await setCompositeRoles((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/get-composite-roles`, async (req, res) => {
+ try {
+ const result = await listCompositeRoles(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/roles`, async (req, res) => {
+ try {
+ const result = await createClientRole((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/role`, async (req, res) => {
+ try {
+ const result = await getClientRole((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/bulk-roles`, async (req, res) => {
+ try {
+ const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
+
+ if (!authorized)
+ return res.status(401).json({ success: false, message: 'You are not authorized to create role' });
+
+ const result = await bulkCreateClientRoles((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/keycloak/delete-role`, async (req, res) => {
+ try {
+ const authorized = await isAllowedToManageRoles(req.session as Session, req.body.integrationId);
+
+ if (!authorized)
+ return res.status(401).json({ success: false, message: 'You are not authorized to delete role' });
+
+ const result = await deleteRoles((req.session as Session).user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/bceid-webservice/idir/search`, async (req, res) => {
+ try {
+ const result = await searchIdirUsers(req.body);
+ if (!result) res.status(404).send();
+ else res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/bceid-webservice/idir/import`, async (req, res) => {
+ try {
+ const result = await importIdirUser(req.body);
+ if (!result) res.status(404).send();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get('/idir-users', async (req, res) => {
+ try {
+ const { email } = req.query;
+ if (!email) {
+ res.status(400).send('Must include email query parameter');
+ return;
+ }
+ const result = await searchIdirEmail(email);
+ res.status(200).send(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/events`, async (req, res) => {
+ try {
+ const result = await getEvents(req.session as Session, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams`, async (req, res) => {
+ try {
+ const result = await listTeams(req.user);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/allowed-teams`, async (req, res) => {
+ try {
+ const result = await getAllowedTeams(req.user, { raw: true });
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/allowed-teams/:id`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const result = await getAllowedTeam(id, req.user, { raw: true });
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/teams`, async (req, res) => {
+ try {
+ const result = await createTeam(req.user, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/teams/:id`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const result = await updateTeam(req.user, id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/teams/:id/members`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const result = await addUsersToTeam(id, req.user.id, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/teams/:id/members/:memberId`, async (req, res) => {
+ try {
+ const { id, memberId } = req.params;
+ const result = await updateMemberInTeam(req.user.id, id, memberId, req.body);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.delete(`/teams/:id/members/:memberId`, async (req, res) => {
+ try {
+ const { id, memberId } = req.params;
+ const result = await removeUserFromTeam(req.user.id, memberId, id);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/:id/members`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const result = await findAllowedTeamUsers(id, req.user.id);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/teams/:id/invite`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ await inviteTeamMembers(req.user.id, [req.body], id);
+ res.status(200).send();
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.delete(`/teams/:id`, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const result = await deleteTeam(req.session as Session, id);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.post(`/teams/:id/service-accounts`, async (req, res) => {
+ try {
+ const { id: teamId } = req.params;
+ const result = await requestServiceAccount(req.session as Session, req.user.id, teamId, req.user.displayName);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/:id/service-accounts`, async (req, res) => {
+ try {
+ const { id: teamId } = req.params;
+ const result = await getServiceAccounts(req.user.id, teamId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/:id/service-accounts/:saId`, async (req, res) => {
+ try {
+ const { id: teamId, saId } = req.params;
+ const result = await getServiceAccount(req.user.id, teamId, saId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/:id/service-accounts/:saId/restore`, async (req, res) => {
+ try {
+ const { id: teamId, saId } = req.params;
+ assertSessionRole(req.session, 'sso-admin');
+ const result = await restoreTeamServiceAccount(req.session as Session, req.user.id, teamId, saId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/teams/:id/service-accounts/:saId/credentials`, async (req, res) => {
+ try {
+ const { id: teamId, saId } = req.params;
+ const result = await getServiceAccountCredentials(req.user.id, teamId, saId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.put(`/teams/:id/service-accounts/:saId/credentials`, async (req, res) => {
+ try {
+ const { id: teamId, saId } = req.params;
+ const result = await updateServiceAccountSecret(req.user.id, teamId, saId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.delete(`/teams/:id/service-accounts/:saId`, async (req, res) => {
+ try {
+ const { id: teamId, saId } = req.params;
+ const result = await deleteServiceAccount(req.session as Session, req.user.id, teamId, saId);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/reports/all-standard-integrations`, async (req, res) => {
+ try {
+ assertSessionRole(req.session, 'sso-admin');
+ const result = await getAllStandardIntegrations();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/reports/database-tables`, async (req, res) => {
+ try {
+ assertSessionRole(req.session, 'sso-admin');
+ const result = await getDatabaseTable(req.query.type, req.query.orderBy);
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/reports/all-bceid-approved-requests-and-events`, async (req, res) => {
+ try {
+ assertSessionRole(req.session, 'sso-admin');
+ const result = await getBceidApprovedRequestsAndEvents();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get(`/reports/data-integrity`, async (req, res) => {
+ try {
+ assertSessionRole(req.session, 'sso-admin');
+ const result = await getDataIntegrityReport();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get('/bc-services-card/privacy-zones', async (req, res) => {
+ try {
+ const result = await getPrivacyZones();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
+ app.get('/bc-services-card/claim-types', async (req, res) => {
+ try {
+ const result = await getAttributes();
+ res.status(200).json(result);
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+ } catch (err) {
+ console.error('Failed to initialize routes', err);
+ }
};
diff --git a/lambda/app/src/utils/rate-limiters.ts b/lambda/app/src/utils/rate-limiters.ts
new file mode 100644
index 000000000..ba00ab04b
--- /dev/null
+++ b/lambda/app/src/utils/rate-limiters.ts
@@ -0,0 +1,26 @@
+import rateLimit from 'express-rate-limit';
+import RedisStore from 'rate-limit-redis';
+import RedisClient from 'ioredis';
+
+const getClientIp = (req) => {
+ const id = req.params?.id ?? req.params?.integrationId;
+ const env = req.query?.env ?? req.params?.environment;
+ const clientIp = req.headers['X-Forwarded-For'] ?? req.connection.remoteAddress;
+ return `${id}-${env}-${clientIp}`;
+};
+
+export const logsRateLimiter = rateLimit({
+ windowMs: 60 * 60 * 1000, // 1 hour
+ limit: 10,
+ standardHeaders: 'draft-7',
+ legacyHeaders: false,
+ keyGenerator: (req) => getClientIp(req),
+ message: 'Too many requests, please try again later.',
+ store:
+ process.env.NODE_ENV === 'production'
+ ? new RedisStore({
+ // @ts-expect-error - Known issue: the `call` function is not present in @types/ioredis
+ sendCommand: async (...args: string[]) => new RedisClient({ host: process.env.REDIS_HOST }).call(...args),
+ })
+ : null,
+});
diff --git a/lambda/app/yarn.lock b/lambda/app/yarn.lock
index 966800f10..83d4a233b 100644
--- a/lambda/app/yarn.lock
+++ b/lambda/app/yarn.lock
@@ -24,18 +24,19 @@
core-js "^2.6.12"
regenerator-runtime "^0.13.4"
-"@keycloak/keycloak-admin-client@18.0.1":
- version "18.0.1"
- resolved "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-18.0.1.tgz"
- integrity sha512-gmKjWbeLv5FvwlqDl+f6ZPFsgCOjFObYOPpmXj+v9itQd1PrSq6d1l1b7xIYyhAZnZ1pkKnDmzqtVrk+XrkAEg==
- dependencies:
- axios "^0.26.1"
- camelize-ts "^1.0.8"
- keycloak-js "^17.0.1"
- lodash "^4.17.21"
- query-string "^7.0.1"
- url-join "^4.0.0"
- url-template "^2.0.8"
+"@ioredis/commands@^1.1.1":
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz"
+ integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==
+
+"@keycloak/keycloak-admin-client@^24.0.5":
+ version "24.0.5"
+ resolved "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-24.0.5.tgz"
+ integrity sha512-SXDVtQ3ov7GQbxXq51Uq8lzhwzQwNg6XiY50ZA9whuUe2t/0zPT4Zd/LcULcjweIjSNWWgfbDyN1E3yRSL8Qqw==
+ dependencies:
+ camelize-ts "^3.0.0"
+ url-join "^5.0.0"
+ url-template "^3.1.1"
"@types/body-parser@*":
version "1.19.2"
@@ -151,18 +152,6 @@ asn1.js@^5.3.0:
minimalistic-assert "^1.0.0"
safer-buffer "^2.1.0"
-axios@^0.26.1:
- version "0.26.1"
- resolved "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz"
- integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==
- dependencies:
- follow-redirects "^1.14.8"
-
-base64-js@^1.5.1:
- version "1.5.1"
- resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz"
- integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
-
bn.js@^4.0.0, bn.js@^4.11.9:
version "4.12.0"
resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz"
@@ -212,10 +201,15 @@ call-bind@^1.0.7:
get-intrinsic "^1.2.4"
set-function-length "^1.2.1"
-camelize-ts@^1.0.8:
- version "1.0.9"
- resolved "https://registry.npmjs.org/camelize-ts/-/camelize-ts-1.0.9.tgz"
- integrity sha512-ePOW3V2qrQ0qtRlcTM6Qe3nXremdydIwsMKI1Vl2NBGM0tOo8n2xzJ7YOQpV1GIKHhs3p+F40ThI8/DoYWbYKQ==
+camelize-ts@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/camelize-ts/-/camelize-ts-3.0.0.tgz"
+ integrity sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ==
+
+cluster-key-slot@^1.1.0:
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz"
+ integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==
content-disposition@0.5.4:
version "0.5.4"
@@ -252,6 +246,13 @@ cors@^2.8.5:
object-assign "^4"
vary "^1"
+debug@^4.3.4:
+ version "4.3.7"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
+ integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
+ dependencies:
+ ms "^2.1.3"
+
debug@2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
@@ -259,11 +260,6 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
-decode-uri-component@^0.2.2:
- version "0.2.2"
- resolved "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz"
- integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
-
deep-diff@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/deep-diff/-/deep-diff-1.0.2.tgz"
@@ -278,6 +274,11 @@ define-data-property@^1.1.4:
es-errors "^1.3.0"
gopd "^1.0.1"
+denque@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz"
+ integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==
+
depd@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
@@ -345,7 +346,12 @@ etag@~1.8.1:
resolved "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
-express@^4.21.0:
+express-rate-limit@^7.4.1, "express-rate-limit@>= 6":
+ version "7.4.1"
+ resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.4.1.tgz"
+ integrity sha512-KS3efpnpIDVIXopMc65EMbWbUht7qvTCdtCR2dD/IZmi9MIkopYESwyRqLgv8Pfu589+KqDqOdzJWW7AHoACeg==
+
+express@^4.21.0, "express@4 || 5 || ^5.0.0-beta.1":
version "4.21.0"
resolved "https://registry.npmjs.org/express/-/express-4.21.0.tgz"
integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==
@@ -392,11 +398,6 @@ fast-json-stable-stringify@^2.0.0:
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
-filter-obj@^1.1.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz"
- integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==
-
finalhandler@1.3.1:
version "1.3.1"
resolved "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz"
@@ -410,11 +411,6 @@ finalhandler@1.3.1:
statuses "2.0.1"
unpipe "~1.0.0"
-follow-redirects@^1.14.8:
- version "1.15.9"
- resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz"
- integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==
-
forwarded@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz"
@@ -512,16 +508,26 @@ inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@2.0.4:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+ioredis@^5.4.1:
+ version "5.4.1"
+ resolved "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz"
+ integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==
+ dependencies:
+ "@ioredis/commands" "^1.1.1"
+ cluster-key-slot "^1.1.0"
+ debug "^4.3.4"
+ denque "^2.1.0"
+ lodash.defaults "^4.2.0"
+ lodash.isarguments "^3.1.0"
+ redis-errors "^1.2.0"
+ redis-parser "^3.0.0"
+ standard-as-callback "^2.1.0"
+
ipaddr.js@1.9.1:
version "1.9.1"
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz"
integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
-js-sha256@^0.9.0:
- version "0.9.0"
- resolved "https://registry.npmjs.org/js-sha256/-/js-sha256-0.9.0.tgz"
- integrity sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==
-
"js-tokens@^3.0.0 || ^4.0.0":
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -596,14 +602,6 @@ jws@^4.0.0:
jwa "^2.0.0"
safe-buffer "^5.0.1"
-keycloak-js@^17.0.1:
- version "17.0.1"
- resolved "https://registry.npmjs.org/keycloak-js/-/keycloak-js-17.0.1.tgz"
- integrity sha512-mbLBSoogCBX5VYeKCdEz8BaRWVL9twzSqArRU3Mo3Z7vEO1mghGZJ5IzREfiMEi7kTUZtk5i9mu+Yc0koGkK6g==
- dependencies:
- base64-js "^1.5.1"
- js-sha256 "^0.9.0"
-
lambda-api-router@^1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/lambda-api-router/-/lambda-api-router-1.0.6.tgz"
@@ -613,11 +611,21 @@ lambda-api-router@^1.0.6:
minimist "^1.2.5"
path-to-regexp "^6.2.0"
+lodash.defaults@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz"
+ integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==
+
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz"
integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==
+lodash.isarguments@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz"
+ integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==
+
lodash.isboolean@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz"
@@ -648,7 +656,7 @@ lodash.once@^4.0.0:
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
-lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
+lodash@^4.17.15, lodash@^4.17.20:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -707,7 +715,7 @@ minimist@^1.2.5:
resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
-ms@^2.1.1, ms@2.1.3:
+ms@^2.1.1, ms@^2.1.3, ms@2.1.3:
version "2.1.3"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -788,21 +796,16 @@ qs@6.13.0:
dependencies:
side-channel "^1.0.6"
-query-string@^7.0.1:
- version "7.1.3"
- resolved "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz"
- integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==
- dependencies:
- decode-uri-component "^0.2.2"
- filter-obj "^1.1.0"
- split-on-first "^1.0.0"
- strict-uri-encode "^2.0.0"
-
range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+rate-limit-redis@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/rate-limit-redis/-/rate-limit-redis-4.2.0.tgz"
+ integrity sha512-wV450NQyKC24NmPosJb2131RoczLdfIJdKCReNwtVpm5998U8SgKrAZrIHaN/NfQgqOHaan8Uq++B4sa5REwjA==
+
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz"
@@ -844,6 +847,18 @@ react@^18.1.0, react@>=15:
dependencies:
loose-envify "^1.1.0"
+redis-errors@^1.0.0, redis-errors@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz"
+ integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==
+
+redis-parser@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz"
+ integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==
+ dependencies:
+ redis-errors "^1.0.0"
+
regenerator-runtime@^0.13.4:
version "0.13.10"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.10.tgz"
@@ -937,21 +952,16 @@ side-channel@^1.0.6:
get-intrinsic "^1.2.4"
object-inspect "^1.13.1"
-split-on-first@^1.0.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz"
- integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==
+standard-as-callback@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz"
+ integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
statuses@2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
-strict-uri-encode@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz"
- integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==
-
toidentifier@1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz"
@@ -977,15 +987,15 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
-url-join@^4.0.0:
- version "4.0.1"
- resolved "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz"
- integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
+url-join@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz"
+ integrity sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==
-url-template@^2.0.8:
- version "2.0.8"
- resolved "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz"
- integrity sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==
+url-template@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.npmjs.org/url-template/-/url-template-3.1.1.tgz"
+ integrity sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==
utils-merge@1.0.1:
version "1.0.1"
diff --git a/lambda/css-api/k6-tests/cleanup.sh b/lambda/css-api/k6-tests/cleanup.sh
index cb622979a..6090c2552 100755
--- a/lambda/css-api/k6-tests/cleanup.sh
+++ b/lambda/css-api/k6-tests/cleanup.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-ACCESS_TOKEN=$(curl -X POST -d grant_type=client_credentials -d client_id=$K6_CLIENT_ID -d client_secret=$K6_CLIENT_SECRET https://sso-keycloak-6-b861c7-test.apps.silver.devops.gov.bc.ca/auth/realms/standard/protocol/openid-connect/token | jq '.access_token' -r)
+ACCESS_TOKEN=$(curl -X POST -d grant_type=client_credentials -d client_id=$K6_CLIENT_ID -d client_secret=$K6_CLIENT_SECRET $K6_KEYCLOAK_TOKEN_URL | jq '.access_token' -r)
INTEGRATION_ID=$(curl -X GET $K6_CSS_API_URL/integrations -H "Authorization: Bearer $ACCESS_TOKEN" | jq '.data | .[].id' -r)
diff --git a/lambda/css-api/k6-tests/load-tests.js b/lambda/css-api/k6-tests/load-tests.js
index fe3c71129..650d67170 100644
--- a/lambda/css-api/k6-tests/load-tests.js
+++ b/lambda/css-api/k6-tests/load-tests.js
@@ -19,12 +19,7 @@ let integrationId;
http.setResponseCallback(http.expectedStatuses({ min: 200, max: 204 }));
export const options = {
- stages: [
- { duration: '10m', target: 100 }, // simulate ramp-up of traffic from 1 to 50 users over 3 minutes.
- ],
- thresholds: {
- 'http_req_duration{status:504}': ['max=0'],
- },
+ stages: [{ duration: '5m', target: 100 }],
};
export function setup() {
diff --git a/lambda/css-api/k6-tests/smoke-tests/modules/user-role-mappings.js b/lambda/css-api/k6-tests/smoke-tests/modules/user-role-mappings.js
index 2bf3db004..e3dea24f5 100644
--- a/lambda/css-api/k6-tests/smoke-tests/modules/user-role-mappings.js
+++ b/lambda/css-api/k6-tests/smoke-tests/modules/user-role-mappings.js
@@ -403,7 +403,7 @@ export function testUserRoleMapping(options) {
console.debug(`Response from CSS API: ${JSON.stringify(response, 0, 2)}`);
check(response, {
- 'should return 400 when user with invalid or no idp passed': (r) => r.status === 400,
+ 'should return 404 when user with invalid or no idp passed': (r) => r.status === 404,
});
sleep(SLEEP_DURATION);
@@ -511,7 +511,7 @@ export function testUserRoleMapping(options) {
console.debug(`Response from CSS API: ${JSON.stringify(response, 0, 2)}`);
check(response, {
- 'should return 400 when invalid or no idp passed': (r) => r.status === 400,
+ 'should return 404 when invalid or no idp passed': (r) => r.status === 404,
});
sleep(SLEEP_DURATION);
@@ -570,7 +570,7 @@ export function testUserRoleMapping(options) {
console.debug(`Response from CSS API: ${JSON.stringify(response, 0, 2)}`);
check(response, {
- 'should return 400 when non-associated role name passed': (r) => r.status === 400,
+ 'should return 404 when non-associated role name passed': (r) => r.status === 404,
});
sleep(SLEEP_DURATION);
@@ -585,7 +585,7 @@ export function testUserRoleMapping(options) {
console.debug(`Response from CSS API: ${JSON.stringify(response, 0, 2)}`);
check(response, {
- 'should return 400 when username with invalid or no idp passed': (r) => r.status === 400,
+ 'should return 404 when username with invalid or no idp passed': (r) => r.status === 404,
});
sleep(SLEEP_DURATION);
diff --git a/lambda/css-api/package-lock.json b/lambda/css-api/package-lock.json
index d90823ee7..564014cfe 100644
--- a/lambda/css-api/package-lock.json
+++ b/lambda/css-api/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "Apache-2.0",
"dependencies": {
+ "@keycloak/keycloak-admin-client": "^26.0.6",
"cors": "^2.8.5",
"express": "^4.21.0",
"http-errors": "^2.0.0",
@@ -29,6 +30,20 @@
"@types/jws": "^3.2.3"
}
},
+ "node_modules/@keycloak/keycloak-admin-client": {
+ "version": "26.0.6",
+ "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-26.0.6.tgz",
+ "integrity": "sha512-pZmaSAyg+LwQ3qnZF+01ZkURpcoEdLAloUK5KOZjE9jyNd86EHdx98/XmTYaJIuQ6ydMXxTWWc5Grq18H+PvJQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "camelize-ts": "^3.0.0",
+ "url-join": "^5.0.0",
+ "url-template": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@types/http-errors": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz",
@@ -196,6 +211,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/camelize-ts": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-3.0.0.tgz",
+ "integrity": "sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1204,6 +1228,24 @@
"node": ">= 0.8"
}
},
+ "node_modules/url-join": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
+ "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
+ "node_modules/url-template": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/url-template/-/url-template-3.1.1.tgz",
+ "integrity": "sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ }
+ },
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
@@ -1230,6 +1272,16 @@
}
},
"dependencies": {
+ "@keycloak/keycloak-admin-client": {
+ "version": "26.0.6",
+ "resolved": "https://registry.npmjs.org/@keycloak/keycloak-admin-client/-/keycloak-admin-client-26.0.6.tgz",
+ "integrity": "sha512-pZmaSAyg+LwQ3qnZF+01ZkURpcoEdLAloUK5KOZjE9jyNd86EHdx98/XmTYaJIuQ6ydMXxTWWc5Grq18H+PvJQ==",
+ "requires": {
+ "camelize-ts": "^3.0.0",
+ "url-join": "^5.0.0",
+ "url-template": "^3.1.1"
+ }
+ },
"@types/http-errors": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.2.tgz",
@@ -1361,6 +1413,11 @@
"set-function-length": "^1.2.1"
}
},
+ "camelize-ts": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/camelize-ts/-/camelize-ts-3.0.0.tgz",
+ "integrity": "sha512-cgRwKKavoDKLTjO4FQTs3dRBePZp/2Y9Xpud0FhuCOTE86M2cniKN4CCXgRnsyXNMmQMifVHcv6SPaMtTx6ofQ=="
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2096,6 +2153,16 @@
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
+ "url-join": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-5.0.0.tgz",
+ "integrity": "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="
+ },
+ "url-template": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/url-template/-/url-template-3.1.1.tgz",
+ "integrity": "sha512-4oszoaEKE/mQOtAmdMWqIRHmkxWkUZMnXFnjQ5i01CuRSK3uluxcH1MRVVVWmhlnzT1SCDfKxxficm2G37qzCA=="
+ },
"utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
diff --git a/lambda/css-api/src/authenticate.ts b/lambda/css-api/src/authenticate.ts
index dd5fee656..419617dd5 100644
--- a/lambda/css-api/src/authenticate.ts
+++ b/lambda/css-api/src/authenticate.ts
@@ -71,7 +71,7 @@ const validateJWTSignature = async (token) => {
return { success: true, data: { teamId: team }, err: null };
} catch (err) {
- console.log(err);
+ console.error(err);
if (err.name === 'TokenExpiredError') failedAuth.err = 'token expired';
else if (err.name === 'JsonWebTokenError') failedAuth.err = 'invalid token';
diff --git a/lambda/css-api/src/controllers/integration-controller.ts b/lambda/css-api/src/controllers/integration-controller.ts
index d1d6bdfab..c22a0414d 100644
--- a/lambda/css-api/src/controllers/integration-controller.ts
+++ b/lambda/css-api/src/controllers/integration-controller.ts
@@ -1,11 +1,11 @@
-import { injectable } from 'tsyringe';
+import { inject, injectable } from 'tsyringe';
import { IntegrationService } from '../services/integration-service';
@injectable()
export class IntegrationController {
private attributes = ['id', 'projectName', 'authType', 'environments', 'status', 'createdAt', 'updatedAt'];
- constructor(private integrationService: IntegrationService) {}
+ constructor(@inject('IntegrationService') private integrationService: IntegrationService) {}
public async getIntegration(id: number, teamId: number) {
const int = await this.integrationService.getById(id, teamId);
diff --git a/lambda/css-api/src/controllers/role-controller.ts b/lambda/css-api/src/controllers/role-controller.ts
index 2f0872b82..f1b93372d 100644
--- a/lambda/css-api/src/controllers/role-controller.ts
+++ b/lambda/css-api/src/controllers/role-controller.ts
@@ -1,10 +1,10 @@
-import { injectable } from 'tsyringe';
+import { container, inject, injectable } from 'tsyringe';
import { RoleService } from '../services/role-service';
import { RolePayload } from '../types';
@injectable()
export class RoleController {
- constructor(private roleService: RoleService) {}
+ constructor(@inject('RoleService') private roleService: RoleService) {}
public async get(teamId: number, integrationId: number, environment: string, roleName: string) {
return await this.roleService.getByName(teamId, integrationId, environment, roleName);
diff --git a/lambda/css-api/src/controllers/user-controller.ts b/lambda/css-api/src/controllers/user-controller.ts
index eeeae1c08..a176ddcfa 100644
--- a/lambda/css-api/src/controllers/user-controller.ts
+++ b/lambda/css-api/src/controllers/user-controller.ts
@@ -1,10 +1,10 @@
import { UserService } from '../services/user-service';
-import { injectable } from 'tsyringe';
+import { inject, injectable } from 'tsyringe';
import { ListUsersFilterQuery } from '../types';
@injectable()
export class UserController {
- constructor(private userService: UserService) {}
+ constructor(@inject('UserService') private userService: UserService) {}
public async listUsers(environment: string, idp: string, query: ListUsersFilterQuery) {
return await this.userService.getUsers(environment, idp, query);
diff --git a/lambda/css-api/src/controllers/user-role-mapping-controller.ts b/lambda/css-api/src/controllers/user-role-mapping-controller.ts
index 2e82258b4..aedeec957 100644
--- a/lambda/css-api/src/controllers/user-role-mapping-controller.ts
+++ b/lambda/css-api/src/controllers/user-role-mapping-controller.ts
@@ -1,17 +1,13 @@
-import { findUserByRealm } from '@lambda-app/keycloak/users';
import { getValidator, postValidator, getUsersByRolenameValidator } from '../schemas/user-role-mapping';
-import { injectable } from 'tsyringe';
+import { inject, injectable } from 'tsyringe';
import { UserRoleMappingService } from '../services/user-role-mapping-service';
import { parseErrors } from '../util';
-import { RoleService } from '../services/role-service';
import createHttpError from 'http-errors';
-import { updateUserProps } from '../helpers/users';
-import { updateRoleProps } from '../helpers/roles';
import { ListUserRoleMappingQuery, RolePayload, UserRoleMappingPayload, ListUsersByRoleName } from '../types';
@injectable()
export class UserRoleMappingController {
- constructor(private userRoleMappingService: UserRoleMappingService, private roleService: RoleService) {}
+ constructor(@inject('UserRoleMappingService') private userRoleMappingService: UserRoleMappingService) {}
public async list(teamId: number, integrationId: number, environment: string, query: ListUserRoleMappingQuery) {
const valid = getValidator(query || {});
diff --git a/lambda/css-api/src/routes.ts b/lambda/css-api/src/routes.ts
index 067f8c7db..bdedac31f 100644
--- a/lambda/css-api/src/routes.ts
+++ b/lambda/css-api/src/routes.ts
@@ -10,6 +10,14 @@ import { TokenController } from './controllers/token-controller';
import { isEmpty } from 'lodash';
import createHttpError from 'http-errors';
import { UserController } from './controllers/user-controller';
+import { getIntegrationByIdAndTeam } from '@lambda-app/queries/request';
+import { fetchLogs } from '@lambda-app/controllers/logs';
+import { logsRateLimiter } from '@lambda-app/utils/rate-limiters';
+import { KeycloakService, KeycloakServiceFactory } from './services/keycloak-service';
+import { RoleService } from './services/role-service';
+import { IntegrationService } from './services/integration-service';
+import { UserRoleMappingService } from './services/user-role-mapping-service';
+import { UserService } from './services/user-service';
const tryJSON = (str: string) => {
try {
@@ -27,6 +35,14 @@ const handleError = (res, err) => {
res.status(err.status || 422).json({ message });
};
+container.registerSingleton('KeycloakServiceFactory', KeycloakServiceFactory);
+container.registerSingleton('DevKeycloakService', KeycloakService);
+container.registerSingleton('TestKeycloakService', KeycloakService);
+container.registerSingleton('ProdKeycloakService', KeycloakService);
+container.registerSingleton('RoleService', RoleService);
+container.registerSingleton('IntegrationService', IntegrationService);
+container.registerSingleton('UserRoleMappingService', UserRoleMappingService);
+container.registerSingleton('UserService', UserService);
const integrationController = container.resolve(IntegrationController);
const roleController = container.resolve(RoleController);
const userRoleMappingController = container.resolve(UserRoleMappingController);
@@ -140,6 +156,74 @@ export const setRoutes = (app: any) => {
}
});
+ app.get(`/integrations/:integrationId/:environment/logs`, logsRateLimiter, async (req, res) => {
+ /*#swagger.auto = false
+ #swagger.tags = ['Logs']
+ #swagger.path = '/integrations/{integrationId}/{environment}/logs'
+ #swagger.method = 'get'
+ #swagger.description = 'Get logs for the integration of the target environment'
+ #swagger.summary = 'Get logs for the integration and environment'
+ #swagger.parameters['integrationId'] = {
+ in: 'path',
+ description: 'Integration Id',
+ required: true,
+ type: 'number',
+ example: 1234
+ }
+ #swagger.parameters['environment'] = {
+ in: 'path',
+ description: 'Environment',
+ required: true,
+ schema: { $ref: '#/components/schemas/environment' }
+ }
+ #swagger.parameters['start'] = {
+ in: 'query',
+ required: true,
+ description: 'Start Datetime in ISO 8601 format, RFC 2822 format, or milliseconds since epoch.',
+ example: '2024-11-14T10:00:00Z'
+ }
+ #swagger.parameters['end'] = {
+ in: 'query',
+ required: true,
+ description: 'End Datetime in ISO 8601 format, RFC 2822 format, or milliseconds since epoch.',
+ example: '2024-11-14T11:00:00Z'
+ }
+ #swagger.responses[200] = {
+ description: 'OK',
+ schema: { $ref: '#/components/schemas/logsResponse' }
+ }
+ #swagger.responses[400] = {
+ description: 'Bad Request',
+ schema: { message: 'string' }
+ }
+ #swagger.responses[403] = {
+ description: 'Forbidden',
+ schema: { message: 'string' }
+ }
+ #swagger.responses[429] = {
+ description: 'Too Many Requests. Will be rate limited after 10 requests for the same integration and environment per hour.',
+ schema: { message: 'string' }
+ }
+ #swagger.responses[500,504] = {
+ description: 'Server Error',
+ schema: { message: 'string' }
+ }
+ */
+ try {
+ const { integrationId, environment } = req.params;
+ const { start, end } = req.query || {};
+ const int = await getIntegrationByIdAndTeam(integrationId, req.teamId);
+ if (!int) {
+ return res.status(403).json({ message: 'forbidden' });
+ }
+ const { status, message, data } = await fetchLogs(environment, int.clientId, int.id, start, end);
+ if (status === 200) res.status(status).send({ data, message });
+ else res.status(status).send({ message });
+ } catch (err) {
+ handleError(res, err);
+ }
+ });
+
app.get(`/integrations/:integrationId/:environment/roles`, async (req, res) => {
/*#swagger.auto = false
#swagger.tags = ['Roles']
@@ -1249,7 +1333,7 @@ export const setRoutes = (app: any) => {
#swagger.tags = ['Role-Mapping']
#swagger.path = '/integrations/{integrationId}/{environment}/users/{username}/roles'
#swagger.method = 'get'
- #swagger.description = 'Get roles associated with user for the integration of the target environment'
+ #swagger.description = 'Get roles associated with user for the integration of the target environment. For a service account, use the client ID as the username.'
#swagger.summary = 'Get roles associated with user'
#swagger.parameters['integrationId'] = {
in: 'path',
@@ -1375,7 +1459,7 @@ export const setRoutes = (app: any) => {
#swagger.tags = ['Role-Mapping']
#swagger.path = '/integrations/{integrationId}/{environment}/users/{username}/roles'
#swagger.method = 'post'
- #swagger.description = 'Assign roles to a user for the integration of the target environment'
+ #swagger.description = 'Assign roles to a user for the integration of the target environment. For a service account, use the client ID as the username.'
#swagger.summary = 'Assign roles to a user'
#swagger.parameters['integrationId'] = {
in: 'path',
@@ -1443,7 +1527,7 @@ export const setRoutes = (app: any) => {
#swagger.tags = ['Role-Mapping']
#swagger.path = '/integrations/{integrationId}/{environment}/users/{username}/roles/{roleName}'
#swagger.method = 'delete'
- #swagger.description = 'Unassign role from a user for the integration of the target environment'
+ #swagger.description = 'Unassign role from a user for the integration of the target environment. For a service account, use your client ID as the username.'
#swagger.summary = 'Unassign role from a user'
#swagger.parameters['integrationId'] = {
in: 'path',
diff --git a/lambda/css-api/src/services/keycloak-service.ts b/lambda/css-api/src/services/keycloak-service.ts
new file mode 100644
index 000000000..3f5cc4556
--- /dev/null
+++ b/lambda/css-api/src/services/keycloak-service.ts
@@ -0,0 +1,422 @@
+import axios, { AxiosError, AxiosInstance } from 'axios';
+import { getKeycloakCredentials } from '../util';
+import jwt from 'jsonwebtoken';
+import { RolePayload } from '../types';
+import createHttpError from 'http-errors';
+import { inject, singleton } from 'tsyringe';
+
+interface KeycloakTokenResponse {
+ access_token: string;
+ refresh_token: string;
+ expires_in: number;
+ refresh_expires_in: number;
+ token_type: string;
+ 'not-before-policy': number;
+ session_state: string;
+ scope: string;
+}
+
+export class KeycloakService {
+ private realm: string = 'standard';
+ private httpClient: AxiosInstance;
+ private environment: string;
+ private accessToken: string | null = null;
+ private refreshToken: string | null = null;
+ private refreshing: boolean = false;
+
+ constructor() {}
+
+ setEnvironment(environment: string) {
+ this.environment = environment;
+ const { keycloakUrl } = getKeycloakCredentials(environment);
+ this.httpClient = axios.create({
+ baseURL: `${keycloakUrl}`,
+ });
+ this.httpClient.interceptors.response.use(
+ (response) => {
+ return response;
+ },
+ (error: AxiosError) => {
+ console.error('keycloak request path: ', error?.request?.path);
+ console.error('keycloak request headers: ', error?.request?.headers);
+ console.error('keycloak response status: ', error?.response?.status);
+ console.error('keycloak response data: ', error?.response?.data);
+ console.error('keycloak response status message: ', error?.response?.statusText);
+ throw error;
+ },
+ );
+ }
+
+ getEnvironment() {
+ return this.environment;
+ }
+
+ isTokenValid(accessToken: string) {
+ return (jwt.decode(accessToken) as any).exp > Date.now().valueOf() / 1000 + 30;
+ }
+
+ async getAccessToken() {
+ if (this.accessToken && this.isTokenValid(this.accessToken)) {
+ return this.accessToken;
+ }
+ if (
+ this.accessToken &&
+ !this.isTokenValid(this.accessToken) &&
+ this.refreshToken &&
+ this.isTokenValid(this.refreshToken)
+ ) {
+ // refreshing access token 30 seconds earlier
+ if (this.refreshing) {
+ return this.accessToken;
+ }
+ this.refreshing = true;
+ const response = await this.httpClient.post
The IDIR user with the idir username {{username}} has an inactive guid in our system. They have been removed from
- client {{clientId}}{{#if roles.length}} and associated roles {{roles}}{{/if}}
+ client {{clientId}} in the {{env}} environment{{#if roles.length}} and associated roles {{roles}}{{/if}}
diff --git a/lambda/siteminder-tests-scheduler/yarn.lock b/lambda/siteminder-tests-scheduler/yarn.lock
index 1ab9d6c0c..9c01b9b5e 100644
--- a/lambda/siteminder-tests-scheduler/yarn.lock
+++ b/lambda/siteminder-tests-scheduler/yarn.lock
@@ -9,7 +9,7 @@
dependencies:
"@octokit/types" "^6.0.3"
-"@octokit/core@^3.5.1":
+"@octokit/core@^3.5.1", "@octokit/core@>=2", "@octokit/core@>=3":
version "3.5.1"
resolved "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz"
integrity sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==
diff --git a/lambda/tsconfig.jest.json b/lambda/tsconfig.jest.json
new file mode 100644
index 000000000..1809033ff
--- /dev/null
+++ b/lambda/tsconfig.jest.json
@@ -0,0 +1,6 @@
+{
+ "extends": "./tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true
+ }
+}
diff --git a/lambda/yarn.lock b/lambda/yarn.lock
index 1570c8907..11d58b059 100644
--- a/lambda/yarn.lock
+++ b/lambda/yarn.lock
@@ -23,7 +23,7 @@
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz"
integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
-"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9":
+"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8":
version "7.25.2"
resolved "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz"
integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
@@ -498,7 +498,7 @@
jest-haste-map "^29.7.0"
slash "^3.0.0"
-"@jest/transform@^29.7.0":
+"@jest/transform@^29.0.0", "@jest/transform@^29.7.0":
version "29.7.0"
resolved "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz"
integrity sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==
@@ -575,14 +575,6 @@
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-"@jridgewell/trace-mapping@0.3.9":
- version "0.3.9"
- resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
- integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
- dependencies:
- "@jridgewell/resolve-uri" "^3.0.3"
- "@jridgewell/sourcemap-codec" "^1.4.10"
-
"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.25"
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz"
@@ -591,6 +583,14 @@
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
+"@jridgewell/trace-mapping@0.3.9":
+ version "0.3.9"
+ resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz"
+ integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.0.3"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+
"@kurkle/color@^0.3.0":
version "0.3.2"
resolved "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz"
@@ -604,7 +604,7 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
-"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -617,6 +617,11 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
+"@popperjs/core@^2.11.6":
+ version "2.11.8"
+ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz"
+ integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
+
"@sinclair/typebox@^0.24.1":
version "0.24.38"
resolved "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.38.tgz"
@@ -1038,7 +1043,7 @@ ajv-errors@^3.0.0:
resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz"
integrity sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==
-ajv@^8.17.1:
+ajv@^8.0.1, ajv@^8.17.1:
version "8.17.1"
resolved "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz"
integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==
@@ -1169,7 +1174,7 @@ axios@^1.7.7:
form-data "^4.0.0"
proxy-from-env "^1.1.0"
-babel-jest@^29.7.0:
+babel-jest@^29.0.0, babel-jest@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz"
integrity sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==
@@ -1244,10 +1249,10 @@ binary-extensions@^2.0.0:
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
-body-parser@1.20.1:
- version "1.20.1"
- resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz"
- integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+body-parser@^1.20.0:
+ version "1.20.0"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz"
+ integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
@@ -1257,15 +1262,15 @@ body-parser@1.20.1:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
- qs "6.11.0"
+ qs "6.10.3"
raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
-body-parser@^1.20.0:
- version "1.20.0"
- resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz"
- integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
@@ -1275,7 +1280,7 @@ body-parser@^1.20.0:
http-errors "2.0.0"
iconv-lite "0.4.24"
on-finished "2.4.1"
- qs "6.10.3"
+ qs "6.11.0"
raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -1307,7 +1312,7 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.23.1:
+browserslist@^4.23.1, "browserslist@>= 4.21.0":
version "4.23.3"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz"
integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
@@ -1479,16 +1484,16 @@ color-convert@^2.0.1:
dependencies:
color-name "~1.1.4"
-color-name@1.1.3:
- version "1.1.3"
- resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
- integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+color-name@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz"
@@ -1608,10 +1613,12 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
-data-uri-to-buffer@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
- integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
+debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4:
+ version "4.3.4"
+ resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
debug@2.6.9:
version "2.6.9"
@@ -1620,13 +1627,6 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
-debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4:
- version "4.3.4"
- resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
- dependencies:
- ms "2.1.2"
-
dedent@^1.0.0:
version "1.5.3"
resolved "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz"
@@ -1683,6 +1683,16 @@ diff-sequences@^29.6.3:
resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz"
integrity sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==
+diff@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
+ integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
+diff@5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz"
+ integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
+
diff2html@^3.1.18:
version "3.4.35"
resolved "https://registry.npmjs.org/diff2html/-/diff2html-3.4.35.tgz"
@@ -1693,16 +1703,6 @@ diff2html@^3.1.18:
optionalDependencies:
highlight.js "11.6.0"
-diff@5.1.0:
- version "5.1.0"
- resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz"
- integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==
-
-diff@^4.0.1:
- version "4.0.2"
- resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz"
- integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
-
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
@@ -1922,7 +1922,7 @@ fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.1.0:
+fast-json-stable-stringify@^2.1.0, fast-json-stable-stringify@2.x:
version "2.1.0"
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
@@ -1951,14 +1951,6 @@ fb-watchman@^2.0.0:
dependencies:
bser "2.1.1"
-fetch-blob@^3.1.2, fetch-blob@^3.1.4:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
- integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
- dependencies:
- node-domexception "^1.0.0"
- web-streams-polyfill "^3.0.3"
-
filelist@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz"
@@ -2022,13 +2014,6 @@ form-data@^4.0.0:
combined-stream "^1.0.8"
mime-types "^2.1.12"
-formdata-polyfill@^4.0.10:
- version "4.0.10"
- resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
- integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
- dependencies:
- fetch-blob "^3.1.2"
-
formidable@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/formidable/-/formidable-2.1.1.tgz"
@@ -2250,7 +2235,7 @@ html-escaper@^2.0.0:
resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz"
integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
-http-errors@2.0.0, http-errors@^2.0.0:
+http-errors@^2.0.0, http-errors@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz"
integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==
@@ -2278,16 +2263,16 @@ iconv-lite@0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
-ieee754@1.1.13:
- version "1.1.13"
- resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz"
- integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
-
ieee754@^1.1.4:
version "1.2.1"
resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
+ieee754@1.1.13:
+ version "1.1.13"
+ resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz"
+ integrity sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==
+
ignore@^5.2.0:
version "5.2.0"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz"
@@ -2319,7 +2304,7 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3, inherits@2, inherits@2.0.4:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -2490,16 +2475,16 @@ is-weakref@^1.0.2:
dependencies:
call-bind "^1.0.2"
-isarray@0.0.1:
- version "0.0.1"
- resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
- integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
-
isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==
+isarray@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+ integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==
+
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
@@ -2836,7 +2821,7 @@ jest-resolve-dependencies@^29.7.0:
jest-regex-util "^29.6.3"
jest-snapshot "^29.7.0"
-jest-resolve@^29.7.0:
+jest-resolve@*, jest-resolve@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz"
integrity sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==
@@ -3023,7 +3008,7 @@ jest-worker@^29.7.0:
merge-stream "^2.0.0"
supports-color "^8.0.0"
-jest@^29.7.0:
+jest@^29.0.0, jest@^29.7.0:
version "29.7.0"
resolved "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz"
integrity sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==
@@ -3071,9 +3056,9 @@ json-schema-traverse@^1.0.0:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
-json5@>=2.2.2, json5@^2.2.3:
+json5@^2.2.3:
version "2.2.3"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+ resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
kleur@^3.0.3:
@@ -3348,21 +3333,21 @@ minimatch@^5.0.1:
dependencies:
brace-expansion "^2.0.1"
-minimist@>=1.2.6, minimist@^1.2.5:
- version "1.2.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
- integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
-
-mkdirp@0.3.0:
- version "0.3.0"
- resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz"
- integrity sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==
+minimist@^1.2.5:
+ version "1.2.6"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz"
+ integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
mkdirp@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+mkdirp@0.3.0:
+ version "0.3.0"
+ resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz"
+ integrity sha512-OHsdUcVAQ6pOtg5JYWpCBo9W/GySVuwvP9hueRMW7UqshC0tbfzLv8wjySTPm3tfUZ/21CE9E1pJagOA91Pxew==
+
moment-timezone@^0.5.35:
version "0.5.41"
resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.41.tgz"
@@ -3370,10 +3355,10 @@ moment-timezone@^0.5.35:
dependencies:
moment "^2.29.4"
-moment@>=2.29.2, moment@^2.27.0, moment@^2.29.1, moment@^2.29.4:
- version "2.30.1"
- resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
- integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
+moment@^2.27.0, moment@^2.29.1, moment@^2.29.4:
+ version "2.29.4"
+ resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz"
+ integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==
ms@2.0.0:
version "2.0.0"
@@ -3415,20 +3400,6 @@ neo-async@^2.6.0:
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-node-domexception@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
- integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
-
-node-fetch@>=2.6.7:
- version "3.3.2"
- resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
- integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
- dependencies:
- data-uri-to-buffer "^4.0.0"
- fetch-blob "^3.1.4"
- formdata-polyfill "^4.0.10"
-
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz"
@@ -3512,7 +3483,14 @@ onetime@^5.1.2:
dependencies:
mimic-fn "^2.1.0"
-p-limit@^2.0.0, p-limit@^2.2.0:
+p-limit@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
+ integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+ dependencies:
+ p-try "^2.0.0"
+
+p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz"
integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
@@ -3636,7 +3614,7 @@ pg-types@^2.1.0:
postgres-date "~1.0.4"
postgres-interval "^1.1.0"
-pg@^8.7.3:
+pg@^8.7.3, pg@>=8.0:
version "8.8.0"
resolved "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz"
integrity sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==
@@ -3774,6 +3752,13 @@ pure-rand@^6.0.0:
resolved "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz"
integrity sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==
+qs@^6.11.0, qs@6.11.0:
+ version "6.11.0"
+ resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz"
+ integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
+ dependencies:
+ side-channel "^1.0.4"
+
qs@6.10.3:
version "6.10.3"
resolved "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz"
@@ -3781,13 +3766,6 @@ qs@6.10.3:
dependencies:
side-channel "^1.0.4"
-qs@6.11.0, qs@^6.11.0:
- version "6.11.0"
- resolved "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz"
- integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
- dependencies:
- side-channel "^1.0.4"
-
querystring@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz"
@@ -3920,7 +3898,7 @@ run-parallel@^1.1.9:
dependencies:
queue-microtask "^1.2.2"
-safe-buffer@5.2.1, safe-buffer@^5.1.2:
+safe-buffer@^5.1.2, safe-buffer@5.2.1:
version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -3935,16 +3913,16 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
-sax@1.2.1:
- version "1.2.1"
- resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
- integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
-
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+sax@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
+ integrity sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA==
+
semver@^6.0.0, semver@^6.3.0, semver@^6.3.1:
version "6.3.1"
resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz"
@@ -3964,7 +3942,17 @@ semver@^7.3.8:
dependencies:
lru-cache "^6.0.0"
-semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
+semver@^7.5.3:
+ version "7.6.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+semver@^7.5.4:
+ version "7.6.3"
+ resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
+ integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
+
+semver@^7.6.3:
version "7.6.3"
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@@ -4101,6 +4089,18 @@ statuses@2.0.1:
resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+ integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
string-length@^4.0.1:
version "4.0.2"
resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz"
@@ -4136,18 +4136,6 @@ string.prototype.trimstart@^1.0.5:
define-properties "^1.1.4"
es-abstract "^1.19.5"
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
- integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==
-
-string_decoder@~1.1.1:
- version "1.1.1"
- resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz"
- integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
- dependencies:
- safe-buffer "~5.1.0"
-
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -4279,7 +4267,7 @@ ts-jest@^29.2.5:
semver "^7.6.3"
yargs-parser "^21.1.1"
-ts-node@^10.8.1:
+ts-node@^10.8.1, ts-node@>=9.0.0:
version "10.9.1"
resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
@@ -4328,7 +4316,7 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
-typescript@^4.8.2:
+typescript@^4.8.2, typescript@>=2.7, "typescript@>=4.3 <6":
version "4.8.2"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz"
integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
@@ -4348,7 +4336,7 @@ unbox-primitive@^1.0.2:
has-symbols "^1.0.3"
which-boxed-primitive "^1.0.2"
-unpipe@1.0.0, unpipe@~1.0.0:
+unpipe@~1.0.0, unpipe@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
@@ -4396,16 +4384,16 @@ utils-merge@1.0.1:
resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz"
integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==
-uuid@8.0.0:
- version "8.0.0"
- resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz"
- integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
-
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+uuid@8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz"
+ integrity sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==
+
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"
@@ -4445,11 +4433,6 @@ watchpack@^2.0.0-beta.10:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
-web-streams-polyfill@^3.0.3:
- version "3.3.3"
- resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
- integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
-
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz"
diff --git a/localserver/bunfig.toml b/localserver/bunfig.toml
new file mode 100644
index 000000000..8779d8ccc
--- /dev/null
+++ b/localserver/bunfig.toml
@@ -0,0 +1 @@
+preload = ["./reflect-metadata-import.ts"]
diff --git a/localserver/express-server.ts b/localserver/express-server.ts
index 16365e0f7..fd105bc64 100644
--- a/localserver/express-server.ts
+++ b/localserver/express-server.ts
@@ -21,7 +21,7 @@ const initExpresss = async () => {
expressServer.use(bodyParser.json());
expressServer.use(bodyParser.urlencoded({ extended: false }));
expressServer.use(cookieParser());
- expressServer.use(cors({ origin: 'http://localhost:3000', credentials: true }));
+ expressServer.use(cors({ origin: 'http://localhost:3000', credentials: true, exposedHeaders: ['X-Message'] }));
expressServer.disable('x-powered-by');
expressServer.set('trust proxy', 1);
diff --git a/localserver/package.json b/localserver/package.json
index bc561a621..e3c35fa42 100644
--- a/localserver/package.json
+++ b/localserver/package.json
@@ -5,7 +5,7 @@
"license": "Apache-2.0",
"author": "SSO Team",
"scripts": {
- "dev": "nodemon --trace-warnings",
+ "dev": "bun --hot server.ts",
"migrate-db": "nodemon --config nodemon-db.json"
},
"devDependencies": {
@@ -26,14 +26,6 @@
"resolutions": {
"minimist": ">=1.2.6"
},
- "nodemonConfig": {
- "watch": [
- "./",
- "../lambda"
- ],
- "ext": "ts,json",
- "exec": "ts-node -r tsconfig-paths/register -r dotenv/config ./server.ts"
- },
"dependencies": {
"axios": "^1.7.7",
"cors": "^2.8.5"
diff --git a/localserver/reflect-metadata-import.ts b/localserver/reflect-metadata-import.ts
new file mode 100644
index 000000000..d2c9bc6e6
--- /dev/null
+++ b/localserver/reflect-metadata-import.ts
@@ -0,0 +1 @@
+import 'reflect-metadata';
diff --git a/localserver/yarn.lock b/localserver/yarn.lock
index b0257e932..814211821 100644
--- a/localserver/yarn.lock
+++ b/localserver/yarn.lock
@@ -195,13 +195,13 @@ binary-extensions@^2.0.0:
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
-body-parser@1.20.1:
- version "1.20.1"
- resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz"
- integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
+body-parser@^1.20.0:
+ version "1.20.2"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz"
+ integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
- content-type "~1.0.4"
+ content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@@ -209,17 +209,17 @@ body-parser@1.20.1:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
- raw-body "2.5.1"
+ raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
-body-parser@^1.20.0:
- version "1.20.2"
- resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz"
- integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
+body-parser@1.20.1:
+ version "1.20.1"
+ resolved "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz"
+ integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
dependencies:
bytes "3.1.2"
- content-type "~1.0.5"
+ content-type "~1.0.4"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@@ -227,7 +227,7 @@ body-parser@^1.20.0:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
- raw-body "2.5.2"
+ raw-body "2.5.1"
type-is "~1.6.18"
unpipe "1.0.0"
@@ -339,13 +339,6 @@ create-require@^1.1.0:
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
-debug@2.6.9:
- version "2.6.9"
- resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
- integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
@@ -353,12 +346,19 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
+debug@2.6.9:
+ version "2.6.9"
+ resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz"
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
-depd@2.0.0, depd@~2.0.0:
+depd@~2.0.0, depd@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz"
integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
@@ -634,9 +634,9 @@ minimatch@^3.1.2:
dependencies:
brace-expansion "^1.1.7"
-minimist@>=1.2.6, minimist@^1.2.6:
+minimist@^1.2.6:
version "1.2.8"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
+ resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
morgan@^1.10.0:
@@ -650,16 +650,16 @@ morgan@^1.10.0:
on-finished "~2.3.0"
on-headers "~1.0.2"
+ms@^2.1.1, ms@2.1.3:
+ version "2.1.3"
+ resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
ms@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-ms@2.1.3, ms@^2.1.1:
- version "2.1.3"
- resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
- integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
-
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz"
@@ -703,13 +703,6 @@ object-inspect@^1.9.0:
resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz"
integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
-on-finished@2.4.1:
- version "2.4.1"
- resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
- integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
- dependencies:
- ee-first "1.1.1"
-
on-finished@~2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"
@@ -717,6 +710,13 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
+on-finished@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz"
+ integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==
+ dependencies:
+ ee-first "1.1.1"
+
on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz"
@@ -946,7 +946,7 @@ type-is@~1.6.18:
media-typer "0.3.0"
mime-types "~2.1.24"
-typescript@^4.8.2:
+typescript@^4.8.2, typescript@>=2.7:
version "4.9.5"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
@@ -956,7 +956,7 @@ undefsafe@^2.0.5:
resolved "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
-unpipe@1.0.0, unpipe@~1.0.0:
+unpipe@~1.0.0, unpipe@1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
diff --git a/package.json b/package.json
index 96af4dfd9..985c04cb5 100644
--- a/package.json
+++ b/package.json
@@ -6,5 +6,6 @@
"author": "SSO Team",
"devDependencies": {
"axios": "^0.27.2"
- }
+ },
+ "dependencies": {}
}
diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl
index b698b6bdb..2ead96017 100644
--- a/terraform/.terraform.lock.hcl
+++ b/terraform/.terraform.lock.hcl
@@ -2,44 +2,44 @@
# Manual edits may be lost in future updates.
provider "registry.terraform.io/hashicorp/aws" {
- version = "5.31.0"
+ version = "5.72.1"
constraints = ">= 3.30.0, >= 4.34.0"
hashes = [
- "h1:ltxyuBWIy9cq0kIKDJH1jeWJy/y7XJLjS4QrsQK4plA=",
- "zh:0cdb9c2083bf0902442384f7309367791e4640581652dda456f2d6d7abf0de8d",
- "zh:2fe4884cb9642f48a5889f8dff8f5f511418a18537a9dfa77ada3bcdad391e4e",
- "zh:36d8bdd72fe61d816d0049c179f495bc6f1e54d8d7b07c45b62e5e1696882a89",
- "zh:539dd156e3ec608818eb21191697b230117437a58587cbd02ce533202a4dd520",
- "zh:6a53f4b57ac4eb3479fc0d8b6e301ca3a27efae4c55d9f8bd24071b12a03361c",
- "zh:6faeb8ff6792ca7af1c025255755ad764667a300291cc10cea0c615479488c87",
- "zh:7d9423149b323f6d0df5b90c4d9029e5455c670aea2a7eb6fef4684ba7eb2e0b",
- "zh:8235badd8a5d0993421cacf5ead48fac73d3b5a25c8a68599706a404b1f70730",
- "zh:860b4f60842b2879c5128b7e386c8b49adeda9287fed12c5cd74861bb659bbcd",
+ "h1:jhd5O5o0CfZCNEwwN0EiDAzb7ApuFrtxJqa6HXW4EKE=",
+ "zh:0dea6843836e926d33469b48b948744079023816d16a2ff7666bcfb6aa3522d4",
+ "zh:195fa9513f75800a0d62797ebec75ee73e9b8c28d713fe9b63d3b1d1eec129b3",
+ "zh:1ed92f3961715bf0e024bcde3c12dfbdc50b00c1f8a43cc00802cfc45a256208",
+ "zh:2ac687e3a52606466cae4a6813e81d923042488df88d2424e28d3f8530f091bb",
+ "zh:32e7ca75f9314557daada3c44628fe1f3bf964a4f833bfb4b2295d833fe64b6f",
+ "zh:374ee0e6b4327cc6ef666908ce5d6450a3a56e90cd2b785e83c2bcfc100021d2",
+ "zh:5500fd6fdac44f96411fcf9c6d01691159ec35455ed127eb4c3a498e1cc92a64",
+ "zh:723a2dc4b064c12e7ee62ad4fbfd72fa5e025206ea47b735994ef53f3c373152",
+ "zh:89d97b87605f1d734f27e642567cbecf785b521af8ea81dac55c77ccde876221",
+ "zh:951ee1e5731e8d65d521d71b95927e55055b3c4656eef6d46fa580a63328befc",
"zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
- "zh:b021fceaf9382c8fe3c6eb608c24d01dce3d11ba7e65bb443d51ca9b90e9b237",
- "zh:b38b0bfc1c69e714e80cf1c9ea06e687ee86aa9f45694be28eb07adcebbe0489",
- "zh:c972d155f6c01af9690a72adfb99cfc24ef5ef311ca92ce46b9b13c5c153f572",
- "zh:e0dd29920ec84fdb6026acff44dcc1fb1a24a0caa093fa04cdbc713d384c651d",
- "zh:e3127ebd2cb0374cd1808f911e6bffe2f4ac4d84317061381242353f3a7bc27d",
+ "zh:9b2b362470b64ec227b2da64762ab8bc4111c6b80365fd9d82fc5e1e33f44038",
+ "zh:aa6e57d0cb974ff0da5dee5d43ad2745cbbc4a2b507d4c799839b9fa96daf688",
+ "zh:ba0d14c4a6b7aa844a830d47c0bf995b632e37f0795394b5b60c638b62b7fc03",
+ "zh:c9764065a9c5d324db0b02bd201b9e3a2118e49c4960884acdeea377173302e9",
]
}
provider "registry.terraform.io/hashicorp/random" {
- version = "3.6.0"
+ version = "3.6.3"
constraints = ">= 2.2.0"
hashes = [
- "h1:I8MBeauYA8J8yheLJ8oSMWqB0kovn16dF/wKZ1QTdkk=",
- "zh:03360ed3ecd31e8c5dac9c95fe0858be50f3e9a0d0c654b5e504109c2159287d",
- "zh:1c67ac51254ba2a2bb53a25e8ae7e4d076103483f55f39b426ec55e47d1fe211",
- "zh:24a17bba7f6d679538ff51b3a2f378cedadede97af8a1db7dad4fd8d6d50f829",
- "zh:30ffb297ffd1633175d6545d37c2217e2cef9545a6e03946e514c59c0859b77d",
- "zh:454ce4b3dbc73e6775f2f6605d45cee6e16c3872a2e66a2c97993d6e5cbd7055",
+ "h1:zG9uFP8l9u+yGZZvi5Te7PV62j50azpgwPunq2vTm1E=",
+ "zh:04ceb65210251339f07cd4611885d242cd4d0c7306e86dda9785396807c00451",
+ "zh:448f56199f3e99ff75d5c0afacae867ee795e4dfda6cb5f8e3b2a72ec3583dd8",
+ "zh:4b4c11ccfba7319e901df2dac836b1ae8f12185e37249e8d870ee10bb87a13fe",
+ "zh:4fa45c44c0de582c2edb8a2e054f55124520c16a39b2dfc0355929063b6395b1",
+ "zh:588508280501a06259e023b0695f6a18149a3816d259655c424d068982cbdd36",
+ "zh:737c4d99a87d2a4d1ac0a54a73d2cb62974ccb2edbd234f333abd079a32ebc9e",
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
- "zh:91df0a9fab329aff2ff4cf26797592eb7a3a90b4a0c04d64ce186654e0cc6e17",
- "zh:aa57384b85622a9f7bfb5d4512ca88e61f22a9cea9f30febaa4c98c68ff0dc21",
- "zh:c4a3e329ba786ffb6f2b694e1fd41d413a7010f3a53c20b432325a94fa71e839",
- "zh:e2699bc9116447f96c53d55f2a00570f982e6f9935038c3810603572693712d0",
- "zh:e747c0fd5d7684e5bfad8aa0ca441903f15ae7a98a737ff6aca24ba223207e2c",
- "zh:f1ca75f417ce490368f047b63ec09fd003711ae48487fba90b4aba2ccf71920e",
+ "zh:a357ab512e5ebc6d1fda1382503109766e21bbfdfaa9ccda43d313c122069b30",
+ "zh:c51bfb15e7d52cc1a2eaec2a903ac2aff15d162c172b1b4c17675190e8147615",
+ "zh:e0951ee6fa9df90433728b96381fb867e3db98f66f735e0c3e24f8f16903f0ad",
+ "zh:e3cdcb4e73740621dabd82ee6a37d6cfce7fee2a03d8074df65086760f5cf556",
+ "zh:eff58323099f1bd9a0bec7cb04f717e7f1b2774c7d612bf7581797e1622613a0",
]
}
diff --git a/terraform/alb.tf b/terraform/alb.tf
index 8a3c0327b..4e484997d 100644
--- a/terraform/alb.tf
+++ b/terraform/alb.tf
@@ -5,29 +5,28 @@ resource "aws_alb" "sso_alb" {
security_groups = [data.aws_security_group.web.id]
subnets = [data.aws_subnet.a.id, data.aws_subnet.b.id]
enable_cross_zone_load_balancing = true
- tags = var.sso_grafana_tags
lifecycle {
ignore_changes = [access_logs]
}
}
-resource "aws_alb_listener" "alb_listener_sso_grafana" {
- count = var.install_sso_css_grafana
+resource "aws_alb_listener" "grafana" {
+ count = var.install_grafana
load_balancer_arn = aws_alb.sso_alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward"
- target_group_arn = aws_alb_target_group.alb_target_group_sso_grafana[count.index].arn
+ target_group_arn = aws_alb_target_group.grafana[count.index].arn
}
}
-resource "aws_alb_target_group" "alb_target_group_sso_grafana" {
- count = var.install_sso_css_grafana
- name = "${var.sso_grafana_name}-tg"
- port = var.sso_grafana_container_port
+resource "aws_alb_target_group" "grafana" {
+ count = var.install_grafana
+ name = "grafana"
+ port = 3000
protocol = "HTTP"
vpc_id = data.aws_vpc.selected.id
target_type = "ip"
@@ -39,8 +38,8 @@ resource "aws_alb_target_group" "alb_target_group_sso_grafana" {
protocol = "HTTP"
matcher = "200"
timeout = "3"
- path = var.sso_grafana_health_check_path
+ path = "/api/health"
unhealthy_threshold = "2"
}
- tags = var.sso_grafana_tags
+ tags = var.grafana_tags
}
diff --git a/terraform/api-gateway-grafana.tf b/terraform/api-gateway-grafana.tf
index 6db364f43..6231a8237 100644
--- a/terraform/api-gateway-grafana.tf
+++ b/terraform/api-gateway-grafana.tf
@@ -1,36 +1,36 @@
-resource "aws_apigatewayv2_api" "sso_grafana_api" {
- count = var.install_sso_css_grafana
- name = var.sso_grafana_name
+resource "aws_apigatewayv2_api" "grafana" {
+ count = var.install_grafana
+ name = "grafana"
protocol_type = "HTTP"
}
-resource "aws_apigatewayv2_vpc_link" "sso_grafana_vpc_link" {
- count = var.install_sso_css_grafana
- name = var.sso_grafana_name
+resource "aws_apigatewayv2_vpc_link" "grafana" {
+ count = var.install_grafana
+ name = "grafana"
subnet_ids = [data.aws_subnet.a.id, data.aws_subnet.b.id]
security_group_ids = [data.aws_security_group.app.id]
}
-resource "aws_apigatewayv2_integration" "sso_grafana_api_integration" {
- count = var.install_sso_css_grafana
- api_id = aws_apigatewayv2_api.sso_grafana_api[count.index].id
+resource "aws_apigatewayv2_integration" "grafana" {
+ count = var.install_grafana
+ api_id = aws_apigatewayv2_api.grafana[count.index].id
integration_type = "HTTP_PROXY"
- connection_id = aws_apigatewayv2_vpc_link.sso_grafana_vpc_link[count.index].id
+ connection_id = aws_apigatewayv2_vpc_link.grafana[count.index].id
connection_type = "VPC_LINK"
integration_method = "ANY"
- integration_uri = aws_alb_listener.alb_listener_sso_grafana[count.index].arn
+ integration_uri = aws_alb_listener.grafana[count.index].arn
}
-resource "aws_apigatewayv2_route" "sso_grafana_route_any" {
- count = var.install_sso_css_grafana
- api_id = aws_apigatewayv2_api.sso_grafana_api[count.index].id
+resource "aws_apigatewayv2_route" "grafana" {
+ count = var.install_grafana
+ api_id = aws_apigatewayv2_api.grafana[count.index].id
route_key = "ANY /{proxy+}"
- target = "integrations/${aws_apigatewayv2_integration.sso_grafana_api_integration[count.index].id}"
+ target = "integrations/${aws_apigatewayv2_integration.grafana[count.index].id}"
}
-resource "aws_apigatewayv2_stage" "sso_grafana_api_default_stage" {
- count = var.install_sso_css_grafana
- api_id = aws_apigatewayv2_api.sso_grafana_api[count.index].id
+resource "aws_apigatewayv2_stage" "grafana" {
+ count = var.install_grafana
+ api_id = aws_apigatewayv2_api.grafana[count.index].id
name = "$default"
auto_deploy = true
}
diff --git a/terraform/api-gateway.tf b/terraform/api-gateway.tf
index 721c6051b..8a884c485 100644
--- a/terraform/api-gateway.tf
+++ b/terraform/api-gateway.tf
@@ -5,6 +5,8 @@
resource "aws_api_gateway_rest_api" "sso_backend" {
name = "SSOApi"
description = "Terraform Serverless Application Example"
+ # Compress for 25Kb and larger responses
+ minimum_compression_size = 25000
endpoint_configuration {
types = ["REGIONAL"]
diff --git a/terraform/ecs.tf b/terraform/ecs.tf
index 97bf3b389..70e4e9e03 100644
--- a/terraform/ecs.tf
+++ b/terraform/ecs.tf
@@ -1,12 +1,18 @@
+locals {
+ grafana_cpu = (var.app_env == "production" ? 256 : 256)
+ grafana_memory = (var.app_env == "production" ? 512 : 512)
+ redis_cpu = (var.app_env == "production" ? 256 : 256)
+ redis_memory = (var.app_env == "production" ? 512 : 512)
+ grafana_port = 3000
+ redis_port = 6379
+}
+
resource "aws_ecs_cluster" "sso_ecs_cluster" {
- count = var.install_sso_css_grafana
- name = "sso-ecs-cluster"
- tags = var.sso_grafana_tags
+ name = "sso-ecs-cluster"
}
resource "aws_ecs_cluster_capacity_providers" "sso_ecs_cluster_capacity_providers" {
- count = var.install_sso_css_grafana
- cluster_name = aws_ecs_cluster.sso_ecs_cluster[count.index].name
+ cluster_name = aws_ecs_cluster.sso_ecs_cluster.name
capacity_providers = ["FARGATE_SPOT"]
default_capacity_provider_strategy {
weight = 100
@@ -14,17 +20,17 @@ resource "aws_ecs_cluster_capacity_providers" "sso_ecs_cluster_capacity_provider
}
}
-resource "aws_ecs_task_definition" "sso_grafana_task_definition" {
- count = var.install_sso_css_grafana
- depends_on = [aws_apigatewayv2_api.sso_grafana_api]
- family = var.sso_grafana_name
- execution_role_arn = aws_iam_role.ecs_sso_grafana_task_execution_role[0].arn
- task_role_arn = aws_iam_role.sso_grafana_container_role[count.index].arn
+resource "aws_ecs_task_definition" "grafana" {
+ count = var.install_grafana
+ depends_on = [aws_apigatewayv2_api.grafana]
+ family = "grafana"
+ execution_role_arn = aws_iam_role.grafana_task_execution_role[0].arn
+ task_role_arn = aws_iam_role.grafana_container_role[count.index].arn
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
- cpu = var.sso_grafana_fargate_cpu
- memory = var.sso_grafana_fargate_memory
- tags = var.sso_grafana_tags
+ cpu = local.grafana_cpu
+ memory = local.grafana_memory
+ tags = var.grafana_tags
volume {
name = "sso-grafana-data"
efs_volume_configuration {
@@ -38,17 +44,18 @@ resource "aws_ecs_task_definition" "sso_grafana_task_definition" {
}
container_definitions = jsonencode([
{
- essential = true
- name = var.sso_grafana_container_name
- image = "${var.aws_ecr_uri}/${var.sso_grafana_container_image}"
- cpu = var.sso_grafana_fargate_cpu
- memory = var.sso_grafana_fargate_memory
- networkMode = "awsvpc"
+ essential = true
+ name = "grafana"
+ image = "${var.aws_ecr_uri}/bcgov-sso/grafana:10.2.2"
+ cpu = local.grafana_cpu
+ memory = local.grafana_memory
+ readonlyRootFilesystem = true
+ networkMode = "awsvpc"
portMappings = [
{
protocol = "tcp"
- containerPort = var.sso_grafana_container_port
- hostPort = var.sso_grafana_container_port
+ containerPort = local.grafana_port
+ hostPort = local.grafana_port
}
]
environment = [
@@ -61,7 +68,7 @@ resource "aws_ecs_task_definition" "sso_grafana_task_definition" {
logDriver = "awslogs"
options = {
awslogs-create-group = "true"
- awslogs-group = "/ecs/${var.sso_grafana_name}"
+ awslogs-group = "/ecs/grafana"
awslogs-region = "ca-central-1"
awslogs-stream-prefix = "ecs"
}
@@ -80,11 +87,11 @@ resource "aws_ecs_task_definition" "sso_grafana_task_definition" {
},
{
name = "GF_SERVER_DOMAIN",
- value = "${aws_apigatewayv2_api.sso_grafana_api[count.index].id}.execute-api.ca-central-1.amazonaws.com"
+ value = "${aws_apigatewayv2_api.grafana[count.index].id}.execute-api.ca-central-1.amazonaws.com"
},
{
name = "GF_SERVER_ROOT_URL",
- value = "https://${aws_apigatewayv2_api.sso_grafana_api[count.index].id}.execute-api.ca-central-1.amazonaws.com"
+ value = "https://${aws_apigatewayv2_api.grafana[count.index].id}.execute-api.ca-central-1.amazonaws.com"
},
{
name = "GF_AUTH_GENERIC_OAUTH_NAME",
@@ -146,27 +153,27 @@ resource "aws_ecs_task_definition" "sso_grafana_task_definition" {
secrets = [
{
name = "GF_SECURITY_ADMIN_PASSWORD",
- valueFrom = "${data.aws_secretsmanager_secret_version.sso_grafana_secret[0].arn}:GF_SECURITY_ADMIN_PASSWORD::"
+ valueFrom = "${data.aws_secretsmanager_secret_version.grafana_secret[0].arn}:GF_SECURITY_ADMIN_PASSWORD::"
},
{
name = "GF_AUTH_GENERIC_OAUTH_CLIENT_ID",
- valueFrom = "${data.aws_secretsmanager_secret_version.sso_grafana_secret[0].arn}:GF_AUTH_GENERIC_OAUTH_CLIENT_ID::"
+ valueFrom = "${data.aws_secretsmanager_secret_version.grafana_secret[0].arn}:GF_AUTH_GENERIC_OAUTH_CLIENT_ID::"
},
{
name = "GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET",
- valueFrom = "${data.aws_secretsmanager_secret_version.sso_grafana_secret[0].arn}:GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET::"
+ valueFrom = "${data.aws_secretsmanager_secret_version.grafana_secret[0].arn}:GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET::"
},
]
}
])
}
-resource "aws_ecs_service" "sso_grafana_service" {
- count = var.install_sso_css_grafana
- name = var.sso_grafana_name
- cluster = aws_ecs_cluster.sso_ecs_cluster[count.index].id
- task_definition = aws_ecs_task_definition.sso_grafana_task_definition[count.index].arn
- desired_count = var.install_sso_css_grafana
+resource "aws_ecs_service" "grafana" {
+ count = var.install_grafana
+ name = "grafana"
+ cluster = aws_ecs_cluster.sso_ecs_cluster.id
+ task_definition = aws_ecs_task_definition.grafana[count.index].arn
+ desired_count = 1
enable_ecs_managed_tags = true
propagate_tags = "TASK_DEFINITION"
health_check_grace_period_seconds = 60
@@ -186,17 +193,96 @@ resource "aws_ecs_service" "sso_grafana_service" {
}
load_balancer {
- target_group_arn = aws_alb_target_group.alb_target_group_sso_grafana[count.index].id
- container_name = var.sso_grafana_container_name
- container_port = var.sso_grafana_container_port
+ target_group_arn = aws_alb_target_group.grafana[count.index].id
+ container_name = "grafana"
+ container_port = local.grafana_port
}
- depends_on = [aws_iam_role_policy_attachment.ecs_sso_grafana_task_role_policy_attachment]
+ depends_on = [aws_iam_role_policy_attachment.grafana]
- tags = var.sso_grafana_tags
+ tags = var.grafana_tags
}
-data "aws_secretsmanager_secret_version" "sso_grafana_secret" {
- count = var.install_sso_css_grafana
+data "aws_secretsmanager_secret_version" "grafana_secret" {
+ count = var.install_grafana
secret_id = "sso-grafana"
}
+
+resource "aws_ecs_task_definition" "redis" {
+ count = var.install_redis
+ depends_on = [aws_lambda_function.app]
+ family = "redis"
+ execution_role_arn = aws_iam_role.redis_task_execution_role[0].arn
+ task_role_arn = aws_iam_role.redis_container_role[count.index].arn
+ network_mode = "awsvpc"
+ requires_compatibilities = ["FARGATE"]
+ cpu = local.redis_cpu
+ memory = local.redis_memory
+ tags = var.redis_tags
+ container_definitions = jsonencode([
+ {
+ essential = true
+ name = "redis"
+ image = "public.ecr.aws/docker/library/redis:latest"
+ cpu = local.redis_cpu
+ memory = local.redis_memory
+ readonlyRootFilesystem = true
+ networkMode = "awsvpc"
+ portMappings = [
+ {
+ protocol = "tcp"
+ containerPort = local.redis_port
+ hostPort = local.redis_port
+ }
+ ]
+ environment = [
+ {
+ name = "AWS_REGION",
+ value = "ca-central-1"
+ }
+ ]
+ logConfiguration = {
+ logDriver = "awslogs"
+ options = {
+ awslogs-create-group = "true"
+ awslogs-group = "/ecs/redis"
+ awslogs-region = "ca-central-1"
+ awslogs-stream-prefix = "ecs"
+ }
+ }
+ }
+ ])
+}
+
+resource "aws_ecs_service" "redis" {
+ count = var.install_redis
+ name = "redis"
+ cluster = aws_ecs_cluster.sso_ecs_cluster.id
+ task_definition = aws_ecs_task_definition.redis[count.index].arn
+ desired_count = var.install_redis
+ enable_ecs_managed_tags = true
+ propagate_tags = "TASK_DEFINITION"
+ health_check_grace_period_seconds = 60
+ wait_for_steady_state = false
+
+
+ capacity_provider_strategy {
+ capacity_provider = "FARGATE_SPOT"
+ weight = 100
+ }
+
+
+ network_configuration {
+ security_groups = [data.aws_security_group.app.id]
+ subnets = [data.aws_subnet.a.id, data.aws_subnet.b.id]
+ assign_public_ip = false
+ }
+
+ load_balancer {
+ target_group_arn = aws_lb_target_group.redis[count.index].id
+ container_name = "redis"
+ container_port = local.redis_port
+ }
+
+ tags = var.redis_tags
+}
diff --git a/terraform/efs.tf b/terraform/efs.tf
index a26837b0b..3200c455e 100644
--- a/terraform/efs.tf
+++ b/terraform/efs.tf
@@ -1,5 +1,5 @@
resource "aws_efs_file_system" "efs_sso_grafana" {
- count = var.install_sso_css_grafana
+ count = var.install_grafana
creation_token = "efs-sso-grafana"
encrypted = true
@@ -7,26 +7,26 @@ resource "aws_efs_file_system" "efs_sso_grafana" {
{
Name = "efs-sso-grafana"
},
- var.sso_grafana_tags
+ var.grafana_tags
)
}
resource "aws_efs_mount_target" "efs_sso_grafana_azA" {
- count = var.install_sso_css_grafana
+ count = var.install_grafana
file_system_id = aws_efs_file_system.efs_sso_grafana[count.index].id
subnet_id = data.aws_subnet.a_data.id
security_groups = [data.aws_security_group.app.id]
}
resource "aws_efs_mount_target" "efs_sso_grafana_azB" {
- count = var.install_sso_css_grafana
+ count = var.install_grafana
file_system_id = aws_efs_file_system.efs_sso_grafana[count.index].id
subnet_id = data.aws_subnet.b_data.id
security_groups = [data.aws_security_group.app.id]
}
resource "aws_efs_backup_policy" "efs_sso_grafana_backups_policy" {
- count = var.install_sso_css_grafana
+ count = var.install_grafana
file_system_id = aws_efs_file_system.efs_sso_grafana[count.index].id
backup_policy {
@@ -35,7 +35,7 @@ resource "aws_efs_backup_policy" "efs_sso_grafana_backups_policy" {
}
resource "aws_efs_access_point" "sso_grafana_efs_access_point" {
- count = var.install_sso_css_grafana
+ count = var.install_grafana
file_system_id = aws_efs_file_system.efs_sso_grafana[count.index].id
root_directory {
@@ -52,7 +52,7 @@ resource "aws_efs_access_point" "sso_grafana_efs_access_point" {
{
Name = "sso-grafana-data"
},
- var.sso_grafana_tags
+ var.grafana_tags
)
}
diff --git a/terraform/lambda-app.tf b/terraform/lambda-app.tf
index 16e4516a6..71e7be7f5 100644
--- a/terraform/lambda-app.tf
+++ b/terraform/lambda-app.tf
@@ -62,9 +62,12 @@ resource "aws_lambda_function" "app" {
BCSC_REGISTRATION_BASE_URL_DEV = var.bcsc_registration_base_url_dev
BCSC_REGISTRATION_BASE_URL_TEST = var.bcsc_registration_base_url_test
BCSC_REGISTRATION_BASE_URL_PROD = var.bcsc_registration_base_url_prod
+ REDIS_HOST = var.install_redis == 1 ? aws_lb.redis_nlb[0].dns_name : ""
}
}
+ depends_on = [aws_lb.redis_nlb]
+
timeout = 30 # up to 900 seconds (15 minutes)
memory_size = 240 # 128 MB to 10,240 MB, in 1-MB increments
ephemeral_storage {
diff --git a/terraform/lambda-css-api.tf b/terraform/lambda-css-api.tf
index f5bdeca86..df58de869 100644
--- a/terraform/lambda-css-api.tf
+++ b/terraform/lambda-css-api.tf
@@ -46,6 +46,9 @@ resource "aws_lambda_function" "css_api" {
CHES_PASSWORD = var.ches_password
CHES_USERNAME = var.ches_username
GOLD_IP_ADDRESS = var.gold_ip_address
+ REDIS_HOST = var.install_redis == 1 ? aws_lb.redis_nlb[0].dns_name : ""
+ GRAFANA_API_TOKEN = var.grafana_api_token
+ GRAFANA_API_URL = var.grafana_api_url
}
}
diff --git a/terraform/lambda-request-queue.tf b/terraform/lambda-request-queue.tf
index 3c7209613..41ac26c29 100644
--- a/terraform/lambda-request-queue.tf
+++ b/terraform/lambda-request-queue.tf
@@ -19,7 +19,7 @@ resource "aws_lambda_function" "request_queue" {
environment {
variables = {
- NODE_ENV = "production"
+ NODE_ENV = var.app_env
DB_HOSTNAME = module.db.this_rds_cluster_endpoint
DB_USERNAME = var.db_username
DB_PASSWORD = random_password.db_password.result
@@ -46,6 +46,7 @@ resource "aws_lambda_function" "request_queue" {
BCSC_REGISTRATION_BASE_URL_DEV = var.bcsc_registration_base_url_dev
BCSC_REGISTRATION_BASE_URL_TEST = var.bcsc_registration_base_url_test
BCSC_REGISTRATION_BASE_URL_PROD = var.bcsc_registration_base_url_prod
+ RC_WEBHOOK = var.rc_webhook
}
}
diff --git a/terraform/main.tf b/terraform/main.tf
index 5877c69db..70099c36c 100644
--- a/terraform/main.tf
+++ b/terraform/main.tf
@@ -65,7 +65,7 @@ data "aws_security_group" "web" {
name = "Web_sg"
}
-# resource "aws_security_group" "ecs_sso_grafana_sg" {
+# resource "aws_security_group" "grafana" {
# name = "ecs-sso-grafana-sg"
# description = "Allow inbound access from the ALB only"
# vpc_id = module.network.aws_vpc.id
@@ -73,8 +73,8 @@ data "aws_security_group" "web" {
# ingress {
# description = "Only from alb"
# protocol = "tcp"
-# from_port = var.sso_grafana_container_port
-# to_port = var.sso_grafana_container_port
+# from_port = 3000
+# to_port = 3000
# security_groups = [data.aws_security_group.app.id]
# }
@@ -86,5 +86,5 @@ data "aws_security_group" "web" {
# cidr_blocks = ["0.0.0.0/0"]
# }
-# tags = var.sso_grafana_tags
+# tags = var.grafana_tags
# }
diff --git a/terraform/nlb.tf b/terraform/nlb.tf
new file mode 100644
index 000000000..5d501ee2e
--- /dev/null
+++ b/terraform/nlb.tf
@@ -0,0 +1,38 @@
+resource "aws_lb" "redis_nlb" {
+ count = var.install_redis
+ name = "redis-nlb"
+ internal = true
+ load_balancer_type = "network"
+ security_groups = [data.aws_security_group.app.id, aws_security_group.rds_sg.id]
+ subnets = [data.aws_subnet.a.id, data.aws_subnet.b.id]
+}
+
+resource "aws_lb_target_group" "redis" {
+ count = var.install_redis
+ name = "redis"
+ port = 6379
+ protocol = "TCP"
+ vpc_id = data.aws_vpc.selected.id
+ target_type = "ip"
+
+ health_check {
+ healthy_threshold = 3
+ unhealthy_threshold = 3
+ timeout = 5
+ interval = 30
+ protocol = "TCP"
+ }
+ tags = var.redis_tags
+}
+
+resource "aws_lb_listener" "redis" {
+ count = var.install_redis
+ load_balancer_arn = aws_lb.redis_nlb[count.index].arn
+ port = 6379
+ protocol = "TCP"
+
+ default_action {
+ type = "forward"
+ target_group_arn = aws_lb_target_group.redis[count.index].arn
+ }
+}
diff --git a/terraform/roles.tf b/terraform/roles.tf
index 70df71943..66da4de9e 100644
--- a/terraform/roles.tf
+++ b/terraform/roles.tf
@@ -1,6 +1,5 @@
# ECS task execution role data
-data "aws_iam_policy_document" "ecs_sso_grafana_task_execution_role" {
- count = var.install_sso_css_grafana
+data "aws_iam_policy_document" "ecs_task_execution_role" {
version = "2012-10-17"
statement {
sid = ""
@@ -14,35 +13,37 @@ data "aws_iam_policy_document" "ecs_sso_grafana_task_execution_role" {
}
}
-data "aws_iam_policy" "iam_sso_grafana_read_secret_policy" {
- count = var.install_sso_css_grafana
+data "aws_iam_policy" "grafana_read_secret" {
+ count = var.install_grafana
name = "SSOPathfinderReadGrafanaSecret"
}
-# ECS task execution role
-resource "aws_iam_role" "ecs_sso_grafana_task_execution_role" {
- count = var.install_sso_css_grafana
- name = "SSODefaultECSTaskExecutionRole"
- assume_role_policy = data.aws_iam_policy_document.ecs_sso_grafana_task_execution_role[0].json
+# Grafana ECS task execution role
+resource "aws_iam_role" "grafana_task_execution_role" {
+ count = var.install_grafana
+ name = "GrafanaECSTaskExecutionRole"
+ assume_role_policy = data.aws_iam_policy_document.ecs_task_execution_role.json
- tags = var.sso_grafana_tags
+ tags = var.grafana_tags
}
+
+
# Attaching task execution and read from RDS policies to task execution role
-resource "aws_iam_role_policy_attachment" "ecs_sso_grafana_task_role_policy_attachment" {
- role = aws_iam_role.ecs_sso_grafana_task_execution_role[0].name
- for_each = var.install_sso_css_grafana == 1 ? toset([
+resource "aws_iam_role_policy_attachment" "grafana" {
+ role = aws_iam_role.grafana_task_execution_role[0].name
+ for_each = var.install_grafana == 1 ? toset([
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
"arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess",
- data.aws_iam_policy.iam_sso_grafana_read_secret_policy[0].arn # secret and policy manually created in AWS
+ data.aws_iam_policy.grafana_read_secret[0].arn # secret and policy manually created in AWS
]) : toset([])
policy_arn = each.value
}
-resource "aws_iam_role_policy" "ecs_sso_grafana_task_execution_cwlogs" {
- count = var.install_sso_css_grafana
- name = "ecs-sso-grafana-task-exec-cwlogs"
- role = aws_iam_role.ecs_sso_grafana_task_execution_role[0].id
+resource "aws_iam_role_policy" "grafana_task_execution_cwlogs" {
+ count = var.install_grafana
+ name = "grafana-task-exec-cwlogs"
+ role = aws_iam_role.grafana_task_execution_role[0].id
policy = <<-EOF
{
@@ -62,9 +63,9 @@ resource "aws_iam_role_policy" "ecs_sso_grafana_task_execution_cwlogs" {
EOF
}
-resource "aws_iam_role" "sso_grafana_container_role" {
- count = var.install_sso_css_grafana
- name = "sso-grafana-container-role"
+resource "aws_iam_role" "grafana_container_role" {
+ count = var.install_grafana
+ name = "grafana-container-role"
assume_role_policy = <Dear Pathfinder SSO friend,