diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 06bc32ddfc..a19db50dca 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -28,7 +28,7 @@ jobs: run: heroku container:login - name: Release Backend on Heroku - uses: akhileshns/heroku-deploy@c3187cbbeceea824a6f5d9e0e14e2995a611059c + uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: mitopen-production @@ -61,6 +61,7 @@ jobs: LEARN_AI_RECOMMENDATION_ENDPOINT: ${{ secrets.LEARN_AI_RECOMMENDATION_ENDPOINT_PROD }} LEARN_AI_SYLLABUS_ENDPOINT: ${{ secrets.LEARN_AI_SYLLABUS_ENDPOINT_PROD }} VERSION: ${{ github.sha }} + MITOL_API_LOGOUT_SUFFIX: ${{ secrets.MITOL_API_LOGOUT_SUFFIX_PROD }} run: | heroku container:push web \ --app $HEROKU_APP_NAME \ @@ -83,7 +84,8 @@ jobs: NEXT_PUBLIC_APPZI_URL=$APPZI_URL,\ NEXT_PUBLIC_LEARN_AI_RECOMMENDATION_ENDPOINT=$LEARN_AI_RECOMMENDATION_ENDPOINT,\ NEXT_PUBLIC_LEARN_AI_SYLLABUS_ENDPOINT=$LEARN_AI_SYLLABUS_ENDPOINT,\ - NEXT_PUBLIC_VERSION=$VERSION \ + NEXT_PUBLIC_VERSION=$VERSION, \ + NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX=$MITOL_API_LOGOUT_SUFFIX \ --context-path . - name: Release Frontend on Heroku diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 068bd4ef26..8822dec324 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -28,7 +28,7 @@ jobs: run: heroku container:login - name: Release Backend on Heroku - uses: akhileshns/heroku-deploy@c3187cbbeceea824a6f5d9e0e14e2995a611059c + uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: mitopen-rc @@ -61,6 +61,7 @@ jobs: LEARN_AI_RECOMMENDATION_ENDPOINT: ${{ secrets.LEARN_AI_RECOMMENDATION_ENDPOINT_RC }} LEARN_AI_SYLLABUS_ENDPOINT: ${{ secrets.LEARN_AI_SYLLABUS_ENDPOINT_RC }} VERSION: ${{ github.sha }} + MITOL_API_LOGOUT_SUFFIX: ${{ secrets.MITOL_API_LOGOUT_SUFFIX_RC }} run: | heroku container:push web \ --app $HEROKU_APP_NAME \ @@ -83,7 +84,8 @@ jobs: NEXT_PUBLIC_APPZI_URL=$APPZI_URL,\ NEXT_PUBLIC_LEARN_AI_RECOMMENDATION_ENDPOINT=$LEARN_AI_RECOMMENDATION_ENDPOINT,\ NEXT_PUBLIC_LEARN_AI_SYLLABUS_ENDPOINT=$LEARN_AI_SYLLABUS_ENDPOINT,\ - NEXT_PUBLIC_VERSION=$VERSION \ + NEXT_PUBLIC_VERSION=$VERSION, \ + NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX=$MITOL_API_LOGOUT_SUFFIX \ --context-path . - name: Release Frontend on Heroku diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41e14e3e39..1eca72c169 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -72,6 +72,10 @@ repos: - yarn.lock - --exclude-files - ".*/generated/" + - --exclude-files + - "config/keycloak/tls/*" + - --exclude-files + - "config/keycloak/realms/default-realm.json" additional_dependencies: ["gibberish-detector"] - repo: https://github.com/astral-sh/ruff-pre-commit rev: "v0.9.4" diff --git a/.secrets.baseline b/.secrets.baseline index 685a624e47..058e26097a 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -100,7 +100,8 @@ "test_.*.py", "poetry.lock", "yarn.lock", - ".*/generated/" + ".*/generated/", + "config/keycloak/tls/*" ] } ], diff --git a/Dockerfile b/Dockerfile index 8e0ac5aa7a..843aa72320 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ RUN poetry install USER root COPY . /src WORKDIR /src -RUN mkdir /src/staticfiles +RUN mkdir -p /src/staticfiles RUN apt-get clean && apt-get purge diff --git a/Dockerfile-litellm b/Dockerfile-litellm deleted file mode 100644 index 6fa3f3ad4e..0000000000 --- a/Dockerfile-litellm +++ /dev/null @@ -1,18 +0,0 @@ -# Use the provided base image -FROM ghcr.io/berriai/litellm:main-latest - -# Set the working directory to /app -WORKDIR /app - - -# Make sure your docker/entrypoint.sh is executable -RUN chmod +x ./docker/entrypoint.sh - -# Expose the necessary port -EXPOSE 4000/tcp - -# Override the CMD instruction with your desired command and arguments -# WARNING: FOR PROD DO NOT USE `--detailed_debug` it slows down response times, instead use the following CMD -# CMD ["--port", "4000", "--config", "config.yaml"] - -CMD ["--port", "4000", "--config", "litellm_config.yml"] diff --git a/README-keycloak.md b/README-keycloak.md new file mode 100644 index 0000000000..a3c8691697 --- /dev/null +++ b/README-keycloak.md @@ -0,0 +1,35 @@ +# Keycloak and APISIX Integration + +The "docker-compose.services.yml" file includes Keycloak and APISIX containers that you can use for authentication instead of spinning up separate ones or using the deployed instances. It's not enabled by default, but you can run it if you prefer not to run your own Keycloak/APISIX instances. + +## Default Settings + +There are some defaults that are part of this. + +_SSL Certificate_: There's a self-signed cert that's in `config/keycloak/tls` - if you'd rather set up your own (or you have a real cert or something to use), you can drop the PEM files in there. See the README there for info. + +_Realm_: There's a `default-realm.json` in `config/keycloak` that will get loaded by Keycloak when it starts up, and will set up a realm for you with some users and a client so you don't have to set it up yourself. The realm it creates is called `ol-local`. + +The users it sets up are: + +| User | Password | +| ------------------- | --------- | +| `student@odl.local` | `student` | +| `prof@odl.local` | `prof` | +| `admin@odl.local` | `admin` | + +The client it sets up is called `apisix`. You can change the passwords and get the secret in the admin. + +## Making it Work + +The Keycloak instance is part of the `keycloak` profile in the Composer file, so if you want to interact with it, you'll need to run `COMPOSE_PROFILES=backend,frontend,keycloak,apisix docker compose up`. (If you start the app without the profile, you can still start Keycloak later by specifying the profile.) + +If you want to use the Keycloak and APISIX instances, follow these steps: + +1. Change the value of `MITOL_API_BASE_URL` to `http://api.open.odl.local:8065` and `MITOL_API_LOGOUT_SUFFIX` to `logout/oidc` in your `shared.local.env` file. +2. Add `MITOL_NEW_USER_LOGIN_URL=http://open.odl.local:8062/onboarding` to your `shared.local.env` file +3. Copy all the env values under the "# APISIX/Keycloak " section of `backend.local.example.env` to your `backend.local.env` file. You can leave all the values as is. +4. Keycloak needs to create its own database, which will only happen if you first destroy your current mit-learn database container: `docker compose down db`. If you prefer not to do this, you can manually create it by running the SQL in `config/postgres/init-keycloak.sql` in a postgres shell. +5. Start containers with the command `COMPOSE_PROFILES=backend,frontend,keycloak,apisix docker compose up` + +The Keycloak and APISIX containers should start up and stay running. APISIX is on port 8065, Keycloak on port 8066. Now you should be able to log in at `https://open.odl.local:8065/login` with one of the users mentioned above, or just click "Log in" from the home page at http://open.odl.local:8062. Try logging out and back in a couple times to make sure it works. diff --git a/RELEASE.rst b/RELEASE.rst index 167e9af078..360b743bcf 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,28 @@ Release Notes ============= +Version 0.30.7 +-------------- + +- Add comma between build args (#2083) +- Fix the user search URL (#2084) +- Tie chatbots to URL parameters (#2076) +- Add all Contentfile metadata to chunk responses (#2075) +- Make embedding generation task use correct run (#2074) +- add MITOL_LOGOUT_SUFFIX to github actions (#2079) +- Fix user migrations for SCIM (#2078) +- Fix SCIM view tests (#2073) +- Accessibility improvements (#2071) +- APISIX integration (#2061) +- Added SCIM fields to User and populate (#2062) +- Fix SCIM search API sort and pagination (#2066) +- fix: Opensearch container on ARM64 based architecture (#2069) +- Update dependency @dnd-kit/sortable to v10 (#1974) +- Update akhileshns/heroku-deploy digest to e3eb99d (#2068) +- Update dependency @sentry/nextjs to v9 (#2034) +- Update dependency @mui/lab to v6.0.0-beta.28 (#2051) +- Update dependency tldextract to v5 (#2031) + Version 0.30.6 (Released February 26, 2025) -------------- diff --git a/config/apisix/apisix.yaml b/config/apisix/apisix.yaml new file mode 100644 index 0000000000..5a23bdb245 --- /dev/null +++ b/config/apisix/apisix.yaml @@ -0,0 +1,79 @@ +upstreams: + - id: 1 + nodes: + "nginx:${{NGINX_PORT}}": 1 + type: roundrobin + +routes: + - id: 1 + name: "passauth" + desc: "Wildcard route that can use auth but doesn't require it." + priority: 0 + upstream_id: 1 + plugins: + openid-connect: + client_id: ${{KEYCLOAK_CLIENT_ID}} + client_secret: ${{KEYCLOAK_CLIENT_SECRET}} + discovery: ${{KEYCLOAK_DISCOVERY_URL}} + realm: ${{KEYCLOAK_REALM_NAME}} + scope: ${{KEYCLOAK_SCOPES}} + bearer_only: false + introspection_endpoint_auth_method: "client_secret_post" + ssl_verify: false + session: + secret: ${{APISIX_SESSION_SECRET_KEY}} + logout_path: "/logout/oidc" + post_logout_redirect_uri: ${{APISIX_LOGOUT_URL}} + unauth_action: "pass" + cors: + allow_origins: "**" + allow_methods: "**" + allow_headers: "**" + allow_credential: true + response-rewrite: + headers: + set: + Referrer-Policy: "origin" + uri: "*" + - id: 2 + name: "logout-redirect" + desc: "Strip trailing slash from logout redirect." + priority: 10 + upstream_id: 1 + uri: "/logout/oidc/*" + plugins: + redirect: + uri: "/logout/oidc" + - id: 3 + name: "reqauth" + desc: "Routes that require authentication." + priority: 10 + upstream_id: 1 + plugins: + openid-connect: + client_id: ${{KEYCLOAK_CLIENT_ID}} + client_secret: ${{KEYCLOAK_CLIENT_SECRET}} + discovery: ${{KEYCLOAK_DISCOVERY_URL}} + realm: ${{KEYCLOAK_REALM_NAME}} + scope: ${{KEYCLOAK_SCOPES}} + bearer_only: false + introspection_endpoint_auth_method: "client_secret_post" + ssl_verify: false + session: + secret: ${{APISIX_SESSION_SECRET_KEY}} + logout_path: "/logout/oidc" + post_logout_redirect_uri: ${{APISIX_LOGOUT_URL}} + unauth_action: "auth" + cors: + allow_origins: "**" + allow_methods: "**" + allow_headers: "**" + allow_credential: true + response-rewrite: + headers: + set: + Referrer-Policy: "origin" + uris: + - "/admin/login/*" + - "/login/*" +#END diff --git a/config/apisix/config.yaml b/config/apisix/config.yaml new file mode 100644 index 0000000000..7fa7131789 --- /dev/null +++ b/config/apisix/config.yaml @@ -0,0 +1,11 @@ +apisix: + enable_admin: false + enable_dev_mode: false + node_listen: + - port: ${{APISIX_PORT}} + +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +#END diff --git a/config/apisix/debug.yaml b/config/apisix/debug.yaml new file mode 100755 index 0000000000..7dcf3bf633 --- /dev/null +++ b/config/apisix/debug.yaml @@ -0,0 +1,35 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +basic: + enable: true # Enable the basic debug mode. +http_filter: + enable: false # Enable HTTP filter to dynamically apply advanced debug settings. + enable_header_name: X-APISIX-Dynamic-Debug # If the header is present in a request, apply the advanced debug settings. +hook_conf: + enable: false # Enable hook debug trace to log the target module function's input arguments or returned values. + name: hook_phase # Name of module and function list. + log_level: warn # Severity level for input arguments and returned values in the error log. + is_print_input_args: true # Print the input arguments. + is_print_return_value: true # Print the return value. + +hook_phase: # Name of module and function list. + apisix: # Required module name. + - http_access_phase # Required function names. + - http_header_filter_phase + - http_body_filter_phase + - http_log_phase +#END diff --git a/config/keycloak/providers/README.md b/config/keycloak/providers/README.md new file mode 100644 index 0000000000..8a25df3bf7 --- /dev/null +++ b/config/keycloak/providers/README.md @@ -0,0 +1 @@ +Place the SCIM plugin here if you intend to run it locally. diff --git a/config/keycloak/realms/default-realm.json b/config/keycloak/realms/default-realm.json new file mode 100644 index 0000000000..bafc77c100 --- /dev/null +++ b/config/keycloak/realms/default-realm.json @@ -0,0 +1,2469 @@ +{ + "id": "160c333f-6b79-44a1-8bfc-e9ca019584bb", + "realm": "ol-local", + "displayName": "OL Local", + "displayNameHtml": "", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "none", + "registrationAllowed": false, + "registrationEmailAsUsername": true, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "b3fca566-95d1-4814-800c-f8af17b6af6e", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "160c333f-6b79-44a1-8bfc-e9ca019584bb", + "attributes": {} + }, + { + "id": "56179262-280a-46e6-995b-9610be72b7a6", + "name": "default-roles-ol-local", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": ["offline_access", "uma_authorization"], + "client": { + "account": ["manage-account", "view-profile"] + } + }, + "clientRole": false, + "containerId": "160c333f-6b79-44a1-8bfc-e9ca019584bb", + "attributes": {} + }, + { + "id": "baf79b99-45ff-49ba-887b-ed07153e136d", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "160c333f-6b79-44a1-8bfc-e9ca019584bb", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "11216038-57fe-4495-8b40-841b38d7b919", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "9c47dea9-e906-4c70-9d30-4a12ebff4a44", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "df95418b-61c7-47bc-987f-871b41b6dde9", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "a6fdc983-8f30-4b63-afb9-26cd44ba2165", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "26160c3e-24f8-4669-9c1b-8f807b40a651", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "d2ee37c5-cfd4-4cb2-90ae-884b94c45dce", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "8554afaa-6112-4ce7-9bca-4bbce0e8592e", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "16c2b432-4af8-4425-9e4a-0feda3c1b60e", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "f54d645d-75ff-4320-bb57-fabacc0bb2a9", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-clients"] + } + }, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "27667873-9c44-4a86-8ff9-48929a85362b", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": ["query-users", "query-groups"] + } + }, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "9d301320-afd6-4fac-8819-beee0b2e66cd", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "94a9242b-c1f7-43f5-b9f8-5b48a357735d", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "dc6e49b1-7e0a-478f-a371-b2538018ac05", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "c783b4e9-7f19-460d-981f-f8c90abffd05", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-realm", + "manage-identity-providers", + "create-client", + "view-identity-providers", + "query-groups", + "impersonation", + "view-authorization", + "manage-users", + "view-clients", + "view-users", + "manage-events", + "query-users", + "query-clients", + "manage-authorization", + "view-events", + "view-realm", + "manage-clients", + "query-realms" + ] + } + }, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "154e2f15-9841-436f-b3b1-649a2b1d835e", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "9970885a-dad6-4459-b3ef-c26c7dec138b", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "df55ee3b-3e32-4431-90c9-2de1b48b9000", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "0e3bd9f7-9f92-4ae6-a806-89fb31e782d7", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + }, + { + "id": "baee37bc-db98-48d0-84ac-b07b58769f7d", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "d81cd727-c2f4-4648-85f8-60b48d812610", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "apisix": [ + { + "id": "5af87aaa-55cd-4f72-8e61-cede77b29de1", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "4c26e25b-68bc-41c9-971a-1a842f39cc72", + "attributes": {} + } + ], + "account-console": [], + "broker": [ + { + "id": "356547a9-1a8b-45dc-abd1-5f1bda1922e0", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "e2dff30c-05f9-429c-930a-b67a9f645724", + "attributes": {} + } + ], + "account": [ + { + "id": "dd04079f-6930-471a-8903-e7bfbc3b9d88", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "d02d8dfb-6a04-4cbb-af8a-0f58a1e6082d", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "37b5c0e7-4a03-4dff-8e73-94a69979ac7e", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": ["view-consent"] + } + }, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "6c27cd26-e43e-4ede-8d96-278a075d8d33", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": ["manage-account-links"] + } + }, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "513b9260-91f4-48b7-9861-9ab02b831477", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "c00115d9-e27a-40c9-bd6b-da3039d233fb", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "14d25453-7a50-4ae6-8920-42659a9ca4d7", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + }, + { + "id": "16fbc264-aeba-485b-b76a-bae0cfba7926", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "attributes": {} + } + ] + } + }, + "groups": [ + { + "id": "5d714806-47a5-4249-aec0-6afdbce60b13", + "name": "Admin", + "path": "/Admin", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + }, + { + "id": "54ce2109-aaf5-4558-b44f-d97b106e6efd", + "name": "Staff", + "path": "/Staff", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + }, + { + "id": "263edf27-7dd4-426b-b5cb-e045620f5b71", + "name": "Students", + "path": "/Students", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + } + ], + "defaultRole": { + "id": "56179262-280a-46e6-995b-9610be72b7a6", + "name": "default-roles-ol-local", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "160c333f-6b79-44a1-8bfc-e9ca019584bb" + }, + "requiredCredentials": ["password"], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": ["ES256"], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": ["ES256"], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "3055af80-6c17-4e98-a19c-af5314564275", + "username": "admin@odl.local", + "firstName": "Test", + "lastName": "Admin", + "email": "admin@odl.local", + "emailVerified": true, + "createdTimestamp": 1726160006223, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "ab8aee5e-283e-4543-9a1c-83c497d86637", + "type": "password", + "userLabel": "My password", + "createdDate": 1726160017829, + "secretData": "{\"value\":\"22qCgdUuyFZ/wONRLX5h1G7dkF7d8CnkAd9uPsHgmUY=\",\"salt\":\"Ke07IqyktC1lN8876fvloQ==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-ol-local"], + "notBefore": 0, + "groups": ["/Admin", "/Staff", "/Students"] + }, + { + "id": "eebdbf3c-9a7a-420b-81f1-dc18715c084b", + "username": "prof@odl.local", + "firstName": "Test", + "lastName": "Professor", + "email": "prof@odl.local", + "emailVerified": true, + "createdTimestamp": 1726160036879, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "508bc817-3277-46a0-aee0-0aa9fbf59f06", + "type": "password", + "userLabel": "My password", + "createdDate": 1726160044548, + "secretData": "{\"value\":\"mIdV9h0BeK2BPCesaYpgRJfjRTdh2VnUBX48VPfdvBw=\",\"salt\":\"DZ3KEZreGmxxhwFT6aVhmg==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-ol-local"], + "notBefore": 0, + "groups": ["/Staff", "/Students"] + }, + { + "id": "ac4fd9db-4f44-487d-b2b1-7b7d79869a97", + "username": "service-account-apisix", + "emailVerified": false, + "createdTimestamp": 1726169767820, + "enabled": true, + "totp": false, + "serviceAccountClientId": "apisix", + "credentials": [], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-ol-local"], + "clientRoles": { + "apisix": ["uma_protection"] + }, + "notBefore": 0, + "groups": [] + }, + { + "id": "e9e0291b-fde0-405a-9dab-5fbe5a7b1830", + "username": "student@odl.local", + "firstName": "Test", + "lastName": "Student", + "email": "student@odl.local", + "emailVerified": true, + "createdTimestamp": 1726159936266, + "enabled": true, + "totp": false, + "credentials": [ + { + "id": "580b79ab-ccd2-45c7-bb47-c0a55a504d91", + "type": "password", + "userLabel": "My password", + "createdDate": 1726159949877, + "secretData": "{\"value\":\"P23HPeLgfgQ9i+0x06J/5anJmV9wwcqrkhW7FBrfHJg=\",\"salt\":\"LW6v4uvkk+vBD9jNxNxAlQ==\",\"additionalParameters\":{}}", + "credentialData": "{\"hashIterations\":5,\"algorithm\":\"argon2\",\"additionalParameters\":{\"hashLength\":[\"32\"],\"memory\":[\"7168\"],\"type\":[\"id\"],\"version\":[\"1.3\"],\"parallelism\":[\"1\"]}}" + } + ], + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": ["default-roles-ol-local"], + "notBefore": 0, + "groups": ["/Students"] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": ["offline_access"] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": ["manage-account", "view-groups"] + } + ] + }, + "clients": [ + { + "id": "097210dd-af96-4e0d-ac33-a5d5243e94c9", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/ol-local/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/ol-local/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ef6b553e-e6cf-4998-8b8d-f5de6c4b7d37", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/ol-local/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/realms/ol-local/account/*"], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9fc61f84-77c5-406a-bb3d-f6d3595ffdda", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "040f5659-ff75-4bef-a0a1-d2165e7e72fb", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "4c26e25b-68bc-41c9-971a-1a842f39cc72", + "clientId": "apisix", + "name": "MIT Learn", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "HckCZXToXfaetbBx0Fo3xbjnC468oMi4", + "redirectUris": ["*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": "1726169767", + "client.introspection.response.allow.jwt.claim.enabled": "false", + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "use.jwks.url": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "client.use.lightweight.access.token.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "true", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "tls.client.certificate.bound.access.tokens": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "382d54ef-ee93-484c-bb49-e92ec8755e88", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "ba759de4-0a57-46a9-9ab6-ffbe28a58f74", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "ebff3e5b-a2a6-40b0-b4ad-ec169824f43f", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email", + "ol-profile" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:apisix:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "uris": ["/*"] + } + ], + "policies": [], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "e2dff30c-05f9-429c-930a-b67a9f645724", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d81cd727-c2f4-4648-85f8-60b48d812610", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c2a960de-d848-4ea7-b08e-a41ca3f5c5a0", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/ol-local/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": ["/admin/ol-local/console/*"], + "webOrigins": ["+"], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9c789a70-df20-460d-915e-d54e5ce8814e", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "basic", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "5720e891-4e24-45a9-9403-3a367254719c", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "93c99fc7-b8d4-4a7a-9c9f-e7d06e094bb5", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "11815a72-6be8-43a9-93e2-32cb7c361824", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "b37345b3-53d6-46b1-805c-dd40ce4e4ac4", + "name": "basic", + "description": "OpenID Connect scope for add all basic claims to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "5e862885-af24-4fa8-9e6f-4592bc7f905a", + "name": "sub", + "protocol": "openid-connect", + "protocolMapper": "oidc-sub-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "ea96e453-0fe5-4d7b-a19a-ab1667857aa4", + "name": "auth_time", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "AUTH_TIME", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "auth_time", + "jsonType.label": "long" + } + } + ] + }, + { + "id": "4e34ca00-c111-472e-ac34-3a5abb5094e3", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "0fedf753-bb59-402d-af30-da95db2c4cd4", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "d068507b-433a-4008-9750-364e62b93aee", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "711064fc-3eab-49a3-898d-798360f5708d", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "8ea58ba3-5fd4-40b2-8b01-d8cf9ab0be93", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "ad8d1712-2cc2-4713-9e50-e2ac9cf6c1b8", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "5a609b33-59b9-4a78-923e-7c5a43e98af5", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "5c10a257-b6da-4f40-9612-213db893e1c1", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "b22fa204-ea95-4929-8f76-4a9ad181d830", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "762d82e2-834c-476d-8eac-2893aad1e511", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d9d8bc36-da19-41e9-89ac-3e46b07e4699", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "b0936606-106a-4bb6-bffb-60ef855a3de7", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "62d90940-8243-4cb2-a7e2-1d927a17b9a7", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "3ef22067-689b-4d4b-8d5e-2d84bd6cbd0c", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "introspection.token.claim": "true", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "7a960a3b-57db-4e69-b4fa-1d0242c880bd", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "3114dec7-0238-42cb-9041-cd5c3705cd82", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "4039f615-461b-4308-8cdf-918625980623", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "379570ef-c15b-477b-a906-9b46e88ca2fb", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "d53ead88-d923-44a3-add4-4483d293d465", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "69e60fb2-8523-4848-a576-e152084c5f47", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "bbee664a-7848-4469-a49d-0dbfff242827", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long" + } + }, + { + "id": "5096843f-7ff5-4c09-9525-57fabd55e2ce", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "f502c4f4-7f8d-430c-b571-bb5df1155249", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "ce89baf0-2b54-47e7-b00f-bd61a0e9a465", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "db8ec0a8-e423-439e-aee1-5147fd9693a9", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "8e4051ec-92f3-472b-8c21-391a03e2ab4d", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "e107de9b-a08e-4746-a27b-5b8fa9ec1ad9", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "77dc3a7f-d257-4b62-9d89-4407793a05fb", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "9a68d8fe-d233-4d0b-9630-15c55b8be2bf", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "74eef1b6-05a4-4cbf-9f3b-aed43c365dd2", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "a23f78f6-538b-4860-b890-84fdfe6b59bd", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "c6dd6cb7-653d-400a-8ead-9bb0f75b4024", + "name": "ol-profile", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + } + }, + { + "id": "e3e5a646-2741-4544-9722-c109fed166d4", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "f8b962ca-f6ea-41e7-9ae2-38f8cfdb5efe", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "bf36fccc-d411-4c62-808e-cfc97b8159a7", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr", + "basic" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "referrerPolicy": "no-referrer", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": ["jboss-logging"], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "4c7052d0-303d-4146-a54b-c153d829d8af", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-full-name-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "saml-role-list-mapper" + ] + } + }, + { + "id": "99dfcd62-0305-4262-943d-920d31c45fc7", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "3b4770f5-3d9a-4bd9-a174-a29d67b85d1f", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": ["200"] + } + }, + { + "id": "61451e1c-1af2-47dd-a177-6643e8391942", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": ["true"], + "client-uris-must-match": ["true"] + } + }, + { + "id": "b75af5b1-65f0-4321-a017-88134b1059c5", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "c6217792-173b-42e2-a6fb-b70c398a1e1f", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": ["true"] + } + }, + { + "id": "04988dcc-f387-4008-a1f4-1fd6cd7cac98", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "2614094a-cfdc-42ec-8e59-b8b21a9a35ac", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "69f86334-5ed3-4c69-9112-a7cb5b6f34b4", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "kc.user.profile.config": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"fullName\",\"displayName\":\"${fullName}\",\"validations\":{},\"annotations\":{},\"required\":{\"roles\":[\"admin\",\"user\"]},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"emailOptin\",\"displayName\":\"${emailOptin}\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "47c93754-2927-44bd-93b7-80660c6f26c0", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "kid": ["e8e164ce-eef7-4407-bb19-f9105d91fc4e"], + "secret": [ + "ViBCEGarcZ69Bv7THlqVfevvS_QR-5FQ6Uj9oSCtSRNA-sdt6r55jzkEg3gsjzUp4AUcnevYzZtqHU1t3zbcf72IPdx0q2DPjiTV1YDsgIM-GivI299JcNHnECPzyipJoI-ri1zA7KzNQoTEud4VKYXQyFGB5hibjzvUoddrIGs" + ], + "priority": ["100"], + "algorithm": ["HS512"] + } + }, + { + "id": "0b4a4440-0999-433f-be3b-9600e747941c", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEAv7CrZkwgQe3+0QmZHm8caZQUCPjswtALevI0tU1621peeu/yM8chqWIr2Hdbts96bvLpr2R6oousI2lCP/WqnATIIXn0bZLLqZUvSDUOH0gZvdpD8h2xoHoXtdDVIAFbqN0gzV5J/APTORgd9Y4juOhyCud6KIMjfCW0yr3Wv8ukol6aABLMZczRlRjFw+BtgEjGaZA39HmeEVkKErbzLCb2kK2F05vYzwQ32WNQy6yfVHq4PhZf+Om7v5h2RHEyp0a0/EUHMxEwujHG2SVG+bMvZIssUTF/TWs6RnnpSoHtP8WzcTtpoj+TH2GSc4agvIZ9QRNT2mXrYBETTx0JQQIDAQABAoIBAAymF0FKT6yaDED17a0k7s6Gr2XCKzlVqhRETDcUdEFqLqbVE3nYL3/yUutXQO2Itk8A52uj6TW1mrfBd9Ypm0btR9lxpy/dymOXzQVqPtLQmqY881PUIsbwl4TvUUjp3gcABGyYxrAC/pqbXUq5ROEsMW5HxPdMY/iKsmnYagXAyHohwhHYyrIkEALFDvUZc8iKaw6QPU9kBp4QNgwNRjMvPzyEzsD8u1wIudgeko4/wN/WwpRgTohwtkbur+F2oAj7SghqGTScCPWVFQAkZHVROgsOZJA09evK2GxMag4erCNrqrXFKfZNicQrx57NFhLH4q2UNv61nuQdewrPUlkCgYEA78cXga8aEL1H7Q/dCsVwjrUd374hxVK3PxIaTIqBMbVQphV7pSyg/BxJHXrHGY83tvBANjA1ITtpEz34SdsjgsNdKmbc1+pJDAQZZ/x88iKLXziuL21NqhfPZ0w2JQttFcFTJBpnlyl1jDbtBDAcl22Z55Z9Eu7IExTDsJL/o60CgYEAzKi1hCN09OgF2HvRGiUKBxuLxasHc5JCWxWw7Q1bH8S2+VOeXt+pl8bDonlXxeIh58tImhSTb2pnfIOU0ZBQeC3Us/NgGN2es3WY0w21hspyWehK/9+vfp4x7702RzgVJu59kBzKqZF5fAw1OgU3A/VzDe2qBW5oJZAJzWRGDmUCgYEAx14nMXFCnwCDOZ2jET2xpTb7K/qPYd6w9wQ6UcIoQgickjvynxhIkteCA7z+p0Xp8XY6LdRPmN4pNBKmy+Il2KhQYt08a1smeZM+/LN3wGzwrbAXROABX5iEn0NDEfI6NYiVdMNvtsSGNJvG32CRpWdAPMtoG8HnIdZ2D+9qF9ECgYBUbmF8IxiUFMicl/AbBh7N8dpG8RkA390KMLeuBC2MvJ3z3EBgyYrwt6pr8/13AKSWOPI5xrVQaKhK4QnbLttTySyQFJ6Xg45+YMxsfaJe+lQUrVWLnB+Nb/wP+JJU7Vkkl40rkAU30XE58NtglVguBOuzWlIjLXo/zN2OY4jXVQKBgFwxPHac7GH2b8OhbQ6Z4eNPXu65dXk3Y5rUE8AVLJPh/RIZd+2t2VXdY/2URDzcmSqmHJde3P9Y5MzziKDJx/jj1VZwtM+DXoYchy23rldsSfhr20InGgoO1wpzSr5oJWpyJixeGXaDZiiWU7pjo0V1eL5/aQDI1GEWQmaMjBfU" + ], + "keyUse": ["SIG"], + "certificate": [ + "MIICnzCCAYcCBgGR5yXAwTANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhvbC1sb2NhbDAeFw0yNDA5MTIxNjQ5NTZaFw0zNDA5MTIxNjUxMzZaMBMxETAPBgNVBAMMCG9sLWxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv7CrZkwgQe3+0QmZHm8caZQUCPjswtALevI0tU1621peeu/yM8chqWIr2Hdbts96bvLpr2R6oousI2lCP/WqnATIIXn0bZLLqZUvSDUOH0gZvdpD8h2xoHoXtdDVIAFbqN0gzV5J/APTORgd9Y4juOhyCud6KIMjfCW0yr3Wv8ukol6aABLMZczRlRjFw+BtgEjGaZA39HmeEVkKErbzLCb2kK2F05vYzwQ32WNQy6yfVHq4PhZf+Om7v5h2RHEyp0a0/EUHMxEwujHG2SVG+bMvZIssUTF/TWs6RnnpSoHtP8WzcTtpoj+TH2GSc4agvIZ9QRNT2mXrYBETTx0JQQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBiBZ+7nbloyTfYiplu0MOsdIh+WjJpAGQtSMTvD1RhR2J5HFwYpgr3cgdZ8zcTJX/5zx/3eQnhzTCR5nfpsZJNBk32rw6XRD6/H6bS7pPrfGyCWoCAnRtlmIz/mTOiTeeaxl2LKNNxecH5C6+vrKkVB8NFC5/nhJmurNzuYtsEyUB1gwGqAlsVpMo1RcPPTbrzdDDCa/1aPzdAYqu/IN2ND993PqeMkUAhgWKKc44n4gHzBLnmQqXHKbLnkStJo9Bn8C1eOaudOtma2+o99DmatvqUyXxBv3K1ZeAxg6UGJexYIScDfrIgwJQTDcWioMd4bXAxDCDlBINTNn+1HPPx" + ], + "priority": ["100"] + } + }, + { + "id": "fd3ba571-0ed9-4032-9ee9-8a666c244b95", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "privateKey": [ + "MIIEowIBAAKCAQEA9R2yURiyyjkwFh7XFeYyyIMQOwlLqRppZ85PSIZVE6JB8u4t5VooFhWuNrt0cOCOh70bOcIX5CCQPVrWDdYrhd2MxOePiZa8eHZSypUNN85GKias5SvQhl8bvK1YkfZBwr9STwOAHVROGZEaNtdFb6DWIiDITHXeHARGnVbmMRipQKleYhOTHZsqCu7uZjQiF9jG2EQv26hmd2S7iClW7YdZUMHD8IQS4h+QuPP5HzmfbC2eX+6uBFoKeUEFZUlQnjiui5eBZzCnObGNdOouLatHidxOcID4ixarrM7KJp88iyferJTfGwEMHuO4W+cQvtkczaHNtSCoZp/N4dj1AwIDAQABAoIBADb7w103qht8upGrt1m9OpUJ+WEU07kKKKzVdalyILF3y01gKkFxeN6DXIIAiL0tUiuWuv240T0mJNTuhGe0LC0qJoKg4uzdOEfZWvcAemeTSN/5rvz1WCBcQ0+OnviXAedanW8F/P4XRDGt//BfV78TmBUtv2CPbBRizShMLvTCAAnKHa8H2IWZM0RBtr7y2Dz2jUl16UIAwLEbOKCo1y0ljv1fhUS5bEzN8OdItbr8mxpYYCNvuAWmtiq5wXkVZmuznW5U08H9P+7xRAisLoCmgQov7qhL/qvLTaQNQNu1lgW06fwCLXT5E5ec2vJaFraaXBe85RN+OPbYYsmz1wkCgYEA/Q4Us+CvYBxJ+vlFm7t2CJEy2L1HZk7zUCFdKp87fM+B6XE1iS4kyQsTlOV+893wiqYbk/ywE7V3CBIW0ND/SJkc/jDc79H/iWyhmGQBsIk2BQbUF7lYovtSzNvl7/5jEQU+FM9vSpFqwFvIQuXx+i5syk9JQBFjjQ2k14kNAk0CgYEA9/f2l8h3GdoWtfK8ITe7T2zK18xTcXRusEszH1/FH8FVEV6tSYElQ+pWSxtmceemoF0KJMpNQdb2ckLyD1V/TwgV1eZCy45z3WJUbb6f0e2UEfe0dMYxE5Wd2ThU9WAkmK9b8PWKalLmBhhSkE8oqqgEfTmqimTVvUbzcihFXI8CgYEAhSy15Hxoj1IT9QregTjEw3l2ou3p94OxNQh5+YZXTjX+jpZPsGQiY9N6eK/WggzZHH6SoO0o1RZ5EDxE2ZxD3TcHNRcODwAbVX1gBc0LvpZXYYnYcKvQY+WUC8/mUMk15a02oMkSLtIctiMXX22YTSvFgSr8x8Te+Uqm5+9uczkCgYBHVzC79HHHn0YfbMmRaP5b2Hn3YhKztoLN96SqpxwIic/Won2KgzxccMQI8cYkiTgYNQxhD07w3U6kCvynSrrI2xOlKY0YEVDmZY7S0CAc/pQ2IyTo38ho0QfL4fgXbGS7BOCXz5zWACmroT3HxO53QsWf3YJiNKaFwS7zLgDeowKBgERHJdfg+KFwzBcg555Ev2wtysmCBOLvYpaCNY28ZdsXfAU+WWymuQdB32uaCA7CukN0c1tZrScqYw+x+2ytQsEVgx6c5dRozroMJe6wJygrIIymDGiL2grAJsWUSvLIZIrcBEEKZ+urCE9MQWIoUgZd1JmcqQsiDQ5miZlHpVjC" + ], + "keyUse": ["ENC"], + "certificate": [ + "MIICnzCCAYcCBgGR5yXBajANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhvbC1sb2NhbDAeFw0yNDA5MTIxNjQ5NTZaFw0zNDA5MTIxNjUxMzZaMBMxETAPBgNVBAMMCG9sLWxvY2FsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9R2yURiyyjkwFh7XFeYyyIMQOwlLqRppZ85PSIZVE6JB8u4t5VooFhWuNrt0cOCOh70bOcIX5CCQPVrWDdYrhd2MxOePiZa8eHZSypUNN85GKias5SvQhl8bvK1YkfZBwr9STwOAHVROGZEaNtdFb6DWIiDITHXeHARGnVbmMRipQKleYhOTHZsqCu7uZjQiF9jG2EQv26hmd2S7iClW7YdZUMHD8IQS4h+QuPP5HzmfbC2eX+6uBFoKeUEFZUlQnjiui5eBZzCnObGNdOouLatHidxOcID4ixarrM7KJp88iyferJTfGwEMHuO4W+cQvtkczaHNtSCoZp/N4dj1AwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQDHFfeuj8dnPed5v7ajCw+Xx0o5EhZ623pFVrCkZNcSsOGmh710aSgvBbpAXDWLkS2R1uD++hD1vNDQ7Pnz2z+6E9BBItqj1dgfN10IcFGJ9ileCEsayoc5uplSwGY1y/0Poe4RFhGV/6uo3UL54/Qw5ecahuUoKqbqbeur9DZLhSoyGibEGYhHUncDzULnIQR6AVfT+bQ2pqBe+FA/ykJeg2fVCdGJHI8uqWW50WgQHKCFrnoaRqrJQ0A7B5sd4PuaZzq5s0PwzVDmcB/nZjfnataSFyyhL2oZoy7BTOY7xh/AiLzAC1nMdfqPs0gQcDMVbpBKtLgpEBQSTBC5xRRR" + ], + "priority": ["100"], + "algorithm": ["RSA-OAEP"] + } + }, + { + "id": "242f7b8c-967c-4e67-a83c-fa36b6337291", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "kid": ["7364b9cd-d1cd-42f0-a22c-3bf309310df8"], + "secret": ["2LzEGveFBdy06ypJTVYSPw"], + "priority": ["100"] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "27bc7a04-ee5f-4b4c-afe9-dca90c809c98", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "c18dd952-b2ec-4c11-b36c-d18121a9f4b7", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "beff5a56-af3c-44de-be47-97812a464014", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b34b8f30-509e-474e-906e-daefb760f718", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "029c0e49-2962-422e-8b80-32cd51992392", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "81d0bc8e-607a-449e-a96e-0fcb8de9c434", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "77e08f18-0a42-4736-94e3-00a145952250", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "9efd0081-5d4d-4aec-80c1-110f6555f9b3", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "83cf9ffb-da75-4d8b-bc0d-660e48a6c74e", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "a5309833-ee5b-4d29-ac61-71e166d819a4", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "9f514c04-cf5c-49d6-97b3-44b822747623", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "3fd74adc-2f11-4c57-b64a-aa38d823671b", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "e1735cfd-fd3e-45af-bfbe-c4c534853bd6", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "5aad5e6a-ea1a-4a23-bbdf-47d76b13020d", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "69ec47b5-6557-4221-9b62-d6218b62dede", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "4427801f-b656-4edd-9988-464b5380a948", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-terms-and-conditions", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 70, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "a5a0e7a4-b1f5-4a32-9bb8-d3bfc23571b0", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "0979f784-315b-44f5-b73b-cc4a73e1050c", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "4dc83da9-89c8-4e4f-8e03-4a648e7993aa", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "96aad416-fda9-4eac-a709-93a97bf6b6f9", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "VERIFY_PROFILE", + "name": "Verify Profile", + "providerId": "VERIFY_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 90, + "config": {} + }, + { + "alias": "delete_credential", + "name": "Delete Credential", + "providerId": "delete_credential", + "enabled": true, + "defaultAction": false, + "priority": 100, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "firstBrokerLoginFlow": "first broker login", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "clientOfflineSessionMaxLifespan": "0", + "oauth2DevicePollingInterval": "5", + "clientSessionIdleTimeout": "0", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "", + "organizationsEnabled": "false", + "acr.loa.map": "{}" + }, + "keycloakVersion": "26.1.2", + "userManagedAccessAllowed": false, + "organizationsEnabled": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/config/keycloak/tls/README.md b/config/keycloak/tls/README.md new file mode 100644 index 0000000000..10e14da888 --- /dev/null +++ b/config/keycloak/tls/README.md @@ -0,0 +1,14 @@ +# TLS Config + +If you want to add a different cert, you can here. The files have to be named: + +- `tls.crt` - the full chain certficate +- `tls.key` - the key for the certificate + +The default certs have `.default` appended to them. These are for the `kc.odl.local` domain. If you're using a different domain (and you care about changing this), you can regenerate the cert using this one-liner: + +```bash +openssl req -x509 -newkey rsa:4096 -keyout tls.key -out tls.crt -sha256 -days 3650 -nodes -subj "/C=XX/ST=StateName/L=CityName/O=CompanyName/OU=CompanySectionName/CN=new-hostname" +``` + +Run from the `config/keycloak/tls` directory (this one) locally. The Keycloak image doesn't have openssl installed so you can't use that. diff --git a/config/keycloak/tls/tls.crt b/config/keycloak/tls/tls.crt new file mode 100644 index 0000000000..119a429bca --- /dev/null +++ b/config/keycloak/tls/tls.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIUVYYQSppdraOkEPpz89UEwDk3LOIwDQYJKoZIhvcNAQEL +BQAwfjELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxFTATBgNVBAMMDGtjLm9kbC5sb2NhbDAeFw0yNDA5MTIxNjI4 +MzNaFw0zNDA5MTAxNjI4MzNaMH4xCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0 +ZU5hbWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEb +MBkGA1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRUwEwYDVQQDDAxrYy5vZGwubG9j +YWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCkAKESyxg5s2WS5Q7K +YaVhBwQJPhzU45nKxSPcoaaC3M+1PYJh2vCNHFa0+5RR/ya7qJ3qMpMimuHH247K +pA2vHQGLxOkmPumJG8A3kvE+Cwas1nMrkJCzTX7jGbKm15b9h6qWt1od69zUEC3V +AR+gau+qfhE79i8psZFSehpZNLEWQUQeKhVj+1Cx1CRTHJ7nIi3jVGaDU8qct/Dp +feIUm4Jq2+7XP0is6nljaJ/xB23so7Ru99FB+5cfXE+0MK00FPJoI4gIgog7GcYr +pBKhHDmcjnD4+dEjZc51Y6ePyvtrO2PdlOUiIr2H3lcStQbi/+abe98Uv3rQuieo +f9rF03eZsfAL9uB4l73MrFbCuEEWSlhwiTiEfUw+6WZ4ejuBVUwkW0fvVkejLPyD +ITZb2NaUmpCdN2GQcA0gbKVMO8pY2af61aEgr6SExi04IlwrdM+UFtTcE7KqDzM8 +GKbb8qht9nwvBCAQDJtsx74Z+22rDj8VBi4i5DUJP5izKsP4OB448SZAvryNla47 +L9X4Hf4K/jwf3PD1YxWF3yyEkDFQkEHsdRzmS1raR+bCEgAZivG3CJIZEPV4LdKT +05ocRp+v67m8NhrMNbsMdgiIFNljG8jEmwSR/ZlDWQDD4QQJWetcuBU8btqO2jtd +3s1qaH07AyN7QK1eTnaHN+zyWwIDAQABo1MwUTAdBgNVHQ4EFgQUZ1tlFb4BVpWR +Mbym2t+mtbatmtgwHwYDVR0jBBgwFoAUZ1tlFb4BVpWRMbym2t+mtbatmtgwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEALskcFZWixNdcm1LxaCSS +m01asWbwoSkxncG3umCzOEMI09NdbfPzuyJrAN9vGPKjMiRaZVQiwk/qDDEhGmn+ +VW/8fD1F0kVnDcPt2o0Q/dyBpN4NKprDQ1iYag2MKkgrkqxj6jIMG+khS/3yBEoZ +XoLHgDTo5uWUzhxOUq5aCCUpIAbsxfA6XLOQt/5/EizPUClG8rVIFGHsExPqBV4x +YnMf2RKDyNt7k4V3GivwK1V5UOvBxGfEOLhWpOBgsD1VQLyRJxlvRV8ZmztBM6WL +mSXnjVKicBxeyX+Sq+KtJ0F1AeXdI1PpVsmanSzOdkAaqPu6u6scNCNH5ipJvinG +IzT9RuXNuY4By9e7VRZ9BsvejTCKuqkxYabMSIuU6OBFzDBIA9nGKfW65JoC+JQp +MUNHdXWHwkx/5aVR/MVeze6+XD3FB4SG1oJQzkM1i3LMozA+jQwlktgxrHkD4D0s +L8NqE3U21a3eIQP3qbDbwzhnTR8Zd4PzB9DWjBGNQI4wK12nfSKaX76WLjrl6gkD +I4Ce77tFtcobBG7VfNMzImuF2JmanT5e7B/YMbLsyEC9fjqLA7ROv7VBA+90U3oL +lcQKjW0ls25+jmJTFwZi5hrZr4WzYjqf835xP6pb0ZAtbd4t2BHCdAMv1mMSuhzk +Coe9G87swXHOC9SwbcRPt7c= +-----END CERTIFICATE----- diff --git a/config/keycloak/tls/tls.crt.default b/config/keycloak/tls/tls.crt.default new file mode 100644 index 0000000000..119a429bca --- /dev/null +++ b/config/keycloak/tls/tls.crt.default @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF3TCCA8WgAwIBAgIUVYYQSppdraOkEPpz89UEwDk3LOIwDQYJKoZIhvcNAQEL +BQAwfjELMAkGA1UEBhMCWFgxEjAQBgNVBAgMCVN0YXRlTmFtZTERMA8GA1UEBwwI +Q2l0eU5hbWUxFDASBgNVBAoMC0NvbXBhbnlOYW1lMRswGQYDVQQLDBJDb21wYW55 +U2VjdGlvbk5hbWUxFTATBgNVBAMMDGtjLm9kbC5sb2NhbDAeFw0yNDA5MTIxNjI4 +MzNaFw0zNDA5MTAxNjI4MzNaMH4xCzAJBgNVBAYTAlhYMRIwEAYDVQQIDAlTdGF0 +ZU5hbWUxETAPBgNVBAcMCENpdHlOYW1lMRQwEgYDVQQKDAtDb21wYW55TmFtZTEb +MBkGA1UECwwSQ29tcGFueVNlY3Rpb25OYW1lMRUwEwYDVQQDDAxrYy5vZGwubG9j +YWwwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCkAKESyxg5s2WS5Q7K +YaVhBwQJPhzU45nKxSPcoaaC3M+1PYJh2vCNHFa0+5RR/ya7qJ3qMpMimuHH247K +pA2vHQGLxOkmPumJG8A3kvE+Cwas1nMrkJCzTX7jGbKm15b9h6qWt1od69zUEC3V +AR+gau+qfhE79i8psZFSehpZNLEWQUQeKhVj+1Cx1CRTHJ7nIi3jVGaDU8qct/Dp +feIUm4Jq2+7XP0is6nljaJ/xB23so7Ru99FB+5cfXE+0MK00FPJoI4gIgog7GcYr +pBKhHDmcjnD4+dEjZc51Y6ePyvtrO2PdlOUiIr2H3lcStQbi/+abe98Uv3rQuieo +f9rF03eZsfAL9uB4l73MrFbCuEEWSlhwiTiEfUw+6WZ4ejuBVUwkW0fvVkejLPyD +ITZb2NaUmpCdN2GQcA0gbKVMO8pY2af61aEgr6SExi04IlwrdM+UFtTcE7KqDzM8 +GKbb8qht9nwvBCAQDJtsx74Z+22rDj8VBi4i5DUJP5izKsP4OB448SZAvryNla47 +L9X4Hf4K/jwf3PD1YxWF3yyEkDFQkEHsdRzmS1raR+bCEgAZivG3CJIZEPV4LdKT +05ocRp+v67m8NhrMNbsMdgiIFNljG8jEmwSR/ZlDWQDD4QQJWetcuBU8btqO2jtd +3s1qaH07AyN7QK1eTnaHN+zyWwIDAQABo1MwUTAdBgNVHQ4EFgQUZ1tlFb4BVpWR +Mbym2t+mtbatmtgwHwYDVR0jBBgwFoAUZ1tlFb4BVpWRMbym2t+mtbatmtgwDwYD +VR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEALskcFZWixNdcm1LxaCSS +m01asWbwoSkxncG3umCzOEMI09NdbfPzuyJrAN9vGPKjMiRaZVQiwk/qDDEhGmn+ +VW/8fD1F0kVnDcPt2o0Q/dyBpN4NKprDQ1iYag2MKkgrkqxj6jIMG+khS/3yBEoZ +XoLHgDTo5uWUzhxOUq5aCCUpIAbsxfA6XLOQt/5/EizPUClG8rVIFGHsExPqBV4x +YnMf2RKDyNt7k4V3GivwK1V5UOvBxGfEOLhWpOBgsD1VQLyRJxlvRV8ZmztBM6WL +mSXnjVKicBxeyX+Sq+KtJ0F1AeXdI1PpVsmanSzOdkAaqPu6u6scNCNH5ipJvinG +IzT9RuXNuY4By9e7VRZ9BsvejTCKuqkxYabMSIuU6OBFzDBIA9nGKfW65JoC+JQp +MUNHdXWHwkx/5aVR/MVeze6+XD3FB4SG1oJQzkM1i3LMozA+jQwlktgxrHkD4D0s +L8NqE3U21a3eIQP3qbDbwzhnTR8Zd4PzB9DWjBGNQI4wK12nfSKaX76WLjrl6gkD +I4Ce77tFtcobBG7VfNMzImuF2JmanT5e7B/YMbLsyEC9fjqLA7ROv7VBA+90U3oL +lcQKjW0ls25+jmJTFwZi5hrZr4WzYjqf835xP6pb0ZAtbd4t2BHCdAMv1mMSuhzk +Coe9G87swXHOC9SwbcRPt7c= +-----END CERTIFICATE----- diff --git a/config/keycloak/tls/tls.key b/config/keycloak/tls/tls.key new file mode 100644 index 0000000000..6bd1df0923 --- /dev/null +++ b/config/keycloak/tls/tls.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCkAKESyxg5s2WS +5Q7KYaVhBwQJPhzU45nKxSPcoaaC3M+1PYJh2vCNHFa0+5RR/ya7qJ3qMpMimuHH +247KpA2vHQGLxOkmPumJG8A3kvE+Cwas1nMrkJCzTX7jGbKm15b9h6qWt1od69zU +EC3VAR+gau+qfhE79i8psZFSehpZNLEWQUQeKhVj+1Cx1CRTHJ7nIi3jVGaDU8qc +t/DpfeIUm4Jq2+7XP0is6nljaJ/xB23so7Ru99FB+5cfXE+0MK00FPJoI4gIgog7 +GcYrpBKhHDmcjnD4+dEjZc51Y6ePyvtrO2PdlOUiIr2H3lcStQbi/+abe98Uv3rQ +uieof9rF03eZsfAL9uB4l73MrFbCuEEWSlhwiTiEfUw+6WZ4ejuBVUwkW0fvVkej +LPyDITZb2NaUmpCdN2GQcA0gbKVMO8pY2af61aEgr6SExi04IlwrdM+UFtTcE7Kq +DzM8GKbb8qht9nwvBCAQDJtsx74Z+22rDj8VBi4i5DUJP5izKsP4OB448SZAvryN +la47L9X4Hf4K/jwf3PD1YxWF3yyEkDFQkEHsdRzmS1raR+bCEgAZivG3CJIZEPV4 +LdKT05ocRp+v67m8NhrMNbsMdgiIFNljG8jEmwSR/ZlDWQDD4QQJWetcuBU8btqO +2jtd3s1qaH07AyN7QK1eTnaHN+zyWwIDAQABAoICABjIiOQd0YJets8fqEAzHgUZ +70/flVx31zTKMnopP3rFGD+rlOQUFt1BgWTbeSAkWQZrcQ8EJ4dHioO44neC78Jy +YAUZc6Dg9EpQ6ZeRsBJlI37DmtHkMx8KwKMHA3VXslc2b4lTxIgtrXL/zZKZFzNs +at1ZLGI4TguUltZfjKhwYFZxnx6ZKMmxdRH6cfwmoL4NmeD2+dWHYws9zL0sKxmx +dQrlmM6USfeU01U6E/lzhaFeDRU72TaJvjOcr+VrTwabfRT5pu6fndX4i3bq1hnc +pGQhLmH24SuNO0mIDrlEaQFdDzaxRShcbEciSrGRWQISvmw+qDPVdR93vNMncbgh +3/R6vQvx4zjfWyuax1bKGliKhyMZ08w5Yn2El5AAysYo85RKc4OPVOsOQEyY73P3 +oSvjIUIWYBSC9xSQTLe6uZj55sXsiXHtjDNy+75lvVkXEIMy+hH5vvQTzO3mpv9W +BI5gus/QXRAVzH2uRrvLLO2yBA/BKV4gOVW0ofHqx/WLUyjOgnNOLVJs6XB+gF1/ +xgtIEjeMmGOtUPhcp2Ep3QZ94vG5SfwQzObJaIDNXomNX80MQ+uOt3GX0mTCe2P0 +ejs3NInL38obDF7KsJ/xsP9nJi3L7mPI3k3gMxVyh1vT2E6nWFHusaJCRngEKqb+ +MZJgTWqFSW3O3Mz+ENnRAoIBAQDhY+xXHXvn/iwgaHTuR+gIhf/NlK1jP0yN+Bj+ +1LZnV56d2sPly6LlWZxPdcDZ8z+t58bY8aMocapBPiwZDphQJ6yjvHv+F/eltvVJ +YyelrWwvZWf0cSa/luFF1CaOnP1EI6Bwc/GHZr8NFrIgYhumNZLJUAkQA/JE4l8e +625MEhAkmbrNOyq54UsphpT1xvf61VzFW2UB/IkHzpxulJl8svNGWXEnYDM06/ui +oIAAu5B8aGBuMPa3brN3kVx/vmGL+VmfZK1kwzLE2hb8OvCl11lcS9388pBys5Kv +13eZSVCiKpx75GtJEu5hn2nlYwdWTCPIihNQrKFV7+jOFsUDAoIBAQC6RnPkSFbJ +e9J6c415p4Vj+PQnjrQw+Hew6STy+hG/nnlARu0r6mpEKgeTfFQT9Yxb4O9wPGZ4 +T4JK366ph4UjuCb/bZSxjdXolokbSlxFwF0EWrvVZUKvL1+zEnmynZJHvvG7CyoP +uvwoGFuPrfeu9PN2Mxz0smXu/rEJ7hwn+ueEd9ljI2pxXySNXaD47Ddj7ChxI2VM +xzcFOeRtHEmDd3ZQFVkvgFU8RK10nn4Xl4ngq9PDIrI3rD3Q+W+ElqQUhZ7EfB2e +WlBlVERQUDLmDYM515JspGeqU8vXRumvUeuilEB7JqR9HhoD+hQ3qPIzdkqCCuwV +2N4wGBeY18HJAoIBAQDTaegJOtW7oXWAnJp526bxP8fW7QvKWViUnk/L0Hib7NsS +lF5GUUGlwe1Vt01C4uErXYnuepGhYSTi989jXYZPQTe1ihoAGDkqDrh7su9Af7BH +sOXWqsA+2+bImhvkj6sc3BIlCQxYBm9UdqJ0r7HhsMTT4ifuBtWb+X5hwVH/Nr4/ +ppdK5KHKI2JePCfDdnOqq7HOSVEwkNF0KkAflXF3P1/j8AeseJbvoB6zx7rpdQYt +O7agBXuWSdc7Y3UROeHD6ws+8K+YIWSgszT2OM77sEjYwy0hk+EcRgZkvEYp2VQy +GKgZqgNcUs6ZcW9iRAZg0yCJfcJqXNMkidmkXkVDAoIBACxEyBANnQp/MdNGGO47 +gLj0llm8UVh+BDv3/H7+LS+j4t8CvCS+rgiLEIfdeUHRDk1blKvQvu2Cv805gZHq +khqeDi6QBVF5Csge1nC06F7vS2vYgGFDkmh90rmE/4USa4w/dcVk7tcUMg75UvE/ +f+iFcEK7/PquVwlIYByjCO/7cgAKV5B2/zn4SYCLKtFdmgBWRHo21kE76viD/KRt +n47t2iFIIYzna9pJ3AsmC4Nh0TOiwk3SthYDCiHa1cTl5BK4erXpZUSX5BlgwGdx +19bSiUg60iKdo8FX7s63nJu81Uoq/3QFB/xwJfCiAyIDNaRDTYvAOsEqbAtz/k+l +nvkCggEAJM3Tys3lBjE2LgFqu7VFo/8WZ8GmGZjTO56sXmFauIP+Qh5bRmhOvkhO +/miLPDkmnQvciBhqbRv0wMAb2+VT1LHx5ZUoGh0X2wijlZE/WpWgA7qzaGt0xRWV +8YJt6Axa23Pbd/gH9u3hkSM3eO9Ous2E1cqtbzFuPmG9P89q5DUTIvGqfszcS4pG +ZpFKOjHQ1u+VNqYfGmv+/pDDWlDz90W29ztmSpGqvvkxcYDLApxizCeFouLATow7 +PCoh5h5I9xnQ5QTw/0iBeuKeCFTZuvj8JOLTPsRirxrWa9krIo6MzDDaO4RUgKel +EGMgVVXBAIcERwhHqC1V65M+9vUg/Q== +-----END PRIVATE KEY----- diff --git a/config/keycloak/tls/tls.key.default b/config/keycloak/tls/tls.key.default new file mode 100644 index 0000000000..6bd1df0923 --- /dev/null +++ b/config/keycloak/tls/tls.key.default @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQCkAKESyxg5s2WS +5Q7KYaVhBwQJPhzU45nKxSPcoaaC3M+1PYJh2vCNHFa0+5RR/ya7qJ3qMpMimuHH +247KpA2vHQGLxOkmPumJG8A3kvE+Cwas1nMrkJCzTX7jGbKm15b9h6qWt1od69zU +EC3VAR+gau+qfhE79i8psZFSehpZNLEWQUQeKhVj+1Cx1CRTHJ7nIi3jVGaDU8qc +t/DpfeIUm4Jq2+7XP0is6nljaJ/xB23so7Ru99FB+5cfXE+0MK00FPJoI4gIgog7 +GcYrpBKhHDmcjnD4+dEjZc51Y6ePyvtrO2PdlOUiIr2H3lcStQbi/+abe98Uv3rQ +uieof9rF03eZsfAL9uB4l73MrFbCuEEWSlhwiTiEfUw+6WZ4ejuBVUwkW0fvVkej +LPyDITZb2NaUmpCdN2GQcA0gbKVMO8pY2af61aEgr6SExi04IlwrdM+UFtTcE7Kq +DzM8GKbb8qht9nwvBCAQDJtsx74Z+22rDj8VBi4i5DUJP5izKsP4OB448SZAvryN +la47L9X4Hf4K/jwf3PD1YxWF3yyEkDFQkEHsdRzmS1raR+bCEgAZivG3CJIZEPV4 +LdKT05ocRp+v67m8NhrMNbsMdgiIFNljG8jEmwSR/ZlDWQDD4QQJWetcuBU8btqO +2jtd3s1qaH07AyN7QK1eTnaHN+zyWwIDAQABAoICABjIiOQd0YJets8fqEAzHgUZ +70/flVx31zTKMnopP3rFGD+rlOQUFt1BgWTbeSAkWQZrcQ8EJ4dHioO44neC78Jy +YAUZc6Dg9EpQ6ZeRsBJlI37DmtHkMx8KwKMHA3VXslc2b4lTxIgtrXL/zZKZFzNs +at1ZLGI4TguUltZfjKhwYFZxnx6ZKMmxdRH6cfwmoL4NmeD2+dWHYws9zL0sKxmx +dQrlmM6USfeU01U6E/lzhaFeDRU72TaJvjOcr+VrTwabfRT5pu6fndX4i3bq1hnc +pGQhLmH24SuNO0mIDrlEaQFdDzaxRShcbEciSrGRWQISvmw+qDPVdR93vNMncbgh +3/R6vQvx4zjfWyuax1bKGliKhyMZ08w5Yn2El5AAysYo85RKc4OPVOsOQEyY73P3 +oSvjIUIWYBSC9xSQTLe6uZj55sXsiXHtjDNy+75lvVkXEIMy+hH5vvQTzO3mpv9W +BI5gus/QXRAVzH2uRrvLLO2yBA/BKV4gOVW0ofHqx/WLUyjOgnNOLVJs6XB+gF1/ +xgtIEjeMmGOtUPhcp2Ep3QZ94vG5SfwQzObJaIDNXomNX80MQ+uOt3GX0mTCe2P0 +ejs3NInL38obDF7KsJ/xsP9nJi3L7mPI3k3gMxVyh1vT2E6nWFHusaJCRngEKqb+ +MZJgTWqFSW3O3Mz+ENnRAoIBAQDhY+xXHXvn/iwgaHTuR+gIhf/NlK1jP0yN+Bj+ +1LZnV56d2sPly6LlWZxPdcDZ8z+t58bY8aMocapBPiwZDphQJ6yjvHv+F/eltvVJ +YyelrWwvZWf0cSa/luFF1CaOnP1EI6Bwc/GHZr8NFrIgYhumNZLJUAkQA/JE4l8e +625MEhAkmbrNOyq54UsphpT1xvf61VzFW2UB/IkHzpxulJl8svNGWXEnYDM06/ui +oIAAu5B8aGBuMPa3brN3kVx/vmGL+VmfZK1kwzLE2hb8OvCl11lcS9388pBys5Kv +13eZSVCiKpx75GtJEu5hn2nlYwdWTCPIihNQrKFV7+jOFsUDAoIBAQC6RnPkSFbJ +e9J6c415p4Vj+PQnjrQw+Hew6STy+hG/nnlARu0r6mpEKgeTfFQT9Yxb4O9wPGZ4 +T4JK366ph4UjuCb/bZSxjdXolokbSlxFwF0EWrvVZUKvL1+zEnmynZJHvvG7CyoP +uvwoGFuPrfeu9PN2Mxz0smXu/rEJ7hwn+ueEd9ljI2pxXySNXaD47Ddj7ChxI2VM +xzcFOeRtHEmDd3ZQFVkvgFU8RK10nn4Xl4ngq9PDIrI3rD3Q+W+ElqQUhZ7EfB2e +WlBlVERQUDLmDYM515JspGeqU8vXRumvUeuilEB7JqR9HhoD+hQ3qPIzdkqCCuwV +2N4wGBeY18HJAoIBAQDTaegJOtW7oXWAnJp526bxP8fW7QvKWViUnk/L0Hib7NsS +lF5GUUGlwe1Vt01C4uErXYnuepGhYSTi989jXYZPQTe1ihoAGDkqDrh7su9Af7BH +sOXWqsA+2+bImhvkj6sc3BIlCQxYBm9UdqJ0r7HhsMTT4ifuBtWb+X5hwVH/Nr4/ +ppdK5KHKI2JePCfDdnOqq7HOSVEwkNF0KkAflXF3P1/j8AeseJbvoB6zx7rpdQYt +O7agBXuWSdc7Y3UROeHD6ws+8K+YIWSgszT2OM77sEjYwy0hk+EcRgZkvEYp2VQy +GKgZqgNcUs6ZcW9iRAZg0yCJfcJqXNMkidmkXkVDAoIBACxEyBANnQp/MdNGGO47 +gLj0llm8UVh+BDv3/H7+LS+j4t8CvCS+rgiLEIfdeUHRDk1blKvQvu2Cv805gZHq +khqeDi6QBVF5Csge1nC06F7vS2vYgGFDkmh90rmE/4USa4w/dcVk7tcUMg75UvE/ +f+iFcEK7/PquVwlIYByjCO/7cgAKV5B2/zn4SYCLKtFdmgBWRHo21kE76viD/KRt +n47t2iFIIYzna9pJ3AsmC4Nh0TOiwk3SthYDCiHa1cTl5BK4erXpZUSX5BlgwGdx +19bSiUg60iKdo8FX7s63nJu81Uoq/3QFB/xwJfCiAyIDNaRDTYvAOsEqbAtz/k+l +nvkCggEAJM3Tys3lBjE2LgFqu7VFo/8WZ8GmGZjTO56sXmFauIP+Qh5bRmhOvkhO +/miLPDkmnQvciBhqbRv0wMAb2+VT1LHx5ZUoGh0X2wijlZE/WpWgA7qzaGt0xRWV +8YJt6Axa23Pbd/gH9u3hkSM3eO9Ous2E1cqtbzFuPmG9P89q5DUTIvGqfszcS4pG +ZpFKOjHQ1u+VNqYfGmv+/pDDWlDz90W29ztmSpGqvvkxcYDLApxizCeFouLATow7 +PCoh5h5I9xnQ5QTw/0iBeuKeCFTZuvj8JOLTPsRirxrWa9krIo6MzDDaO4RUgKel +EGMgVVXBAIcERwhHqC1V65M+9vUg/Q== +-----END PRIVATE KEY----- diff --git a/config/litellm_config.yml b/config/litellm_config.yml deleted file mode 100644 index ca6dc1db3e..0000000000 --- a/config/litellm_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -model_list: - - model_name: "*" - litellm_params: - model: openai/* - api_key: os.environ/OPENAI_API_KEY - -general_settings: - master_key: os.environ/LITELLM_MASTER_KEY - -litellm_settings: - # The following should set default customer budgets, but they are - # being ignored or not created (not sure which). - # https://docs.litellm.ai/docs/proxy/users - max_end_user_budget: os.environ/OPENAI_AI_MAX_BUDGET - max_end_user_budget_duration: os.environ/OPENAI_AI_MAX_BUDGET_DURATION diff --git a/config/postgres/init-keycloak.sql b/config/postgres/init-keycloak.sql new file mode 100644 index 0000000000..8a7e4d5c02 --- /dev/null +++ b/config/postgres/init-keycloak.sql @@ -0,0 +1,2 @@ +CREATE DATABASE keycloak; +GRANT ALL PRIVILEGES ON DATABASE keycloak TO postgres; diff --git a/docker-compose.litellm.yml b/docker-compose.litellm.yml deleted file mode 100644 index ee472ad56d..0000000000 --- a/docker-compose.litellm.yml +++ /dev/null @@ -1,23 +0,0 @@ -include: - - docker-compose.services.yml - -services: - litellm: - profiles: - - backend - build: - dockerfile: Dockerfile-litellm - ports: - - "4000:4000" - environment: - - DATABASE_URL=postgres://postgres:postgres@db:5432/litellm - - OPENAI_API_KEY=${OPENAI_API_KEY} - - LITELLM_MASTER_KEY=${AI_PROXY_AUTH_TOKEN} - - LITELLM_SALT_KEY=${AI_PROXY_AUTH_TOKEN} - depends_on: - db: - condition: service_healthy - redis: - condition: service_healthy - volumes: - - ./config:/app diff --git a/docker-compose.opensearch.base.yml b/docker-compose.opensearch.base.yml index 15ef3a0e4c..947efbc783 100644 --- a/docker-compose.opensearch.base.yml +++ b/docker-compose.opensearch.base.yml @@ -4,6 +4,7 @@ services: environment: - "cluster.name=opensearch-cluster" - "bootstrap.memory_lock=true" # along with the memlock settings below, disables swapping + - "_JAVA_OPTIONS=-XX:UseSVE=0" # disables SVE (Scalable Vector Extension) for ARM64 - "OPENSEARCH_JAVA_OPTS=-Xms1024m -Xmx1024m" # Set min and max JVM heap sizes to at least 50% of system RAM - "DISABLE_INSTALL_DEMO_CONFIG=true" # disables execution of install_demo_configuration.sh bundled with security plugin, which installs demo certificates and security configurations to OpenSearch - "DISABLE_SECURITY_PLUGIN=true" # disables security plugin entirely in OpenSearch by setting plugins.security.disabled: true in opensearch.yml diff --git a/docker-compose.services.yml b/docker-compose.services.yml index 57d355265b..3ec0cb1928 100644 --- a/docker-compose.services.yml +++ b/docker-compose.services.yml @@ -19,6 +19,7 @@ services: - POSTGRES_PASSWORD=postgres volumes: - pgdata:/var/lib/postgresql + - ./config/postgres:/docker-entrypoint-initdb.d - ./backups:/mnt/backups redis: @@ -34,7 +35,6 @@ services: - "6379" qdrant: image: qdrant/qdrant:latest - restart: always ports: - "6333:6333" volumes: @@ -88,9 +88,56 @@ services: profiles: - load-testing + keycloak: + profiles: + - keycloak + image: quay.io/keycloak/keycloak:latest + depends_on: + db: + condition: service_healthy + ports: + - ${KEYCLOAK_PORT}:${KEYCLOAK_PORT} + - ${KEYCLOAK_SSL_PORT}:${KEYCLOAK_SSL_PORT} + environment: + - KEYCLOAK_ADMIN=${KEYCLOAK_SVC_ADMIN:-admin} + - KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_SVC_ADMIN_PASSWORD:-admin} + networks: + default: + aliases: + - ${KEYCLOAK_SVC_HOSTNAME:-kc.ol.local} + command: start --verbose --features scripts --import-realm --hostname=${KEYCLOAK_SVC_HOSTNAME:-kc.ol.local} --hostname-strict=false --hostname-debug=true --https-port=${KEYCLOAK_SSL_PORT} --https-certificate-file=/etc/x509/https/tls.crt --https-certificate-key-file=/etc/x509/https/tls.key --http-enabled=true --http-port=${KEYCLOAK_PORT} --config-keystore=/etc/keycloak-store --config-keystore-password=${KEYCLOAK_SVC_KEYSTORE_PASSWORD} --db=postgres --db-url-database=keycloak --db-url-host=db --db-schema=public --db-password=${POSTGRES_PASSWORD:-postgres} --db-username=postgres --db-url-port=${PGPORT:-5432} + volumes: + - keycloak-store:/etc/keycloak-store + - ./config/keycloak/tls:/etc/x509/https + - ./config/keycloak/realms:/opt/keycloak/data/import + - ./config/keycloak/providers:/opt/keycloak/providers + - ./config/keycloak/themes:/opt/jboss/keycloak/themes + + apigateway: + profiles: + - apisix + image: apache/apisix:latest + environment: + - KEYCLOAK_REALM_NAME=${KEYCLOAK_REALM_NAME:-ol-local} + - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-apisix} + - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET} + - KEYCLOAK_DISCOVERY_URL=${KEYCLOAK_DISCOVERY_URL:-https://kc.ol.local:8066/realms/ol-local/.well-known/openid-configuration} + - KEYCLOAK_SCOPES=${KEYCLOAK_SCOPES:-openid,profile,ol-profile} + - APISIX_PORT=${APISIX_PORT:-8065} + - APISIX_SESSION_SECRET_KEY=${APISIX_SESSION_SECRET_KEY:-something_at_least_16_characters} + - APISIX_LOGOUT_URL=${APISIX_LOGOUT_URL:-http://open.odl.local:8065/} + - NGINX_PORT=${NGINX_PORT:-8062} + ports: + - ${APISIX_PORT}:${APISIX_PORT} + volumes: + - ./config/apisix/config.yaml:/usr/local/apisix/conf/config.yaml + - ./config/apisix/apisix.yaml:/usr/local/apisix/conf/apisix.yaml + - ./config/apisix/debug.yaml:/usr/local/apisix/conf/debug.yaml + volumes: pgdata: # note: these are here instead of docker-compose.apps.yml because `extends` doesn't pull them in django_media: yarn-cache: qdrant-data: + keycloak-store: diff --git a/docker-compose.yml b/docker-compose.yml index 584fe7c06c..0f9ff040f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,10 @@ include: - - docker-compose.services.yml + - path: docker-compose.services.yml + env_file: + - env/shared.env + - env/shared.local.env + - env/backend.env + - env/backend.local.env services: web: diff --git a/env/backend.env b/env/backend.env index a82e14ceba..1bc162adbc 100644 --- a/env/backend.env +++ b/env/backend.env @@ -8,7 +8,7 @@ CSRF_TRUSTED_ORIGINS='["http://open.odl.local:8062", "http://api.open.odl.local: CSRF_COOKIE_DOMAIN=open.odl.local CSRF_COOKIE_SECURE=False MITOL_COOKIE_DOMAIN=open.odl.local -MITOL_COOKIE_NAME=discussions +MITOL_COOKIE_NAME=mitlearn DEBUG=True diff --git a/env/backend.local.example.env b/env/backend.local.example.env index c9cb02854f..4df2d8cff5 100644 --- a/env/backend.local.example.env +++ b/env/backend.local.example.env @@ -15,8 +15,31 @@ # AUTHORIZATION_URL= # ACCESS_TOKEN_URL= # USERINFO_URL= -# KEYCLOAK_BASE_URL= -# KEYCLOAK_REALM_NAME= -# + # POSTHOG_PROJECT_ID= # POSTHOG_PERSONAL_API_KEY= + +# APISIX/Keycloak settings +APISIX_LOGOUT_URL=http://api.open.odl.local:8065/logout +APISIX_SESSION_SECRET_KEY=supertopsecret1234 +KC_SPI_THEME_WELCOME_THEME=scim +KC_SPI_REALM_RESTAPI_EXTENSION_SCIM_LICENSE_KEY= +KEYCLOAK_BASE_URL=http://kc.ol.local:8066 +KEYCLOAK_CLIENT_ID=apisix +# This is not a secret. This is for the Keycloak container, only for local use. +KEYCLOAK_CLIENT_SECRET=HckCZXToXfaetbBx0Fo3xbjnC468oMi4 # pragma: allowlist-secret +KEYCLOAK_DISCOVERY_URL=http://kc.ol.local:8066/realms/ol-local/.well-known/openid-configuration +KEYCLOAK_REALM_NAME=ol-local +KEYCLOAK_SCOPES="openid profile ol-profile" +KEYCLOAK_SVC_KEYSTORE_PASSWORD=supertopsecret1234 +KEYCLOAK_SVC_HOSTNAME=kc.ol.local +KEYCLOAK_SVC_ADMIN=admin +KEYCLOAK_SVC_ADMIN_PASSWORD=admin +AUTHORIZATION_URL=http://kc.ol.local:8066/realms/ol-local/protocol/openid-connect/auth +ACCESS_TOKEN_URL=http://kc.ol.local:8066/realms/ol-local/protocol/openid-connect/token +OIDC_ENDPOINT=http://kc.ol.local:8066/realms/ol-local +SOCIAL_AUTH_OL_OIDC_OIDC_ENDPOINT=http://kc.ol.local:8066/realms/ol-local +SOCIAL_AUTH_OL_OIDC_KEY=apisix +# This is not a secret. This is for the Keycloak container, only for local use. +SOCIAL_AUTH_OL_OIDC_SECRET=HckCZXToXfaetbBx0Fo3xbjnC468oMi4 # pragma: allowlist-secret +USERINFO_URL=http://kc.ol.local:8066/realms/ol-local/protocol/openid-connect/userinfo diff --git a/env/frontend.env b/env/frontend.env index 4efd9adbf8..569d3fde89 100644 --- a/env/frontend.env +++ b/env/frontend.env @@ -5,6 +5,7 @@ SENTRY_ENV=dev # Re-enable sentry # Environment variables with `NEXT_PUBLIC_` prefix are exposed to the client side NEXT_PUBLIC_ORIGIN=${MITOL_APP_BASE_URL} NEXT_PUBLIC_MITOL_API_BASE_URL=${MITOL_API_BASE_URL} +NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX=${MITOL_API_LOGOUT_SUFFIX} NEXT_PUBLIC_CSRF_COOKIE_NAME=${CSRF_COOKIE_NAME} NEXT_PUBLIC_MITOL_SUPPORT_EMAIL=${MITOL_SUPPORT_EMAIL} diff --git a/env/shared.env b/env/shared.env index 82a3653e03..c00aece744 100644 --- a/env/shared.env +++ b/env/shared.env @@ -1,6 +1,17 @@ MITOL_APP_BASE_URL=http://open.odl.local:8062 + +# Without apisix and keycloak MITOL_API_BASE_URL=http://api.open.odl.local:8063 +MITOL_API_LOGOUT_SUFFIX=logout +# With apisix and keycloak +#MITOL_API_BASE_URL=http://api.open.odl.local:8065 +#MITOL_API_LOGOUT_SUFFIX=logout/oidc + MITOL_SUPPORT_EMAIL=support@localhost CSRF_COOKIE_NAME=csrftoken-local POSTHOG_TIMEOUT_MS=1500 +NGINX_PORT=8063 +APISIX_PORT=8065 +KEYCLOAK_PORT=8066 +KEYCLOAK_SSL_PORT=8067 diff --git a/env/shared.local.example.env b/env/shared.local.example.env index 5e378f125a..25b4eccc50 100644 --- a/env/shared.local.example.env +++ b/env/shared.local.example.env @@ -3,3 +3,4 @@ # POSTHOG_PROJECT_API_KEY= # POSTHOG_TIMEOUT_MS=1500 # EMBEDLY_KEY= +MITOL_NEW_USER_LOGIN_URL=http://open.odl.local:8062/onboarding diff --git a/frontends/api/src/generated/v0/api.ts b/frontends/api/src/generated/v0/api.ts index 1190999f03..c236804628 100644 --- a/frontends/api/src/generated/v0/api.ts +++ b/frontends/api/src/generated/v0/api.ts @@ -10916,6 +10916,7 @@ export const VectorContentFilesSearchApiAxiosParamCreator = function ( * @param {string} [collection_name] Manually specify the name of the Qdrant collection to query * @param {Array} [content_feature_type] The feature type of the content file. Possible options are at api/v1/course_features/ * @param {Array} [course_number] Course number of the content file + * @param {Array} [edx_block_id] The edx_block_id of the content file * @param {Array} [file_extension] The extension of the content file. * @param {Array} [key] The filename of the content file * @param {number} [limit] Number of results to return per page @@ -10933,6 +10934,7 @@ export const VectorContentFilesSearchApiAxiosParamCreator = function ( collection_name?: string, content_feature_type?: Array, course_number?: Array, + edx_block_id?: Array, file_extension?: Array, key?: Array, limit?: number, @@ -10973,6 +10975,10 @@ export const VectorContentFilesSearchApiAxiosParamCreator = function ( localVarQueryParameter["course_number"] = course_number } + if (edx_block_id) { + localVarQueryParameter["edx_block_id"] = edx_block_id + } + if (file_extension) { localVarQueryParameter["file_extension"] = file_extension } @@ -11046,6 +11052,7 @@ export const VectorContentFilesSearchApiFp = function ( * @param {string} [collection_name] Manually specify the name of the Qdrant collection to query * @param {Array} [content_feature_type] The feature type of the content file. Possible options are at api/v1/course_features/ * @param {Array} [course_number] Course number of the content file + * @param {Array} [edx_block_id] The edx_block_id of the content file * @param {Array} [file_extension] The extension of the content file. * @param {Array} [key] The filename of the content file * @param {number} [limit] Number of results to return per page @@ -11063,6 +11070,7 @@ export const VectorContentFilesSearchApiFp = function ( collection_name?: string, content_feature_type?: Array, course_number?: Array, + edx_block_id?: Array, file_extension?: Array, key?: Array, limit?: number, @@ -11085,6 +11093,7 @@ export const VectorContentFilesSearchApiFp = function ( collection_name, content_feature_type, course_number, + edx_block_id, file_extension, key, limit, @@ -11140,6 +11149,7 @@ export const VectorContentFilesSearchApiFactory = function ( requestParameters.collection_name, requestParameters.content_feature_type, requestParameters.course_number, + requestParameters.edx_block_id, requestParameters.file_extension, requestParameters.key, requestParameters.limit, @@ -11184,6 +11194,13 @@ export interface VectorContentFilesSearchApiVectorContentFilesSearchRetrieveRequ */ readonly course_number?: Array + /** + * The edx_block_id of the content file + * @type {Array} + * @memberof VectorContentFilesSearchApiVectorContentFilesSearchRetrieve + */ + readonly edx_block_id?: Array + /** * The extension of the content file. * @type {Array} @@ -11279,6 +11296,7 @@ export class VectorContentFilesSearchApi extends BaseAPI { requestParameters.collection_name, requestParameters.content_feature_type, requestParameters.course_number, + requestParameters.edx_block_id, requestParameters.file_extension, requestParameters.key, requestParameters.limit, diff --git a/frontends/main/Dockerfile.web b/frontends/main/Dockerfile.web index e4a500f251..c1694ce691 100644 --- a/frontends/main/Dockerfile.web +++ b/frontends/main/Dockerfile.web @@ -146,6 +146,9 @@ ENV NEXT_PUBLIC_LEARN_AI_RECOMMENDATION_ENDPOINT=$NEXT_PUBLIC_LEARN_AI_RECOMMEND ARG NEXT_PUBLIC_LEARN_AI_SYLLABUS_ENDPOINT ENV NEXT_PUBLIC_LEARN_AI_SYLLABUS_ENDPOINT=$NEXT_PUBLIC_LEARN_AI_SYLLABUS_ENDPOINT +ARG NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX +ENV NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX=$NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX + ENV NEXT_PUBLIC_DEFAULT_SEARCH_MODE="phrase" ENV NEXT_PUBLIC_DEFAULT_SEARCH_SLOP="6" ENV NEXT_PUBLIC_DEFAULT_SEARCH_STALENESS_PENALTY="2.5" diff --git a/frontends/main/package.json b/frontends/main/package.json index 7de0302f82..3ca6e2db3c 100644 --- a/frontends/main/package.json +++ b/frontends/main/package.json @@ -17,7 +17,7 @@ "@mitodl/smoot-design": "^3.3.0", "@next/bundle-analyzer": "^14.2.15", "@remixicon/react": "^4.2.0", - "@sentry/nextjs": "^8.36.0", + "@sentry/nextjs": "^9.0.0", "@tanstack/react-query": "^5.66", "api": "workspace:*", "classnames": "^2.5.1", diff --git a/frontends/main/src/app-pages/ChatPage/ChatPage.tsx b/frontends/main/src/app-pages/ChatPage/ChatPage.tsx index c3744660a3..90846db4bf 100644 --- a/frontends/main/src/app-pages/ChatPage/ChatPage.tsx +++ b/frontends/main/src/app-pages/ChatPage/ChatPage.tsx @@ -49,6 +49,7 @@ const ChatPage = () => { headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ message: messages[messages.length - 1].content, diff --git a/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx b/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx index e57eadf4ea..70cacae2df 100644 --- a/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx +++ b/frontends/main/src/app-pages/ChatSyllabusPage/ChatSyllabusPage.tsx @@ -110,6 +110,7 @@ const ChatSyllabusPage = () => { headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ message: messages[messages.length - 1].content, diff --git a/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx b/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx index 53b3991a73..19c5a499cb 100644 --- a/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx +++ b/frontends/main/src/app-pages/DashboardPage/DashboardPage.tsx @@ -171,6 +171,10 @@ const TabsContainer = styled(TabList)(({ theme }) => ({ a: { padding: "0", opacity: "1", + + "&:focus-visible": { + outlineOffset: "-1px", + }, }, "&:hover": { a: { diff --git a/frontends/main/src/common/metadata.ts b/frontends/main/src/common/metadata.ts index 583e5a6383..b41966d98c 100644 --- a/frontends/main/src/common/metadata.ts +++ b/frontends/main/src/common/metadata.ts @@ -1,4 +1,4 @@ -import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" import { learningResourcesApi } from "api/clients" import type { Metadata } from "next" import handleNotFound from "./handleNotFound" @@ -28,7 +28,9 @@ export const getMetadataAsync = async ({ ...otherMeta }: MetadataAsyncProps) => { // The learning resource drawer is open - const learningResourceId = (await searchParams)?.[RESOURCE_DRAWER_QUERY_PARAM] + const learningResourceId = (await searchParams)?.[ + RESOURCE_DRAWER_PARAMS.resource + ] if (learningResourceId) { const { data } = await handleNotFound( learningResourcesApi.learningResourcesRetrieve({ diff --git a/frontends/main/src/common/urls.ts b/frontends/main/src/common/urls.ts index 8d8fd01d4b..9611dd62ef 100644 --- a/frontends/main/src/common/urls.ts +++ b/frontends/main/src/common/urls.ts @@ -54,9 +54,10 @@ if (process.env.NODE_ENV !== "production") { } const MITOL_API_BASE_URL = process.env.NEXT_PUBLIC_MITOL_API_BASE_URL +const MITOL_API_LOGOUT_SUFFIX = process.env.NEXT_PUBLIC_MITOL_API_LOGOUT_SUFFIX export const LOGIN = `${MITOL_API_BASE_URL}/login/ol-oidc/` -export const LOGOUT = `${MITOL_API_BASE_URL}/logout/` +export const LOGOUT = `${MITOL_API_BASE_URL}/${MITOL_API_LOGOUT_SUFFIX}/` /** * Returns the URL to the login page, with a `next` parameter to redirect back @@ -111,7 +112,12 @@ export const UNITS = "/units" export const CONTACT = "mailto:mitlearn-support@mit.edu" -export const RESOURCE_DRAWER_QUERY_PARAM = "resource" +export const RECOMMENDER_QUERY_PARAM = "recommender" + +export const RESOURCE_DRAWER_PARAMS = { + resource: "resource", + syllabus: "syllabus", +} as const export const querifiedSearchUrl = ( params: diff --git a/frontends/main/src/page-components/AiChat/AiChatWithEntryScreen.tsx b/frontends/main/src/page-components/AiChat/AiChatWithEntryScreen.tsx index d95beddcc4..d6213f5a18 100644 --- a/frontends/main/src/page-components/AiChat/AiChatWithEntryScreen.tsx +++ b/frontends/main/src/page-components/AiChat/AiChatWithEntryScreen.tsx @@ -170,7 +170,7 @@ const AiChatWithEntryScreen = ({ return ( {showEntryScreen ? ( - + diff --git a/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx b/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx index 8975b68a1e..4026ba4986 100644 --- a/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx +++ b/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx @@ -1,10 +1,11 @@ import React from "react" -import { styled, Drawer } from "ol-components" +import { styled, RoutedDrawer } from "ol-components" import { RiCloseLine } from "@remixicon/react" import { ActionButton } from "@mitodl/smoot-design" import type { AiChatProps } from "@mitodl/smoot-design/ai" import AiChatWithEntryScreen from "./AiChatWithEntryScreen" import { getCsrfToken } from "@/common/utils" +import { RECOMMENDER_QUERY_PARAM } from "@/common/urls" const CloseButton = styled(ActionButton)(({ theme }) => ({ position: "absolute", @@ -51,37 +52,15 @@ const STARTERS = [ }, ] -const AiRecommendationBotDrawer = ({ - open, - setOpen, -}: { - open: boolean - setOpen: (open: boolean) => void -}) => { - const closeDrawer = () => { - setOpen(false) - // setShowEntryScreen(true) - } - +const DrawerContent: React.FC<{ + onClose?: () => void +}> = ({ onClose }) => { return ( - ({ - [theme.breakpoints.down("md")]: { - width: "100%", - }, - }), - }, - }} - > + <> @@ -97,13 +76,37 @@ const AiRecommendationBotDrawer = ({ headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ message: messages[messages.length - 1].content, }), }} /> - + + ) +} + +const DRAWER_REQUIRED_PARAMS = [RECOMMENDER_QUERY_PARAM] as const +const AiRecommendationBotDrawer = () => { + return ( + ({ + [theme.breakpoints.down("md")]: { + width: "100%", + }, + }), + }, + }} + > + {({ closeDrawer }) => } + ) } diff --git a/frontends/main/src/page-components/AiChat/AskTimDrawerButton.test.tsx b/frontends/main/src/page-components/AiChat/AskTimDrawerButton.test.tsx new file mode 100644 index 0000000000..106d45493f --- /dev/null +++ b/frontends/main/src/page-components/AiChat/AskTimDrawerButton.test.tsx @@ -0,0 +1,42 @@ +import React from "react" +import { renderWithProviders, screen, user, waitFor } from "@/test-utils" +import AskTIMButton from "./AskTimDrawerButton" +import { RECOMMENDER_QUERY_PARAM } from "@/common/urls" + +describe("AskTIMButton", () => { + it.each([ + { url: "", open: false }, + { url: `?${RECOMMENDER_QUERY_PARAM}`, open: true }, + ])("Opens drawer based on URL param", async ({ url, open }) => { + renderWithProviders(, { + url, + }) + + const aiChat = screen.queryByTestId("ai-chat-entry-screen") + expect(!!aiChat).toBe(open) + }) + + test("Clicking button opens / closes drawer", async () => { + const { location } = renderWithProviders() + + expect(location.current.searchParams.has(RECOMMENDER_QUERY_PARAM)).toBe( + false, + ) + + const askTim = screen.getByRole("link", { name: /ask tim/i }) + + await user.click(askTim) + + expect(location.current.searchParams.has(RECOMMENDER_QUERY_PARAM)).toBe( + true, + ) + + await user.click(screen.getByRole("button", { name: "Close" })) + + await waitFor(() => { + expect(location.current.searchParams.has(RECOMMENDER_QUERY_PARAM)).toBe( + false, + ) + }) + }) +}) diff --git a/frontends/main/src/page-components/AiChat/AskTimDrawerButton.tsx b/frontends/main/src/page-components/AiChat/AskTimDrawerButton.tsx index 5be9380656..962d90d329 100644 --- a/frontends/main/src/page-components/AiChat/AskTimDrawerButton.tsx +++ b/frontends/main/src/page-components/AiChat/AskTimDrawerButton.tsx @@ -1,10 +1,11 @@ -import React, { useState } from "react" +import React from "react" import { Typography, styled } from "ol-components" -import { Button } from "@mitodl/smoot-design" +import { ButtonLink } from "@mitodl/smoot-design" import { RiSparkling2Line } from "@remixicon/react" import AiRecommendationBotDrawer from "./AiRecommendationBotDrawer" +import { RECOMMENDER_QUERY_PARAM } from "@/common/urls" -const StyledButton = styled(Button)(({ theme }) => ({ +const StyledButton = styled(ButtonLink)(({ theme }) => ({ display: "flex", flexDirection: "row", gap: "8px", @@ -30,21 +31,20 @@ const StyledButton = styled(Button)(({ theme }) => ({ })) const AskTIMButton = () => { - const [open, setOpen] = useState(false) - return ( <> setOpen(true)} + href={`?${RECOMMENDER_QUERY_PARAM}`} > AskTIM - + ) } diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx index 967badc543..4f244a59de 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.test.tsx @@ -1,19 +1,21 @@ import React from "react" import { + expectLastProps, expectProps, renderWithProviders, screen, + user, waitFor, within, } from "@/test-utils" import LearningResourceDrawer from "./LearningResourceDrawer" import { urls, factories, setMockResponse } from "api/test-utils" import { LearningResourceExpanded } from "../LearningResourceExpanded/LearningResourceExpanded" -import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" import { LearningResource, ResourceTypeEnum } from "api" import { makeUserSettings } from "@/test-utils/factories" import type { User } from "api/hooks/user" -import { usePostHog } from "posthog-js/react" +import { useFeatureFlagEnabled, usePostHog } from "posthog-js/react" jest.mock("../LearningResourceExpanded/LearningResourceExpanded", () => { const actual = jest.requireActual( @@ -31,6 +33,9 @@ jest.mocked(usePostHog).mockReturnValue( // @ts-expect-error Not mocking all of posthog { capture: mockedPostHogCapture }, ) +const mockedUseFeatureFlagEnabled = jest + .mocked(useFeatureFlagEnabled) + .mockImplementation(() => false) describe("LearningResourceDrawer", () => { const setupApis = ( @@ -94,7 +99,7 @@ describe("LearningResourceDrawer", () => { : "" renderWithProviders(, { - url: `?dog=woof&${RESOURCE_DRAWER_QUERY_PARAM}=${resource.id}`, + url: `?dog=woof&${RESOURCE_DRAWER_PARAMS.resource}=${resource.id}`, }) expect(LearningResourceExpanded).toHaveBeenCalled() await waitFor(() => { @@ -220,4 +225,80 @@ describe("LearningResourceDrawer", () => { similarResources.some((r) => text.includes(r.title)), ) }) + + it.each([ + { extraQueryParams: "", expectChat: false }, + { + extraQueryParams: `&${RESOURCE_DRAWER_PARAMS.syllabus}`, + expectChat: true, + }, + ])( + "Renders drawer with chatExpanded based on URL", + async ({ extraQueryParams, expectChat }) => { + mockedUseFeatureFlagEnabled.mockReturnValue(true) + const { resource } = setupApis({ + resource: { + // Chat is only enabled for courses + resource_type: ResourceTypeEnum.Course, + }, + }) + renderWithProviders(, { + url: `?resource=${resource.id}${extraQueryParams}`, + }) + + await screen.findByText(resource.title) + + await waitFor(() => { + expectLastProps(LearningResourceExpanded, { + resource, + chatExpanded: expectChat, + }) + }) + }, + ) + + test("If chat is not supported, 'syllabus' param removed from URL", async () => { + mockedUseFeatureFlagEnabled.mockReturnValue(true) + const { resource } = setupApis({ + resource: { + // Chat is only enabled for courses; NOT enabled here + resource_type: ResourceTypeEnum.Program, + }, + }) + const { location } = renderWithProviders(, { + url: `?resource=${resource.id}&syllabus`, + }) + + expect(location.current.searchParams.has("syllabus")).toBe(true) + + await waitFor(() => { + expectLastProps(LearningResourceExpanded, { + resource, + chatExpanded: false, + }) + }) + expect(location.current.searchParams.has("syllabus")).toBe(false) + }) + + test("Clicking 'Ask Tim' toggles chat query param", async () => { + mockedUseFeatureFlagEnabled.mockReturnValue(true) + const { resource } = setupApis({ + resource: { + // Chat is only enabled for courses + resource_type: ResourceTypeEnum.Course, + }, + }) + const { location } = renderWithProviders(, { + url: `?resource=${resource.id}`, + }) + + const askTimButton = await screen.findByRole("button", { name: /Ask\sTIM/ }) + expect(askTimButton).toBeInTheDocument() + + expect(location.current.searchParams.has("syllabus")).toBe(false) + await user.click(askTimButton) + expect(location.current.searchParams.has("syllabus")).toBe(true) + await user.click(askTimButton) + expect(location.current.searchParams.has("syllabus")).toBe(false) + }) }) diff --git a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx index 975ffb8d25..28e350885b 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx +++ b/frontends/main/src/page-components/LearningResourceDrawer/LearningResourceDrawer.tsx @@ -7,7 +7,7 @@ import type { } from "ol-components" import { useLearningResourcesDetail } from "api/hooks/learningResources" -import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" import { useUserMe } from "api/hooks/user" import NiceModal from "@ebay/nice-modal-react" import { @@ -23,7 +23,11 @@ import { TopicCarouselConfig } from "@/common/carousels" import { ResourceTypeEnum } from "api" import { PostHogEvents } from "@/common/constants" -const RESOURCE_DRAWER_PARAMS = [RESOURCE_DRAWER_QUERY_PARAM] as const +const REQUIRED_PARAMS = [RESOURCE_DRAWER_PARAMS.resource] as const +const ALL_PARAMS = [ + RESOURCE_DRAWER_PARAMS.resource, + RESOURCE_DRAWER_PARAMS.syllabus, +] as const const useCapturePageView = (resourceId: number) => { const { data, isSuccess } = useLearningResourcesDetail(Number(resourceId)) @@ -54,7 +58,8 @@ const DrawerContent: React.FC<{ resourceId: number titleId: string closeDrawer: () => void -}> = ({ resourceId, closeDrawer, titleId }) => { + chatExpanded: boolean +}> = ({ resourceId, closeDrawer, titleId, chatExpanded }) => { /** * Ideally the resource data should already exist in the query cache, e.g., by: * - a server-side prefetch @@ -207,8 +212,9 @@ const DrawerContent: React.FC<{ resource={resource.data} topCarousels={topCarousels} bottomCarousels={bottomCarousels} + chatExpanded={chatExpanded} user={user} - shareUrl={`${window.location.origin}/search?${RESOURCE_DRAWER_QUERY_PARAM}=${resourceId}`} + shareUrl={`${window.location.origin}/search?${RESOURCE_DRAWER_PARAMS.resource}=${resourceId}`} inLearningPath={inLearningPath} inUserList={inUserList} onAddToLearningPathClick={handleAddToLearningPathClick} @@ -244,7 +250,8 @@ const LearningResourceDrawer = () => { { {({ params, closeDrawer }) => { return ( ) diff --git a/frontends/main/src/page-components/LearningResourceDrawer/useResourceDrawerHref.ts b/frontends/main/src/page-components/LearningResourceDrawer/useResourceDrawerHref.ts index e4b7ce055f..ed000e47a4 100644 --- a/frontends/main/src/page-components/LearningResourceDrawer/useResourceDrawerHref.ts +++ b/frontends/main/src/page-components/LearningResourceDrawer/useResourceDrawerHref.ts @@ -1,5 +1,5 @@ import { useCallback } from "react" -import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" import { ReadonlyURLSearchParams, useSearchParams } from "next/navigation" const getOpenDrawerSearchParams = ( @@ -7,7 +7,7 @@ const getOpenDrawerSearchParams = ( resourceId: number, ) => { const newSearchParams = new URLSearchParams(current) - newSearchParams.set(RESOURCE_DRAWER_QUERY_PARAM, resourceId.toString()) + newSearchParams.set(RESOURCE_DRAWER_PARAMS.resource, resourceId.toString()) return newSearchParams } diff --git a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.test.tsx b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.test.tsx index c06675376a..3c390b1056 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.test.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.test.tsx @@ -18,7 +18,11 @@ describe("AiChatSyllabus", () => { setMockResponse.get(urls.userMe.get(), userMe) renderWithProviders( - , + , ) await user.click( @@ -37,7 +41,11 @@ describe("AiChatSyllabus", () => { setMockResponse.get(urls.userMe.get(), {}, { code: 403 }) renderWithProviders( - , + , ) const input = screen.getByRole("textbox") diff --git a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx index 3b94c148ce..e7455fed37 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react" +import React from "react" import { Typography, styled } from "ol-components" import { Button } from "@mitodl/smoot-design" import { @@ -116,18 +116,14 @@ const getInitialMessage = ( const AiChatSyllabusSlideDown = ({ resource, onToggleOpen, + open, }: { resource?: LearningResource + open: boolean onToggleOpen: (open: boolean) => void }) => { - const [open, setOpen] = useState(false) const user = useUserMe() - const toggleOpen = () => { - setOpen(!open) - onToggleOpen(!open) - } - if (!resource) return null return ( @@ -136,8 +132,9 @@ const AiChatSyllabusSlideDown = ({ onToggleOpen(open)} > @@ -157,6 +154,7 @@ const AiChatSyllabusSlideDown = ({ headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ collection_name: "content_files", diff --git a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx index 159d82992f..785b6189f4 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.test.tsx @@ -41,6 +41,7 @@ const setup = (props: SetupProps, opts?: SetupOpts) => { setMockResponse.get(urls.userMe.get(), user) const allProps: LearningResourceExpandedProps = { user: user, + chatExpanded: false, shareUrl: `https://learn.mit.edu/search?resource=${resourceId}`, imgConfig: IMG_CONFIG, ...props, @@ -388,16 +389,15 @@ describe.each([true, false])( resource_type: ResourceTypeEnum.PodcastEpisode, }) - const { rerender } = setup({ resource: course1 }) + const { rerender } = setup({ + resource: course1, + chatExpanded: true, + }) await user.click( screen.getByRole("button", { name: "Ask TIM about this course" }), ) - const input = screen.getByRole("textbox") - expect(input).toBeInTheDocument() - await user.type(input, "tell me more{enter}") - - const dataTestId = "ai-chat-screen" + const dataTestId = "ai-chat-entry-screen" expect(screen.getByTestId(dataTestId)).toBeInTheDocument() rerender({ resource: course2 }) expect(screen.getByTestId(dataTestId)).toBeInTheDocument() @@ -406,5 +406,28 @@ describe.each([true, false])( expect(screen.queryByTestId(dataTestId)).toBe(null) }) }) + + test.each([ + { chatExpanded: false, expectChat: false }, + { chatExpanded: true, expectChat: true }, + ])( + "When `chatExpanded=true`, chat button is pressed and interactive", + ({ chatExpanded, expectChat }) => { + if (!enabled) return + const resource = factories.learningResources.resource({ + resource_type: ResourceTypeEnum.Course, + }) + setup({ resource, chatExpanded }) + + screen.getByRole("button", { + name: /Ask\sTIM/, + pressed: chatExpanded, + }) + + // AiChat is always in the dom, but it's hidden and inert when not expanded. + const aiChat = screen.getByTestId("ai-chat-entry-screen") + expect(!!aiChat.closest("[inert]")).toBe(!expectChat) + }, + ) }, ) diff --git a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx index 1dc1db6d70..183f7406aa 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/LearningResourceExpanded.tsx @@ -4,7 +4,6 @@ import { theme } from "ol-components" import type { ImageConfig, LearningResourceCardProps } from "ol-components" import { ResourceTypeEnum } from "api" import type { LearningResource } from "api" -import { useToggle } from "ol-utilities" import InfoSection from "./InfoSection" import type { User } from "api/hooks/user" import TitleSection from "./TitleSection" @@ -13,6 +12,7 @@ import ResourceDescription from "./ResourceDescription" import { FeatureFlags } from "@/common/feature_flags" import { useFeatureFlagEnabled } from "posthog-js/react" import AiSyllabusBotSlideDown from "./AiChatSyllabusSlideDown" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" const DRAWER_WIDTH = "900px" @@ -112,6 +112,7 @@ const TopCarouselContainer = styled.div({ type LearningResourceExpandedProps = { resourceId: number + chatExpanded: boolean titleId?: string resource?: LearningResource user?: User @@ -126,6 +127,25 @@ type LearningResourceExpandedProps = { closeDrawer?: () => void } +const closeChat = () => { + const params = new URLSearchParams(window.location.search) + params.delete(RESOURCE_DRAWER_PARAMS.syllabus) + window.history.replaceState({}, "", `?${params.toString()}`) +} +const openChat = () => { + const params = new URLSearchParams(window.location.search) + params.set(RESOURCE_DRAWER_PARAMS.syllabus, "") + window.history.replaceState({}, "", `?${params.toString()}`) +} +const toggleChat = () => { + const params = new URLSearchParams(window.location.search) + if (params.has(RESOURCE_DRAWER_PARAMS.syllabus)) { + closeChat() + } else { + openChat() + } +} + const LearningResourceExpanded: React.FC = ({ resourceId, resource, @@ -140,12 +160,18 @@ const LearningResourceExpanded: React.FC = ({ onAddToLearningPathClick, onAddToUserListClick, closeDrawer, + chatExpanded, }) => { const chatEnabled = useFeatureFlagEnabled(FeatureFlags.LrDrawerChatbot) && resource?.resource_type === ResourceTypeEnum.Course - const [chatExpanded, setChatExpanded] = useToggle(false) + useEffect(() => { + // If URL indicates syllabus open, but it's not enabled, update URL + if (resource && !chatEnabled) { + closeChat() + } + }, [resource, chatEnabled]) const outerContainerRef = useRef(null) const titleSectionRef = useRef(null) @@ -186,7 +212,8 @@ const LearningResourceExpanded: React.FC = ({ ) : null} diff --git a/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx b/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx index 8d65a9dfee..406152d409 100644 --- a/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx +++ b/frontends/main/src/page-components/ResourceCard/ResourceCard.test.tsx @@ -18,7 +18,7 @@ import { } from "../Dialogs/AddToListDialog" import type { ResourceCardProps } from "./ResourceCard" import { urls, factories, setMockResponse } from "api/test-utils" -import { RESOURCE_DRAWER_QUERY_PARAM } from "@/common/urls" +import { RESOURCE_DRAWER_PARAMS } from "@/common/urls" import invariant from "tiny-invariant" import { LearningResourceCard, LearningResourceListCard } from "ol-components" @@ -220,7 +220,7 @@ describe.each([ const href = link.getAttribute("href") invariant(href) const url = new URL(href, window.location.href) - expect(url.searchParams.get(RESOURCE_DRAWER_QUERY_PARAM)).toBe( + expect(url.searchParams.get(RESOURCE_DRAWER_PARAMS.resource)).toBe( String(resource.id), ) }) diff --git a/frontends/main/src/test-utils/index.tsx b/frontends/main/src/test-utils/index.tsx index 17d27f01af..063defd903 100644 --- a/frontends/main/src/test-utils/index.tsx +++ b/frontends/main/src/test-utils/index.tsx @@ -1,7 +1,7 @@ /* eslint-disable import/no-extraneous-dependencies */ import React from "react" import { QueryClientProvider } from "@tanstack/react-query" -import { ThemeProvider } from "@mitodl/smoot-design" +import { ThemeProvider } from "ol-components" import { Provider as NiceModalProvider } from "@ebay/nice-modal-react" import type { QueryClient } from "@tanstack/react-query" @@ -128,7 +128,7 @@ const expectProps = ( */ const expectLastProps = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any - fc: jest.Mock, + fc: (...args: any[]) => void, partialProps: unknown, ) => { expect(fc).toHaveBeenLastCalledWith( diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index 413e47881b..f8c01f3e0f 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -13,16 +13,16 @@ "sideEffects": false, "dependencies": { "@dnd-kit/core": "^6.0.8", - "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.1", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mitodl/smoot-design": "^3.3.0", "@mui/base": "5.0.0-beta.69", - "@mui/lab": "6.0.0-beta.26", - "@mui/material": "^6.4.1", - "@mui/material-nextjs": "^6.3.1", - "@mui/system": "^6.4.1", + "@mui/lab": "6.0.0-beta.28", + "@mui/material": "^6.4.5", + "@mui/material-nextjs": "^6.4.3", + "@mui/system": "^6.4.3", "@remixicon/react": "^4.2.0", "@testing-library/dom": "^10.4.0", "@types/react-dom": "^19", diff --git a/frontends/ol-components/src/components/Checkbox/Checkbox.tsx b/frontends/ol-components/src/components/Checkbox/Checkbox.tsx index a65e7b3bae..5d4cca8e1f 100644 --- a/frontends/ol-components/src/components/Checkbox/Checkbox.tsx +++ b/frontends/ol-components/src/components/Checkbox/Checkbox.tsx @@ -64,6 +64,11 @@ const Container = styled.div` && input[type="checkbox"] { margin: 0; margin-right: 4px; + + /* Help avoid focus outline from being cutoff */ + :focus-visible { + outline-offset: -1px; + } } ${containerStyles} diff --git a/frontends/ol-components/src/components/Checkbox/CheckboxChoiceField.tsx b/frontends/ol-components/src/components/Checkbox/CheckboxChoiceField.tsx index 827f51070b..6d2ec8c469 100644 --- a/frontends/ol-components/src/components/Checkbox/CheckboxChoiceField.tsx +++ b/frontends/ol-components/src/components/Checkbox/CheckboxChoiceField.tsx @@ -36,7 +36,7 @@ const Label = styled(FormLabel)(({ theme }) => ({ width: "100%", color: theme.custom.colors.darkGray2, ...theme.typography.subtitle2, -})) +})) as typeof FormLabel // https://mui.com/material-ui/guides/typescript/?srsltid=AfmBOoo9kvRiALbxt4kAarRGiKaiJ7tbui5tstoL23DYscJPyk6UaTul#complications-with-the-component-prop const CheckboxChoiceField: React.FC = ({ label, @@ -59,7 +59,7 @@ const CheckboxChoiceField: React.FC = ({ className={className} disabled={disabled} > - {label && } + {label && } <_Container> {choices.map((choice) => { return ( diff --git a/frontends/ol-components/src/components/Link/Link.tsx b/frontends/ol-components/src/components/Link/Link.tsx index 9b31b2eb73..cdcdbb1603 100644 --- a/frontends/ol-components/src/components/Link/Link.tsx +++ b/frontends/ol-components/src/components/Link/Link.tsx @@ -1,9 +1,8 @@ import React from "react" import styled from "@emotion/styled" import { css } from "@emotion/react" -import { default as NextLink } from "next/link" import { theme } from "../ThemeProvider/ThemeProvider" -import invariant from "tiny-invariant" +import { LinkAdapter } from "../LinkAdapter/LinkAdapter" type LinkStyleProps = { size?: "small" | "medium" | "large" @@ -19,6 +18,12 @@ const DEFAULT_PROPS: Required = { nohover: false, } +const NO_FORWARD = Object.keys({ + size: false, + color: false, + hovercolor: false, + nohover: false, +} satisfies Record) /** * Generate styles used for the Link component. * @@ -73,38 +78,6 @@ type LinkProps = LinkStyleProps & prefetch?: boolean } -const BaseLink = ({ - href, - shallow, - nohover, - scroll, - onClick, - ...rest -}: LinkProps) => { - if (process.env.NODE_ENV === "development") { - invariant( - !shallow || href?.startsWith("?"), - "Shallow routing should only be used to update search params", - ) - } - return ( - { - e.preventDefault() - window.history.pushState({}, "", href) - } - : undefined) - } - /> - ) -} - /** * A styled link. By default, renders a medium-sized black link using the Link * component from `next/link`. This is appropriate for in-app routing. @@ -114,7 +87,9 @@ const BaseLink = ({ * * For a link styled as a button, use ButtonLink. */ -const Link = styled(BaseLink)(linkStyles) +const Link = styled(LinkAdapter, { + shouldForwardProp: (propName) => !NO_FORWARD.includes(propName), +})(linkStyles) export { Link, linkStyles } export type { LinkProps } diff --git a/frontends/ol-components/src/components/LinkAdapter/LinkAdapter.tsx b/frontends/ol-components/src/components/LinkAdapter/LinkAdapter.tsx new file mode 100644 index 0000000000..6e95f372cf --- /dev/null +++ b/frontends/ol-components/src/components/LinkAdapter/LinkAdapter.tsx @@ -0,0 +1,51 @@ +import React from "react" +import NextLink from "next/link" +import type { LinkProps } from "next/link" +import invariant from "tiny-invariant" + +type LinkAdapterExtraProps = Pick & { + /* + * If true, enables client-side-only routing via window.history.pushState. + * This is ONLY available for query-param updates, e.g., + * `href="?resource=123"`. + * + * This avoids calls to the NextJS server for RSC payloads that can cause + * performance and hydration mismatch issues for example where we are only + * updating the URL search params for modal views within the page, such as the + * resource drawer, and do not want to trigger calls to the server page which + * may re-fetch API data. + */ + shallow?: boolean + // Note: NextJS LinkProps actually does have a `shallow` prop, but at time of + // writing it is only supported by the Pages router, so the docs for it are + // unhelpful. +} + +type LinkAdapterProps = React.ComponentProps<"a"> & LinkAdapterExtraProps + +/** + * Default link implementation used for our smoot-design theme. + */ +const LinkAdapter = ({ shallow, href = "", ...props }: LinkAdapterProps) => { + invariant( + !shallow || href.startsWith("?"), + "shallow links must start with '?'", + ) + return ( + { + if (shallow) { + e.preventDefault() + window.history.pushState({}, "", href) + } else { + props.onClick?.(e) + } + }} + /> + ) +} + +export { LinkAdapter } +export type { LinkAdapterProps, LinkAdapterExtraProps } diff --git a/frontends/ol-components/src/components/RoutedDrawer/RoutedDrawer.tsx b/frontends/ol-components/src/components/RoutedDrawer/RoutedDrawer.tsx index 82b81f9ca8..fd9927a314 100644 --- a/frontends/ol-components/src/components/RoutedDrawer/RoutedDrawer.tsx +++ b/frontends/ol-components/src/components/RoutedDrawer/RoutedDrawer.tsx @@ -28,6 +28,13 @@ type RoutedDrawerProps = { }) => React.ReactNode } & Omit +/** + * Drawer that opens & closes based on the presence of required URL params. + * + * This is particularly useful when the drawer content depends on the URL + * parameters: the drawer handles removing the URL params *after* its closing + * animation. + */ const RoutedDrawer = ( props: RoutedDrawerProps, ) => { diff --git a/frontends/ol-components/src/components/SelectField/SelectField.test.tsx b/frontends/ol-components/src/components/SelectField/SelectField.test.tsx index d0566b9f48..63a4c45d5e 100644 --- a/frontends/ol-components/src/components/SelectField/SelectField.test.tsx +++ b/frontends/ol-components/src/components/SelectField/SelectField.test.tsx @@ -1,7 +1,8 @@ import React from "react" import { screen } from "@testing-library/react" -import { SelectField } from "./SelectField" -import type { SelectFieldProps } from "./SelectField" +import user from "@testing-library/user-event" +import { Select, SelectField } from "./SelectField" +import type { SelectFieldProps, SelectProps } from "./SelectField" import { faker } from "@faker-js/faker/locale/en" import MenuItem from "@mui/material/MenuItem" @@ -39,3 +40,52 @@ describe("SelectField", () => { expect(input).toBeRequired() }) }) + +describe("Select", () => { + const setup = (props?: Partial) => { + const defaults = { + name: "test-name", + value: "", + label: "test-label", + } + const { rerender: _rerender } = renderWithTheme( + , + ) + const rerender = (newProps: Partial) => { + _rerender() + } + return { rerender } + } + + /** + * This test exists to ensure our workaround for + * https://github.com/mui/material-ui/issues/23747 + * is behaving as expected. + */ + it("Applies class 'pointer-open' to menu if and only if opened via pointer", async () => { + setup() + const select = screen.getByRole("combobox") + const getMenu = () => document.querySelector(".MuiMenu-root") + + // Opened via pointer; has class pointer-open + await user.click(select) + expect(getMenu()).toHaveClass("pointer-open") + expect(document.activeElement).toHaveTextContent("Option 1") + + // close it + await user.keyboard("{Escape}") + expect(getMenu()).toBe(null) + expect(document.activeElement).toBe(select) + + // open via keyboard, does NOT have class pointer-open + await user.keyboard("{Enter}") + expect(getMenu()).not.toHaveClass("pointer-open") + expect(document.activeElement).toHaveTextContent("Option 1") + }) +}) diff --git a/frontends/ol-components/src/components/SelectField/SelectField.tsx b/frontends/ol-components/src/components/SelectField/SelectField.tsx index 055d9167a7..287c3c7159 100644 --- a/frontends/ol-components/src/components/SelectField/SelectField.tsx +++ b/frontends/ol-components/src/components/SelectField/SelectField.tsx @@ -96,6 +96,7 @@ const SelectIcon = styled(RiArrowDownSLine)({ width: "1em", }) +const POINTER_CLASSNAME = "pointer-open" /** * WARNING: You likely do not need this component. Try one of * @@ -105,12 +106,39 @@ const SelectIcon = styled(RiArrowDownSLine)({ * instead. */ function Select({ size, ...props }: SelectProps) { + const menu = React.useRef(null) return ( { + // This likely isn't necessasry---the Menu unmounts on close. + // But let's not rely on that. + menu.current?.classList.remove(POINTER_CLASSNAME) + }} + onPointerUp={() => { + menu.current?.classList.add(POINTER_CLASSNAME) + }} + MenuProps={{ + ref: menu, + sx: { + [`&.${POINTER_CLASSNAME} .MuiMenuItem-root.Mui-focusVisible`]: { + outline: "none", + }, + }, + onKeyDown: () => { + menu.current?.classList.remove(POINTER_CLASSNAME) + }, + }} input={} /> ) diff --git a/frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx b/frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx index 6760603b4c..a3d9410ce7 100644 --- a/frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx +++ b/frontends/ol-components/src/components/SimpleSelect/SimpleSelect.tsx @@ -75,7 +75,6 @@ const SimpleSelectField: React.FC = ({ }) => { return ( - {options.map(({ value, label, ...itemProps }) => ( {label} diff --git a/frontends/ol-components/src/components/ThemeProvider/ThemeProvider.tsx b/frontends/ol-components/src/components/ThemeProvider/ThemeProvider.tsx index e6475aedab..8fb20be34a 100644 --- a/frontends/ol-components/src/components/ThemeProvider/ThemeProvider.tsx +++ b/frontends/ol-components/src/components/ThemeProvider/ThemeProvider.tsx @@ -5,12 +5,20 @@ import { } from "@mitodl/smoot-design" import type {} from "@mitodl/smoot-design/type-augmentation" import type {} from "@mui/lab/themeAugmentation" -import Link from "next/link" import Image from "next/image" +import { LinkAdapter } from "../LinkAdapter/LinkAdapter" +import type { LinkAdapterExtraProps } from "../LinkAdapter/LinkAdapter" + +declare module "@mitodl/smoot-design" { + // Add extra props to smoot-design's LinkAdapter + // See https://mitodl.github.io/smoot-design/?path=/docs/smoot-design-themeprovider--docs + // eslint-disable-next-line @typescript-eslint/no-empty-object-type + interface LinkAdapterPropsOverrides extends LinkAdapterExtraProps {} +} const theme = createTheme({ custom: { - LinkAdapter: Link, + LinkAdapter, ImgAdapter: Image, }, components: { diff --git a/frontends/ol-utilities/package.json b/frontends/ol-utilities/package.json index 8a7deba262..c884a588db 100644 --- a/frontends/ol-utilities/package.json +++ b/frontends/ol-utilities/package.json @@ -13,7 +13,7 @@ "sideEffects": false, "dependencies": { "@dnd-kit/core": "^6.0.8", - "@dnd-kit/sortable": "^8.0.0", + "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.1", "@faker-js/faker": "^9.0.0", "api": "workspace:*", diff --git a/learning_resources/models.py b/learning_resources/models.py index c3db10b55c..1dd4ecb61f 100644 --- a/learning_resources/models.py +++ b/learning_resources/models.py @@ -838,13 +838,21 @@ def for_serialization(self): return self.select_related("run").prefetch_related( "content_tags", "run__learning_resource", + "run__learning_resource__course", + "run__learning_resource__platform", Prefetch( "run__learning_resource__topics", queryset=LearningResourceTopic.objects.for_serialization(), ), + Prefetch( + "run__learning_resource__offered_by", + queryset=LearningResourceOfferor.objects.for_serialization(), + ), Prefetch( "run__learning_resource__departments", - queryset=LearningResourceDepartment.objects.for_serialization(), + queryset=LearningResourceDepartment.objects.for_serialization( + prefetch_school=True + ).select_related("school"), ), ) diff --git a/main/factories.py b/main/factories.py index 2ea8eccb1d..02101d945a 100644 --- a/main/factories.py +++ b/main/factories.py @@ -4,7 +4,14 @@ import ulid from django.conf import settings -from factory import Faker, LazyFunction, RelatedFactory, SubFactory, Trait +from factory import ( + Faker, + LazyFunction, + RelatedFactory, + SelfAttribute, + SubFactory, + Trait, +) from factory.django import DjangoModelFactory from factory.fuzzy import FuzzyText from social_django.models import UserSocialAuth @@ -20,6 +27,9 @@ class UserFactory(DjangoModelFactory): profile = RelatedFactory("profiles.factories.ProfileFactory", "user") + scim_external_id = Faker("uuid4") + scim_username = SelfAttribute("email") + class Meta: model = settings.AUTH_USER_MODEL skip_postgeneration_save = True diff --git a/main/middleware/apisix_user.py b/main/middleware/apisix_user.py new file mode 100644 index 0000000000..c5def2acd0 --- /dev/null +++ b/main/middleware/apisix_user.py @@ -0,0 +1,52 @@ +"""APISIX Middleware for MIT Learn.""" + +import base64 +import json +import logging + +from django.contrib.auth.middleware import RemoteUserMiddleware + +log = logging.getLogger(__name__) + + +def decode_apisix_headers(request, header): + """ + Decode APISIX-specific headers and return the username as a dict. + + Returns: dict containing username from APISIX/Keycloak + """ + x_userinfo = request.META[header] + if not x_userinfo: + return None + + try: + apisix_result = json.loads(base64.b64decode(x_userinfo)) + if not apisix_result: + err_msg = "decode_apisix_headers: No APISIX-specific header found" + raise KeyError(err_msg) + except json.JSONDecodeError: + log.debug( + "decode_apisix_headers: Got bad APISIX-specific header: %s", + request.META.get("HTTP_X_USERINFO", ""), + ) + + return None + + log.debug("decode_apisix_headers: Got %s", apisix_result) + return {"username": apisix_result.get("preferred_username", None)} + + +class ApisixUserMiddleware(RemoteUserMiddleware): + """Checks for and processes APISIX-specific headers.""" + + header = "HTTP_X_USERINFO" + + def process_request(self, request): + """ + Modify the header to contaiin username, pass off to RemoteUserMiddleware + """ + if request.META.get(self.header): + new_header = decode_apisix_headers(request, self.header) + request.META["REMOTE_USER"] = new_header + + return super().process_request(request) diff --git a/main/middleware/apisix_user_test.py b/main/middleware/apisix_user_test.py new file mode 100644 index 0000000000..24fc17d85d --- /dev/null +++ b/main/middleware/apisix_user_test.py @@ -0,0 +1,36 @@ +"""Tests for apisix middleware.""" + +import json +from base64 import b64encode + +import pytest +from django.contrib.auth.models import AnonymousUser + +from main.middleware.apisix_user import ApisixUserMiddleware + + +@pytest.mark.django_db(transaction=True) +def test_get_request(mocker): + """Test RemoteUserMiddleware is called with expected headers""" + apisix_user_info = { + "global_id": "123456", + "preferred_username": "testuser", + "email": "testuser@test.edu", + "given_name": "test", + "family_name": "user", + "fullName": "test user fullname", + "emailOptIn": 0, + } + mock_process_request = mocker.patch( + "main.middleware.apisix_user.RemoteUserMiddleware.process_request" + ) + mock_request = mocker.Mock( + META={ + "HTTP_X_USERINFO": b64encode(json.dumps(apisix_user_info).encode()), + }, + user=AnonymousUser(), + ) + apisix_middleware = ApisixUserMiddleware(mocker.Mock()) + apisix_middleware.process_request(mock_request) + mock_process_request.assert_called_once_with(mock_request) + assert mock_request.META.get("REMOTE_USER") == {"username": "testuser"} diff --git a/main/settings.py b/main/settings.py index cf9b1749cc..95f6f05c02 100644 --- a/main/settings.py +++ b/main/settings.py @@ -33,7 +33,7 @@ from main.settings_pluggy import * # noqa: F403 from openapi.settings_spectacular import open_spectacular_settings -VERSION = "0.30.6" +VERSION = "0.30.7" log = logging.getLogger() @@ -158,6 +158,7 @@ "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", + "main.middleware.apisix_user.ApisixUserMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "corsheaders.middleware.CorsMiddleware", @@ -743,6 +744,7 @@ def get_all_config_keys(): "DEFAULT_PARENT_LOOKUP_KWARG_NAME_PREFIX": DRF_NESTED_PARENT_LOOKUP_PREFIX } +# Keycloak API settings KEYCLOAK_BASE_URL = get_string( name="KEYCLOAK_BASE_URL", default="http://mit-keycloak-base-url.edu", diff --git a/openapi/specs/v0.yaml b/openapi/specs/v0.yaml index 1cb7224969..f22b9b2fe2 100644 --- a/openapi/specs/v0.yaml +++ b/openapi/specs/v0.yaml @@ -854,6 +854,14 @@ paths: type: string minLength: 1 description: Course number of the content file + - in: query + name: edx_block_id + schema: + type: array + items: + type: string + minLength: 1 + description: The edx_block_id of the content file - in: query name: file_extension schema: diff --git a/poetry.lock b/poetry.lock index 8fa5510e7f..1f2bba49bb 100644 --- a/poetry.lock +++ b/poetry.lock @@ -7226,20 +7226,25 @@ test = ["pytest", "ruff"] [[package]] name = "tldextract" -version = "2.2.3" -description = "Accurately separate the TLD from the registered domain and subdomains of a URL, using the Public Suffix List. By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." +version = "5.1.3" +description = "Accurately separates a URL's subdomain, domain, and public suffix, using the Public Suffix List (PSL). By default, this includes the public ICANN TLDs and their exceptions. You can optionally support the Public Suffix List's private domains as well." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=3.9" files = [ - {file = "tldextract-2.2.3-py2.py3-none-any.whl", hash = "sha256:c2a8a392edf3ea6fa8be80930f04c3ac29e91fa604cb2139bdf6a37fc1e1ac6d"}, - {file = "tldextract-2.2.3.tar.gz", hash = "sha256:ab0e38977a129c72729476d5f8c85a8e1f8e49e9202e1db8dca76e95da7be9a8"}, + {file = "tldextract-5.1.3-py3-none-any.whl", hash = "sha256:78de310cc2ca018692de5ddf320f9d6bd7c5cf857d0fd4f2175f0cdf4440ea75"}, + {file = "tldextract-5.1.3.tar.gz", hash = "sha256:d43c7284c23f5dc8a42fd0fee2abede2ff74cc622674e4cb07f514ab3330c338"}, ] [package.dependencies] +filelock = ">=3.0.8" idna = "*" requests = ">=2.1.0" requests-file = ">=1.4" +[package.extras] +release = ["build", "twine"] +testing = ["mypy", "pytest", "pytest-gitignore", "pytest-mock", "responses", "ruff", "syrupy", "tox", "tox-uv", "types-filelock", "types-requests"] + [[package]] name = "tokenizers" version = "0.21.0" @@ -7844,4 +7849,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.6" -content-hash = "19a6194e1e74fc5a8bb3ab881c9b30118cfec612402a6b943ebb22bc80fa572b" +content-hash = "720f25b86477babc8dc3b3d70c6483f6789187f30c9ec1d7343d1f146e1e6dd5" diff --git a/profiles/admin.py b/profiles/admin.py index 06211236a5..38613ba64a 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -27,9 +27,6 @@ class ProfileAdmin(admin.ModelAdmin): "image_small_file", "image_medium_file", "updated_at", - "scim_id", - "scim_username", - "scim_external_id", ) diff --git a/profiles/forms.py b/profiles/forms.py index 5fef0a7c1b..601b62b35f 100644 --- a/profiles/forms.py +++ b/profiles/forms.py @@ -30,7 +30,4 @@ class Meta: "current_education", "time_commitment", "delivery", - "scim_id", - "scim_username", - "scim_external_id", ] diff --git a/pyproject.toml b/pyproject.toml index e91ae4d570..4f3ba8fd7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -59,7 +59,7 @@ sentry-sdk = "^2.13.0" social-auth-app-django = "^5.2.0" static3 = "^0.7.0" tika = "^2.6.0" -tldextract = "^2.2.0" +tldextract = "^5.0.0" toolz = "^1.0.0" ulid-py = "^0.2.0" urllib3 = "^2.0.0" diff --git a/scim/adapters.py b/scim/adapters.py index 6983d4c480..e85ac01612 100644 --- a/scim/adapters.py +++ b/scim/adapters.py @@ -38,7 +38,7 @@ class LearnSCIMUser(SCIMUser): resource_type = "User" - id_field = "profile__scim_id" + id_field = "scim_id" ATTR_MAP = { ("active", None, None): "is_active", @@ -66,7 +66,7 @@ def id(self): """ Return the SCIM id """ - return self.obj.profile.scim_id + return self.obj.scim_id @property def emails(self): @@ -89,10 +89,8 @@ def meta(self): """ return { "resourceType": self.resource_type, - "created": self.obj.date_joined.isoformat(timespec="milliseconds"), - "lastModified": self.obj.profile.updated_at.isoformat( - timespec="milliseconds" - ), + "created": self.obj.created_on.isoformat(timespec="milliseconds"), + "lastModified": self.obj.updated_on.isoformat(timespec="milliseconds"), "location": self.location, } @@ -103,7 +101,7 @@ def to_dict(self): """ return { "id": self.id, - "externalId": self.obj.profile.scim_external_id, + "externalId": self.obj.scim_external_id, "schemas": [constants.SchemaURI.USER], "userName": self.obj.username, "name": { @@ -135,10 +133,10 @@ def from_dict(self, d): self.obj.username = d.get("userName") self.obj.first_name = d.get("name", {}).get("givenName", "") self.obj.last_name = d.get("name", {}).get("familyName", "") + self.obj.scim_username = d.get("userName") + self.obj.scim_external_id = d.get("externalId") self.obj.profile = getattr(self.obj, "profile", Profile()) - self.obj.profile.scim_username = d.get("userName") - self.obj.profile.scim_external_id = d.get("externalId") self.obj.profile.name = d.get("fullName", "") self.obj.profile.email_optin = d.get("emailOptIn", 1) == 1 @@ -179,7 +177,7 @@ def handle_add( return if path.first_path == ("externalId", None, None): - self.obj.profile.scim_external_id = value + self.obj.scim_external_id = value self.obj.save() def parse_scim_for_keycloak_payload(self, payload: str) -> dict: diff --git a/scim/urls.py b/scim/urls.py index 88221d982e..f5fd81c52c 100644 --- a/scim/urls.py +++ b/scim/urls.py @@ -6,12 +6,13 @@ ol_scim_urls = ( [ - re_path("^Bulk$", views.BulkView.as_view(), name="bulk"), + re_path(r"^Bulk$", views.BulkView.as_view(), name="bulk"), + re_path(r"^Users/\.search$", views.SearchView.as_view(), name="users-search"), ], "ol-scim", ) urlpatterns = [ - re_path("^scim/v2/", include(ol_scim_urls)), - re_path("^scim/v2/", include("django_scim.urls", namespace="scim")), + re_path(r"^scim/v2/", include(ol_scim_urls)), + re_path(r"^scim/v2/", include("django_scim.urls", namespace="scim")), ] diff --git a/scim/views.py b/scim/views.py index 72dcc09a65..40517d9f54 100644 --- a/scim/views.py +++ b/scim/views.py @@ -4,13 +4,14 @@ import json import logging from http import HTTPStatus -from urllib.parse import urlparse +from urllib.parse import urljoin, urlparse from django.http import HttpRequest, HttpResponse -from django.urls import Resolver404, resolve +from django.urls import Resolver404, resolve, reverse from django_scim import constants as djs_constants from django_scim import exceptions from django_scim import views as djs_views +from django_scim.utils import get_base_scim_location_getter from scim import constants @@ -158,3 +159,53 @@ def _operation_error(self, method, bulk_id, status_code, detail): "detail": detail, }, } + + +class SearchView(djs_views.UserSearchView): + """ + View for /.search endpoint + """ + + def post(self, request, *args, **kwargs): # noqa: ARG002 + body = self.load_body(request.body) + if body.get("schemas") != [djs_constants.SchemaURI.SERACH_REQUEST]: + msg = "Invalid schema uri. Must be SearchRequest." + raise exceptions.BadRequestError(msg) + + start = body.get("startIndex", 1) + count = body.get("count", 50) + sort_by = body.get("sortBy", "id") + sort_order = body.get("sortOrder", "ascending") + query = body.get("filter", None) + + if sort_by is not None and sort_by not in ("id", "email", "username"): + msg = "Sorting only supports email or username" + raise exceptions.BadRequestError(msg) + + if sort_order is not None and sort_order not in ("ascending", "descending"): + msg = "Sorting only supports ascending or descending" + raise exceptions.BadRequestError(msg) + + if not query: + msg = "No filter query specified" + raise exceptions.BadRequestError(msg) + + try: + qs = self.__class__.parser_getter().search(query, request) + except ValueError as e: + msg = "Invalid filter/search query: " + str(e) + raise exceptions.BadRequestError(msg) from e + + qs = qs.order_by(sort_by) + + if sort_order == "descending": + qs = qs.reverse() + + response = self._build_response(request, qs, start, count) + + path = reverse(self.scim_adapter.url_name) + url = urljoin(get_base_scim_location_getter()(request=request), path).rstrip( + "/" + ) + response["Location"] = url + "/.search" + return response diff --git a/scim/views_test.py b/scim/views_test.py index a3b86a962a..b35f261263 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -28,9 +28,17 @@ def scim_client(staff_user): return client +@pytest.fixture(scope="module") +def large_user_set(django_db_setup, django_db_blocker): + """Large set of users""" + # per https://pytest-django.readthedocs.io/en/latest/database.html#populate-the-test-database-if-you-don-t-use-transactional-or-live-server + with django_db_blocker.unblock(): + yield UserFactory.create_batch(1100) + + def test_scim_user_post(scim_client): """Test that we can create a user via SCIM API""" - user_q = User.objects.filter(profile__scim_external_id="1") + user_q = User.objects.filter(scim_external_id="1") assert not user_q.exists() resp = scim_client.post( @@ -71,7 +79,7 @@ def test_scim_user_put(scim_client): user = UserFactory.create() resp = scim_client.put( - f"{reverse('scim:users')}/{user.profile.scim_id}", + f"{reverse('scim:users')}/{user.scim_id}", content_type="application/scim+json", data=json.dumps( { @@ -107,7 +115,7 @@ def test_scim_user_patch(scim_client): user = UserFactory.create() resp = scim_client.patch( - f"{reverse('scim:users')}/{user.profile.scim_id}", + f"{reverse('scim:users')}/{user.scim_id}", content_type="application/scim+json", data=json.dumps( { @@ -208,7 +216,7 @@ def _put_operation(user, data, bulk_id_gen): payload={ "method": "put", "bulkId": bulk_id, - "path": f"/Users/{user.profile.scim_id}", + "path": f"/Users/{user.scim_id}", "data": _user_to_scim_payload(data), }, user=user, @@ -218,7 +226,7 @@ def _put_operation(user, data, bulk_id_gen): "location": ANY_STR, "bulkId": bulk_id, "status": "200", - "id": str(user.profile.scim_id), + "id": str(user.scim_id), }, ) @@ -241,7 +249,7 @@ def _expected_patch_value(field): payload={ "method": "patch", "bulkId": bulk_id, - "path": f"/Users/{user.profile.scim_id}", + "path": f"/Users/{user.scim_id}", "data": { "schemas": [djs_constants.SchemaURI.PATCH_OP], "Operations": [ @@ -268,7 +276,7 @@ def _expected_patch_value(field): "location": ANY_STR, "bulkId": bulk_id, "status": "200", - "id": str(user.profile.scim_id), + "id": str(user.scim_id), }, ) @@ -280,7 +288,7 @@ def _delete_operation(user, bulk_id_gen): payload={ "method": "delete", "bulkId": bulk_id, - "path": f"/Users/{user.profile.scim_id}", + "path": f"/Users/{user.scim_id}", }, user=user, expected_user_state=None, @@ -415,28 +423,93 @@ def test_bulk_post(scim_client, bulk_test_data): assert actual_value == expected_value -def test_user_search(scim_client): +@pytest.mark.parametrize( + ("sort_by", "sort_order"), + [ + (None, None), + ("id", None), + ("id", "ascending"), + ("id", "descending"), + ("email", None), + ("email", "ascending"), + ("email", "descending"), + ("username", None), + ("username", "ascending"), + ("username", "descending"), + ], +) +@pytest.mark.parametrize("count", [None, 100, 500]) +def test_user_search(large_user_set, scim_client, sort_by, sort_order, count): """Test the user search endpoint""" - users = UserFactory.create_batch(1500) - emails = [user.email for user in users[:1000]] + search_users = large_user_set[:1000] + emails = [user.email for user in search_users] - resp = scim_client.post( - f"{reverse('scim:users-search')}?count={len(emails)}", - content_type="application/scim+json", - data=json.dumps( - { - "schemas": [djs_constants.SchemaURI.SERACH_REQUEST], - "filter": " OR ".join([f'email EQ "{email}"' for email in emails]), - } - ), - ) + expected = search_users - assert resp.status_code == 200 + effective_count = count or 50 + effective_sort_by = sort_by or "id" + effective_sort_order = sort_order or "ascending" + + def _sort(user): + value = getattr(user, effective_sort_by) + + # postgres sort is case-insensitive + return value.lower() if isinstance(value, str) else value - data = resp.json() + expected = sorted( + expected, + key=_sort, + reverse=effective_sort_order == "descending", + ) - assert data["totalResults"] == len(emails) - assert len(data["Resources"]) == len(emails) + for page in range(int(len(emails) / effective_count)): + start_index = page * effective_count # zero based index + resp = scim_client.post( + reverse("ol-scim:users-search"), + content_type="application/scim+json", + data=json.dumps( + { + "schemas": [djs_constants.SchemaURI.SERACH_REQUEST], + "filter": " OR ".join([f'email EQ "{email}"' for email in emails]), + "startIndex": start_index + 1, # SCIM API is 1-based index + **({"sortBy": sort_by} if sort_by is not None else {}), + **({"sortOrder": sort_order} if sort_order is not None else {}), + **({"count": count} if count is not None else {}), + } + ), + ) - for resource in data["Resources"]: - assert resource["emails"][0]["value"] in emails + expected_in_resp = expected[start_index : start_index + effective_count] + + assert resp.status_code == 200, f"Got error: {resp.content}" + assert resp.json() == { + "totalResults": len(emails), + "itemsPerPage": effective_count, + "startIndex": start_index + 1, + "schemas": [djs_constants.SchemaURI.LIST_RESPONSE], + "Resources": [ + { + "id": user.scim_id, + "active": user.is_active, + "userName": user.username, + "displayName": user.profile.name, + "emails": [{"value": user.email, "primary": True}], + "externalId": str(user.scim_external_id), + "name": { + "givenName": user.first_name, + "familyName": user.last_name, + }, + "meta": { + "resourceType": "User", + "location": f"https://localhost/scim/v2/Users/{user.scim_id}", + "lastModified": user.updated_on.isoformat( + timespec="milliseconds" + ), + "created": user.created_on.isoformat(timespec="milliseconds"), + }, + "groups": [], + "schemas": [djs_constants.SchemaURI.USER], + } + for user in expected_in_resp + ], + } diff --git a/users/admin.py b/users/admin.py new file mode 100644 index 0000000000..0aac66c825 --- /dev/null +++ b/users/admin.py @@ -0,0 +1,24 @@ +"""Users admin""" + +from django.contrib import admin +from django.contrib.auth.admin import UserAdmin as ContribUserAdmin +from hijack.contrib.admin import HijackUserAdminMixin + +from users.models import User + + +@admin.register(User) +class UserAdmin(ContribUserAdmin, HijackUserAdminMixin): + """Admin for User""" + + readonly_fields = ( + *ContribUserAdmin.readonly_fields, + "scim_id", + "scim_username", + "scim_external_id", + ) + + fieldsets = ( + *ContribUserAdmin.fieldsets, + ("SCIM", {"fields": ("scim_id", "scim_username", "scim_external_id")}), + ) diff --git a/users/migrations/0004_add_scim_and_timestamp_fields.py b/users/migrations/0004_add_scim_and_timestamp_fields.py new file mode 100644 index 0000000000..1ab22e5a9b --- /dev/null +++ b/users/migrations/0004_add_scim_and_timestamp_fields.py @@ -0,0 +1,70 @@ +# Generated by Django 4.2.19 on 2025-02-19 18:01 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0003_rename_user_table"), + ] + + operations = [ + migrations.RunSQL( + sql="DROP VIEW auth_user;", + reverse_sql="CREATE VIEW auth_user AS SELECT * FROM users_user;", + elidable=True, + ), + migrations.AddField( + model_name="user", + name="created_on", + field=models.DateTimeField( + auto_now_add=True, db_index=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="user", + name="scim_external_id", + field=models.CharField( + blank=True, + db_index=True, + default=None, + help_text="A string that is an identifier for the resource as defined by the provisioning client.", # noqa: E501 + max_length=254, + null=True, + verbose_name="SCIM External ID", + ), + ), + migrations.AddField( + model_name="user", + name="scim_id", + field=models.CharField( + blank=True, + default=None, + help_text="A unique identifier for a SCIM resource as defined by the service provider.", # noqa: E501 + max_length=254, + null=True, + unique=True, + verbose_name="SCIM ID", + ), + ), + migrations.AddField( + model_name="user", + name="scim_username", + field=models.CharField( + blank=True, + db_index=True, + default=None, + help_text="A service provider's unique identifier for the user", + max_length=254, + null=True, + verbose_name="SCIM Username", + ), + ), + migrations.AddField( + model_name="user", + name="updated_on", + field=models.DateTimeField(auto_now=True, null=True), + ), + ] diff --git a/users/migrations/0005_set_user_scim_id.py b/users/migrations/0005_set_user_scim_id.py new file mode 100644 index 0000000000..6370dd7b78 --- /dev/null +++ b/users/migrations/0005_set_user_scim_id.py @@ -0,0 +1,67 @@ +# Generated by Django 4.2.19 on 2025-02-19 16:16 +import logging + +from django.db import migrations, models + +BATCH_SIZE = 10_000 + +log = logging.getLogger() + + +def _set_scim_and_timestamps(apps, schema_editor): + User = apps.get_model("users", "User") + Profile = apps.get_model("profiles", "Profile") + + query = User.objects.filter( + id__in=User.objects.filter(scim_id__isnull=True).only("id")[:BATCH_SIZE] + ) + + while num_updates := query.update( + # this uses the user.id to avoid conflicts with new users + scim_id=models.F("id"), + scim_external_id=models.Subquery( + Profile.objects.filter(user_id=models.OuterRef("id")).values( + "scim_external_id" + )[:1] + ), + scim_username=models.Subquery( + Profile.objects.filter(user_id=models.OuterRef("id")).values( + "scim_username" + )[:1] + ), + # created_on previously got a default of timestamp.now + # so we update it with the correct date + created_on=models.F("date_joined"), + # this would've been null + updated_on=models.Subquery( + Profile.objects.filter(user_id=models.OuterRef("id")).values("updated_at")[ + :1 + ] + ), + ): + log.info("Updated %s user records", num_updates) + + +class Migration(migrations.Migration): + """ + This is a separate migration from 0004 because for performance reasons + we don't want to update the entire table in a transaction but we DO + want the schema changes in 0004 in a transaction. + """ + + atomic = False + + dependencies = [ + ("users", "0004_add_scim_and_timestamp_fields"), + ] + + # we don't bother to undo these changes because if we're rolling back the columns + # just get dropped in the previous migration + operations = [ + migrations.RunPython(_set_scim_and_timestamps, migrations.RunPython.noop), + migrations.AlterField( + model_name="user", + name="updated_on", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/users/models.py b/users/models.py index a03ddf4c4e..91fc6cc8b5 100644 --- a/users/models.py +++ b/users/models.py @@ -1,7 +1,10 @@ """Users models""" from django.contrib.auth.models import AbstractUser +from django_scim.models import AbstractSCIMUserMixin +from main.models import TimestampedModel -class User(AbstractUser): + +class User(AbstractUser, AbstractSCIMUserMixin, TimestampedModel): """Custom model for users""" diff --git a/vector_search/constants.py b/vector_search/constants.py index 5343665f29..2c59a8b699 100644 --- a/vector_search/constants.py +++ b/vector_search/constants.py @@ -14,6 +14,11 @@ "run_readable_id": "run_readable_id", "resource_readable_id": "resource_readable_id", "run_title": "run_title", + "edx_block_id": "edx_block_id", + "content_type": "content_type", + "description": "description", + "url": "url", + "file_type": "file_type", } QDRANT_RESOURCE_PARAM_MAP = { @@ -66,4 +71,5 @@ "run_readable_id": models.PayloadSchemaType.INTEGER, "resource_readable_id": models.PayloadSchemaType.KEYWORD, "run_title": models.PayloadSchemaType.KEYWORD, + "edx_block_id": models.PayloadSchemaType.KEYWORD, } diff --git a/vector_search/serializers.py b/vector_search/serializers.py index d04910c8e1..facfcc1f02 100644 --- a/vector_search/serializers.py +++ b/vector_search/serializers.py @@ -229,6 +229,11 @@ class ContentFileVectorSearchRequestSerializer(serializers.Serializer): "The readable_id value of the parent learning resource for the content file" ), ) + edx_block_id = serializers.ListField( + required=False, + child=serializers.CharField(), + help_text="The edx_block_id of the content file", + ) collection_name = serializers.CharField( required=False, help_text=("Manually specify the name of the Qdrant collection to query"), diff --git a/vector_search/tasks.py b/vector_search/tasks.py index 7c6c0d00b3..1a0249e343 100644 --- a/vector_search/tasks.py +++ b/vector_search/tasks.py @@ -105,7 +105,9 @@ def start_embed_resources(self, indexes, skip_content_files, overwrite): .order_by("id") ): run = ( - course.runs.filter(published=True) + course.next_run + if course.next_run + else course.runs.filter(published=True) .order_by("-start_date") .first() ) @@ -193,7 +195,9 @@ def embed_learning_resources_by_id(self, ids, skip_content_files, overwrite): etl_source__in=RESOURCE_FILE_ETL_SOURCES ).order_by("id"): run = ( - course.runs.filter(published=True) + course.next_run + if course.next_run + else course.runs.filter(published=True) .order_by("-start_date") .first() ) diff --git a/vector_search/tasks_test.py b/vector_search/tasks_test.py index 1c76770c5c..83b76e5705 100644 --- a/vector_search/tasks_test.py +++ b/vector_search/tasks_test.py @@ -221,3 +221,78 @@ def test_embed_learning_resources_by_id(mocker, mocked_celery): assert mock_call.args[1] == "content_file" embedded_resource_ids = generate_embeddings_mock.si.mock_calls[0].args[0] assert sorted(resource_ids) == sorted(embedded_resource_ids) + + +def test_embedded_content_from_next_run(mocker, mocked_celery): + """ + Content files to embed should come from next course run + """ + + mocker.patch("vector_search.tasks.load_course_blocklist", return_value=[]) + + course = CourseFactory.create(etl_source=ETLSource.ocw.value) + + other_run = LearningResourceRunFactory.create( + learning_resource=course.learning_resource, + created_on=datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(days=2), + ) + LearningResourceRunFactory.create( + learning_resource=course.learning_resource, + created_on=datetime.datetime.now(tz=datetime.UTC), + ) + + next_run_contentfiles = [ + cf.id + for cf in ContentFileFactory.create_batch( + 3, run=course.learning_resource.next_run + ) + ] + # create contentfiles using the other run + ContentFileFactory.create_batch(3, run=other_run) + + generate_embeddings_mock = mocker.patch( + "vector_search.tasks.generate_embeddings", autospec=True + ) + + with pytest.raises(mocked_celery.replace_exception_class): + start_embed_resources.delay( + ["course"], skip_content_files=False, overwrite=True + ) + + generate_embeddings_mock.si.assert_called_with( + next_run_contentfiles, + "content_file", + True, # noqa: FBT003 + ) + + +def test_embedded_content_from_latest_run_if_next_missing(mocker, mocked_celery): + """ + Content files to embed should come from latest run if the next run is missing + """ + + mocker.patch("vector_search.tasks.load_course_blocklist", return_value=[]) + + course = CourseFactory.create(etl_source=ETLSource.ocw.value) + course.runs.all().delete() + latest_run = LearningResourceRunFactory.create( + learning_resource=course.learning_resource, + created_on=datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(hours=1), + ) + latest_run_contentfiles = [ + cf.id for cf in ContentFileFactory.create_batch(3, run=latest_run) + ] + generate_embeddings_mock = mocker.patch( + "vector_search.tasks.generate_embeddings", autospec=True + ) + + with pytest.raises(mocked_celery.replace_exception_class): + start_embed_resources.delay( + ["course"], skip_content_files=False, overwrite=True + ) + + generate_embeddings_mock.si.assert_called_with( + latest_run_contentfiles, + "content_file", + True, # noqa: FBT003 + ) diff --git a/vector_search/utils.py b/vector_search/utils.py index fd6e7e70be..23f0db0239 100644 --- a/vector_search/utils.py +++ b/vector_search/utils.py @@ -6,8 +6,11 @@ from langchain_experimental.text_splitter import SemanticChunker from qdrant_client import QdrantClient, models -from learning_resources.models import LearningResource -from learning_resources.serializers import LearningResourceSerializer +from learning_resources.models import ContentFile, LearningResource +from learning_resources.serializers import ( + ContentFileSerializer, + LearningResourceSerializer, +) from learning_resources_search.constants import CONTENT_FILE_TYPE from learning_resources_search.serializers import ( serialize_bulk_content_files, @@ -235,21 +238,8 @@ def _process_content_embeddings(serialized_content): "chunk_content": d.page_content, **{ key: d.metadata[key] - for key in [ - "run_title", - "platform", - "offered_by", - "run_readable_id", - "resource_readable_id", - "content_type", - "file_extension", - "content_feature_type", - "course_number", - "file_type", - "description", - "key", - "url", - ] + for key in QDRANT_CONTENT_FILE_PARAM_MAP + if key in d.metadata }, } for chunk_id, d in enumerate(split_docs) @@ -368,7 +358,31 @@ def _resource_vector_hits(search_result): def _content_file_vector_hits(search_result): - return [hit.payload for hit in search_result] + run_readable_ids = [hit.payload["run_readable_id"] for hit in search_result] + keys = [hit.payload["key"] for hit in search_result] + + serialized_content_files = ContentFileSerializer( + ContentFile.objects.for_serialization().filter( + run__run_id__in=run_readable_ids, key__in=keys + ), + many=True, + ).data + results = [] + contentfiles_dict = {} + [ + contentfiles_dict.update({(cf["run_readable_id"], cf["key"]): cf}) + for cf in serialized_content_files + ] + results = [] + for hit in search_result: + payload = hit.payload + serialized = contentfiles_dict.get((payload["run_readable_id"], payload["key"])) + if serialized: + if "content" in serialized: + serialized.pop("content") + payload.update(serialized) + results.append(payload) + return results def vector_search( diff --git a/yarn.lock b/yarn.lock index e69fb9f3bf..025ff8744f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1616,16 +1616,16 @@ __metadata: languageName: node linkType: hard -"@dnd-kit/sortable@npm:^8.0.0": - version: 8.0.0 - resolution: "@dnd-kit/sortable@npm:8.0.0" +"@dnd-kit/sortable@npm:^10.0.0": + version: 10.0.0 + resolution: "@dnd-kit/sortable@npm:10.0.0" dependencies: "@dnd-kit/utilities": "npm:^3.2.2" tslib: "npm:^2.0.0" peerDependencies: - "@dnd-kit/core": ^6.1.0 + "@dnd-kit/core": ^6.3.0 react: ">=16.8.0" - checksum: 10/e2e0d37ace13db2e6aceb65a803195ef29e1a33a37e7722a988d7a9c1aacce77472a93b2adcd8e6780ac98b3d5640c5481892f530177c2eb966df235726942ad + checksum: 10/bc61c25e76905204a53f91294b8116bf106fa27247eebca2c66478450b2051d7177115a384054e7e5639e6c4430083ade63056f79ee45f549da537cf05bc5288 languageName: node linkType: hard @@ -2739,16 +2739,16 @@ __metadata: languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^6.4.1": - version: 6.4.1 - resolution: "@mui/core-downloads-tracker@npm:6.4.1" - checksum: 10/8873b3a6af393a7de9fee290a42b0ca6d1a1b2168497079ecaccc813b0b78ef0552b906b760d0e532c0b30fff9ac82f6197341e82ee53c8a0f7b9ad2bba5f3a1 +"@mui/core-downloads-tracker@npm:^6.4.5": + version: 6.4.5 + resolution: "@mui/core-downloads-tracker@npm:6.4.5" + checksum: 10/45de88d2b63f6cfaaf5d141f6d96accb53a6455ea6d7e9f1b370b4811b9b728de2289d33f77d7d037b5c203197dced8cd366b0245049d3e887dc30c36a2251db languageName: node linkType: hard -"@mui/lab@npm:6.0.0-beta.26": - version: 6.0.0-beta.26 - resolution: "@mui/lab@npm:6.0.0-beta.26" +"@mui/lab@npm:6.0.0-beta.28": + version: 6.0.0-beta.28 + resolution: "@mui/lab@npm:6.0.0-beta.28" dependencies: "@babel/runtime": "npm:^7.26.0" "@mui/base": "npm:5.0.0-beta.69" @@ -2760,7 +2760,7 @@ __metadata: peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material": ^6.4.3 + "@mui/material": ^6.4.5 "@mui/material-pigment-css": ^6.4.3 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2774,13 +2774,13 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/1d3f2f51bbd92eaa9955dbec063aa90b25954bd4f120141cfeda08c0a4f4248fb2fb8737fd96b864cc604c7fe56e48336208ec72e2d581880ffd0aba0e133bc5 + checksum: 10/12f2eeb66571992003526f6e6aac25ac1cbc90ab1e12871129d5ba581f5e902715681f50f575e3297a50c7e547acca6d609a627d08211d4d4e5bd2be1ceccb3a languageName: node linkType: hard -"@mui/material-nextjs@npm:^6.3.1": - version: 6.3.1 - resolution: "@mui/material-nextjs@npm:6.3.1" +"@mui/material-nextjs@npm:^6.4.3": + version: 6.4.3 + resolution: "@mui/material-nextjs@npm:6.4.3" dependencies: "@babel/runtime": "npm:^7.26.0" peerDependencies: @@ -2797,19 +2797,19 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/ae0e0c74e25c1a7dc6e116fd48b779c46ef9740de8c1bbbf29b5c311fc86610cb32262a1dedd9068cee92ad1acc22e6a0324e374864f87ccb72a7c3b04dca121 + checksum: 10/78438121896a51afb9e3c82272a65f6e806589ed4755ded21228f118f2f85540676400d1b746e8df4d5d1954a2c9ae6f48700d7bc9104ca2e6e14a53d0d29620 languageName: node linkType: hard -"@mui/material@npm:^6.4.1": - version: 6.4.1 - resolution: "@mui/material@npm:6.4.1" +"@mui/material@npm:^6.4.5": + version: 6.4.5 + resolution: "@mui/material@npm:6.4.5" dependencies: "@babel/runtime": "npm:^7.26.0" - "@mui/core-downloads-tracker": "npm:^6.4.1" - "@mui/system": "npm:^6.4.1" + "@mui/core-downloads-tracker": "npm:^6.4.5" + "@mui/system": "npm:^6.4.3" "@mui/types": "npm:^7.2.21" - "@mui/utils": "npm:^6.4.1" + "@mui/utils": "npm:^6.4.3" "@popperjs/core": "npm:^2.11.8" "@types/react-transition-group": "npm:^4.4.12" clsx: "npm:^2.1.1" @@ -2820,7 +2820,7 @@ __metadata: peerDependencies: "@emotion/react": ^11.5.0 "@emotion/styled": ^11.3.0 - "@mui/material-pigment-css": ^6.4.1 + "@mui/material-pigment-css": ^6.4.3 "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2833,24 +2833,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/aa2c52764e58ce978ab89d9a000bbc06989ff25a2789ee4f6472e90b7424ff1cbd8e8e506be6fb189c5f730a7f4f462f1c1f9e7b95852fcca41f983d5fdc1150 - languageName: node - linkType: hard - -"@mui/private-theming@npm:^6.4.1": - version: 6.4.1 - resolution: "@mui/private-theming@npm:6.4.1" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@mui/utils": "npm:^6.4.1" - prop-types: "npm:^15.8.1" - peerDependencies: - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@types/react": - optional: true - checksum: 10/f23e47ecf87f3c2acbf405f4ba2efa495fa7a72e24961215945cbf1b3d292da55aae4db029faa6513b5282f25621a2c7898ff55fd3986426a29b95a5c2c04f60 + checksum: 10/e2ce159ebcc8d2295e70d904f148d2f281b8d4a557901b36d63dfe72a72b4cad6db244a805bc23fff9284b5a9d7c73fa82645bf02b5057a56eb67de3badb7a61 languageName: node linkType: hard @@ -2871,29 +2854,6 @@ __metadata: languageName: node linkType: hard -"@mui/styled-engine@npm:^6.4.0": - version: 6.4.0 - resolution: "@mui/styled-engine@npm:6.4.0" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@emotion/cache": "npm:^11.13.5" - "@emotion/serialize": "npm:^1.3.3" - "@emotion/sheet": "npm:^1.4.0" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.4.1 - "@emotion/styled": ^11.3.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - checksum: 10/dca3f784a53e8b4838f6d468eb6b503eaa55b001bd09de4e99237a4b227f1401d932b7fb306bad9d68fe77549ebe5faf368a1ae6be6f20ff6499101fc0dfb600 - languageName: node - linkType: hard - "@mui/styled-engine@npm:^6.4.3": version: 6.4.3 resolution: "@mui/styled-engine@npm:6.4.3" @@ -2917,34 +2877,6 @@ __metadata: languageName: node linkType: hard -"@mui/system@npm:^6.4.1": - version: 6.4.1 - resolution: "@mui/system@npm:6.4.1" - dependencies: - "@babel/runtime": "npm:^7.26.0" - "@mui/private-theming": "npm:^6.4.1" - "@mui/styled-engine": "npm:^6.4.0" - "@mui/types": "npm:^7.2.21" - "@mui/utils": "npm:^6.4.1" - clsx: "npm:^2.1.1" - csstype: "npm:^3.1.3" - prop-types: "npm:^15.8.1" - peerDependencies: - "@emotion/react": ^11.5.0 - "@emotion/styled": ^11.3.0 - "@types/react": ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - "@emotion/react": - optional: true - "@emotion/styled": - optional: true - "@types/react": - optional: true - checksum: 10/af0776024c24ba02606a1c55e6e1904684ec9a3c8dbb990d122e91b1793966c177b6453c076babd8f1d29eb1914206fb2b10eec4c76c3c61dc6d491687365374 - languageName: node - linkType: hard - "@mui/system@npm:^6.4.3": version: 6.4.3 resolution: "@mui/system@npm:6.4.3" @@ -3247,50 +3179,61 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.52.1": - version: 0.52.1 - resolution: "@opentelemetry/api-logs@npm:0.52.1" +"@opentelemetry/api-logs@npm:0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/api-logs@npm:0.56.0" dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10/7515667a41a38014ffda70674c0b77c9c68417cde9f8ce8840e675308b4431f99d879e8d347f1b08486561617f914c07ee704ad6ed8a6522dabc3a81ac39dc88 + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/5a6e25015acada7449d11124e9adbbe6670e1e9f7e8b46c60360ac89bb1537f2be326bcf18c66dcbcdee9f34e3a18bd4807c5a40faa0a4ac0135cb3675efb2a9 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/api-logs@npm:0.53.0" +"@opentelemetry/api-logs@npm:0.57.1": + version: 0.57.1 + resolution: "@opentelemetry/api-logs@npm:0.57.1" dependencies: - "@opentelemetry/api": "npm:^1.0.0" - checksum: 10/347b4554d6ee01afb29bd39e8f9cbbccd80abb0883fe6a84e3bcce8ab4dbfe357a2729246d2f66de0de6272846fd1bb2d71e286e18ad2690d9e7f46f02f00f73 + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10/4e06b34797f40245e8b51f52092cd74a44a5755a89bb80108428f7ef5490b8c812451fff3138d24d9b57e1f53a3b9815c40300dcf9852deacd64dad93990f736 languageName: node linkType: hard -"@opentelemetry/api-logs@npm:0.54.0": - version: 0.54.0 - resolution: "@opentelemetry/api-logs@npm:0.54.0" +"@opentelemetry/api-logs@npm:0.57.2": + version: 0.57.2 + resolution: "@opentelemetry/api-logs@npm:0.57.2" dependencies: "@opentelemetry/api": "npm:^1.3.0" - checksum: 10/891c592c93e1eb32d7dfb7588f04bee59671f60f3a685d9aab2a8ec927e237076af49f809056d537eb591c11e66b070a62730e986d9d87cf2f763732ef3d3ca4 + checksum: 10/8e3bac962e8f1fc93bfee6b433121bd2e07e8a8d1b86ef0d9d4a2c54d1759b64c74cf5da400f82f5ab5a4fe0da481726d8635fd1b15d123cf43090fa0adb8ea8 languageName: node linkType: hard -"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.0.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.8, @opentelemetry/api@npm:^1.9.0": +"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0, @opentelemetry/api@npm:^1.9.0": version: 1.9.0 resolution: "@opentelemetry/api@npm:1.9.0" checksum: 10/a607f0eef971893c4f2ee2a4c2069aade6ec3e84e2a1f5c2aac19f65c5d9eeea41aa72db917c1029faafdd71789a1a040bdc18f40d63690e22ccae5d7070f194 languageName: node linkType: hard -"@opentelemetry/context-async-hooks@npm:^1.25.1": - version: 1.26.0 - resolution: "@opentelemetry/context-async-hooks@npm:1.26.0" +"@opentelemetry/context-async-hooks@npm:^1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/context-async-hooks@npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10/95c3ec3683afb26e5d00a6efbdc459f76d1526a4f5bda07b265bb1f62a77770242695a48feb44b7b479490f89503e2283a2efdb833ed0cdf0256398feed9870f + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.30.1, @opentelemetry/core@npm:^1.26.0, @opentelemetry/core@npm:^1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/core@npm:1.30.1" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/c8824cc00385f21ecdf5b48ac474096687f9ce2e8d34612a62ee8bc7a6e25797c787239349a12bfeefbff200dcb7379ca45355a5684b9755dcf8fbd3b69cf523 + checksum: 10/fa3df9619fdbf8f607132d72915849754b71c4c5f5f705b30c8c59b209abe97206decf25cb8ebafdbb6105a4baab2acddee47468cb9d0b67f1a8df96cebc3548 languageName: node linkType: hard -"@opentelemetry/core@npm:1.26.0, @opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.25.1, @opentelemetry/core@npm:^1.8.0": +"@opentelemetry/core@npm:^1.1.0, @opentelemetry/core@npm:^1.8.0": version: 1.26.0 resolution: "@opentelemetry/core@npm:1.26.0" dependencies: @@ -3301,289 +3244,303 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/instrumentation-amqplib@npm:^0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-amqplib@npm:0.42.0" +"@opentelemetry/instrumentation-amqplib@npm:^0.46.0": + version: 0.46.1 + resolution: "@opentelemetry/instrumentation-amqplib@npm:0.46.1" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.1" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c97a5738792095faec20847e3bb1cb229269af2b445331ca922468b80bc2da65a3107dfe0e2e706ab7fb5c25fc260c5d5ffccda1c332cebae7d464155e6bf20d + checksum: 10/4f718937b865adec3aa7756484cf4192493f1e8946a448ec74711b08f44646eab112683fbd25ed2fce3e78aaacbe6b1a61d05fc08ad2a3303ae0873d8b74159a languageName: node linkType: hard -"@opentelemetry/instrumentation-connect@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-connect@npm:0.40.0" +"@opentelemetry/instrumentation-connect@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-connect@npm:0.43.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/connect": "npm:3.4.36" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/31d6adb3fbc04d4e831730562f57f8c54c6844e5214a31c70d5e855b7363202822d320cca603132ff9e4b4a597ecd4dcfb32ca2725cf5cd226fa239bf8fcd779 + checksum: 10/fd93463ff041a32e632b026307db035c26609dd232eb1ea97eaad45db4fc93fd09240e5421ceca249fb3e9c37797c0bf14171325b108cbc844117759e53fbf8a languageName: node linkType: hard -"@opentelemetry/instrumentation-dataloader@npm:0.12.0": - version: 0.12.0 - resolution: "@opentelemetry/instrumentation-dataloader@npm:0.12.0" +"@opentelemetry/instrumentation-dataloader@npm:0.16.0": + version: 0.16.0 + resolution: "@opentelemetry/instrumentation-dataloader@npm:0.16.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d560b519a6be6572a3bd3707f2035f4e1f8e50b95eee109ee138b9ebfadd1ec7bca288aeabb54e8299746eae9457001162dac6ccd92af5ba7449301e0bb139bd + checksum: 10/edf4f2f2b1602b3cd5bb92020e1989c6afae918e7e4e75c4a3cf3a4b33d25effdfd5ca67adaa2747494ca923bcf6b5d2ae3ff8ce19a18a2af8d48bcaf6b45fc7 languageName: node linkType: hard -"@opentelemetry/instrumentation-express@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-express@npm:0.44.0" +"@opentelemetry/instrumentation-express@npm:0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-express@npm:0.47.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/a2ae344c1c2b8346f6957dfadbe4c789a0abf08a5dbcd424c41b320faa5b72d9a399406041792ab6a18093b428958b067d3c66dcd492d9cc5d97a17347d3f88a + checksum: 10/a8bffa443d869065dc7e013f02aaff0a6593db9ebca36748d940968fabcc9d61e71e4235489d867abb62c71e1f2df5ec6af6f3bdf21e750552be86b29850bd9e languageName: node linkType: hard -"@opentelemetry/instrumentation-fastify@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-fastify@npm:0.40.0" +"@opentelemetry/instrumentation-fastify@npm:0.44.1": + version: 0.44.1 + resolution: "@opentelemetry/instrumentation-fastify@npm:0.44.1" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/38ec436b802464ec94e730a117e5472d62114b15987040fd39567257258a4e6f028f0a2e9a3625302a48ea794914378d46df7d41dfc8125222c5d1a01daf26fd + checksum: 10/845d7b68755d0addf329e2ea4d40663d576676b2400d936759eb09e3d41e01df6c1673e51ac7aecdda950f7b8be8d12e2cd8811eb8b2a45ebc7dbec96d287eb7 languageName: node linkType: hard -"@opentelemetry/instrumentation-fs@npm:0.16.0": - version: 0.16.0 - resolution: "@opentelemetry/instrumentation-fs@npm:0.16.0" +"@opentelemetry/instrumentation-fs@npm:0.19.0": + version: 0.19.0 + resolution: "@opentelemetry/instrumentation-fs@npm:0.19.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/01ac3a8c488a85cbd63e8cdb62e4ab228af569c05d731c4615ff90a4fe699e2e619b626d6838f03e7aaeb715a695d6e45a5ba4c5a976e748c04276719924efb9 + checksum: 10/a24312c092aaec0f4f7fcae445dde17f3e8732fcc3a2583a83412ee22d284fe99752828e7afd6883cab34481008915497088f192ee91a6d6b1b43755dbcd6f0e languageName: node linkType: hard -"@opentelemetry/instrumentation-generic-pool@npm:0.39.0": - version: 0.39.0 - resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.39.0" +"@opentelemetry/instrumentation-generic-pool@npm:0.43.0": + version: 0.43.0 + resolution: "@opentelemetry/instrumentation-generic-pool@npm:0.43.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/37b476cdddaf3fa2f83a340dcd6949e70cbead45cf747a953099fdb422cb0e89fd52017d0ca01e74283e5af4caa788eb4d163f81e4f21e6ba8e89d0a0dbc99c5 + checksum: 10/2ea9570a87df53b00c866fab9074efde1d4a1ad1d8f271c7ab341dc2d40c73b60b67f3021f33f07e94e8cc0cc1b911b710d1cb03829fe29b5130fbbdd7b15a03 languageName: node linkType: hard -"@opentelemetry/instrumentation-graphql@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-graphql@npm:0.43.0" +"@opentelemetry/instrumentation-graphql@npm:0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-graphql@npm:0.47.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/2d1e5a46b4174c8d9acfa9ed93cf06f1aafcc74048f3553219deb42a9c8aa5d87b1e67b0e44c7be6e7954005e63233958bf9af306702c8709f5ab6e2f0c7bbb0 + checksum: 10/1699c89735dd9a1f25df236ba66052aca4a93e4d894657b8495249f0a7ad67691e05ac2db5e3110c85b5c15a22c19325ecee9c70c0eacacf4ec93e8f8370a654 languageName: node linkType: hard -"@opentelemetry/instrumentation-hapi@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-hapi@npm:0.41.0" +"@opentelemetry/instrumentation-hapi@npm:0.45.1": + version: 0.45.1 + resolution: "@opentelemetry/instrumentation-hapi@npm:0.45.1" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/5025db3e785476757947915e9512d454f565eabc883757d7a122e134f3cb2e5d418142f916e5ab4b2db2bfb9c59ab105f602c19af268442ae07106b5b547fa64 + checksum: 10/606f4817cae57a658dc77c9fa7c235aaadef5aaf5addd137dc9c9c1fddfedc93916e80ed5d6413d36b160d2b4223974369f18090d07501bcf72a7b07f9e0b24f languageName: node linkType: hard -"@opentelemetry/instrumentation-http@npm:0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation-http@npm:0.53.0" +"@opentelemetry/instrumentation-http@npm:0.57.1": + version: 0.57.1 + resolution: "@opentelemetry/instrumentation-http@npm:0.57.1" dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/instrumentation": "npm:0.53.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/instrumentation": "npm:0.57.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + forwarded-parse: "npm:2.1.2" semver: "npm:^7.5.2" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/c00e71f7a5a03723bf13e55e74dcc8e44d61b87fc38c50821fa6bf86a09d3eca68a62a4ccc6f35e70a6529c36d134eca77889852869d7a5a9b2af73f3fb5f097 + checksum: 10/31371f56209362486cb4c8c8e1b31111d6846db89dae4442aaa8ffa47cfb3c7f7ef4c7d19635130a25c391499d7ee17a0c35f140b7641cc4a3749692e70aeb81 languageName: node linkType: hard -"@opentelemetry/instrumentation-ioredis@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-ioredis@npm:0.43.0" +"@opentelemetry/instrumentation-ioredis@npm:0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-ioredis@npm:0.47.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/redis-common": "npm:^0.36.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/fa405f521134a375c3ae1894d39da2a62bd021695fbc6a28d7efe61202d9a3b895047cf59353d6773e5d8528aea24a63841110ba48800132f5aac47615603c10 + checksum: 10/3a885546c950db88ac71c2506544d3e977c561fbfdbe53b4e9d071a017968d5b6ef347dbd64954ae2d315fdd0209429832156438d9eb904bb6c576ed2ff79af1 languageName: node linkType: hard -"@opentelemetry/instrumentation-kafkajs@npm:0.4.0": - version: 0.4.0 - resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.4.0" +"@opentelemetry/instrumentation-kafkajs@npm:0.7.0": + version: 0.7.0 + resolution: "@opentelemetry/instrumentation-kafkajs@npm:0.7.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.54.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/e5abcbbf2a458c3754d8a5790cf364384c84f51929ec66973ae1390020ef945a4be3d42db214a6738362a9d319e03ad6df0abc9470b2107568728d1e42f7ea94 + checksum: 10/a92f1ffb75e86f4f9db0e7f866c3993af9c5c1af850afcd49a928266df3394ca6fb073c92f2de670c6441b07ad113d9f3a4261bd965c80a6de701beff0f54a56 languageName: node linkType: hard -"@opentelemetry/instrumentation-koa@npm:0.43.0": - version: 0.43.0 - resolution: "@opentelemetry/instrumentation-koa@npm:0.43.0" +"@opentelemetry/instrumentation-knex@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-knex@npm:0.44.0" + dependencies: + "@opentelemetry/instrumentation": "npm:^0.57.0" + "@opentelemetry/semantic-conventions": "npm:^1.27.0" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10/d4e8197b83f55744ee35029105e53cd2e00b6afc79528c949429a786d69c3febe847d607ecda73503b1bfdff48b468dc5390c1935253335469b9b3873cd1d58b + languageName: node + linkType: hard + +"@opentelemetry/instrumentation-koa@npm:0.47.0": + version: 0.47.0 + resolution: "@opentelemetry/instrumentation-koa@npm:0.47.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/b494196962c0840651e5fdec7350a8d9f443ee9e682e4c20c8b47ed82c6c34875adc7fd467ac04c3838edbf14bf79aafddb889f2755fc1957f27275a08442e83 + checksum: 10/abdb5a4e27200ba776faef44f028c2629f5342480eb35a95a179552e587bfef0e1d82490174f51580ddf2bd5a550b73c24aa46e9a0aea3d53653e30bf32aeece languageName: node linkType: hard -"@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.40.0" +"@opentelemetry/instrumentation-lru-memoizer@npm:0.44.0": + version: 0.44.0 + resolution: "@opentelemetry/instrumentation-lru-memoizer@npm:0.44.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/07bb795faedb0c01bf7dd2cc660431b2303fd1f3a904b3fcc06eb601fde94653f8391a40ccf101a391893187a68381ab6ea8a284118fff328d32b130fac2ea6c + checksum: 10/c46b48af519232ab52b6ad38e78cb8e665005167d8e2fe73c44c388b4770cdbbbda9646b9db71ab14c3516951d4ae87f106e678a8b2ba236d602f0bdb5bb9115 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongodb@npm:0.47.0": - version: 0.47.0 - resolution: "@opentelemetry/instrumentation-mongodb@npm:0.47.0" +"@opentelemetry/instrumentation-mongodb@npm:0.51.0": + version: 0.51.0 + resolution: "@opentelemetry/instrumentation-mongodb@npm:0.51.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/sdk-metrics": "npm:^1.9.1" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/570379bf6873dac9535d7b710e0c3d7228e132b7e290dfa0d244e22d4b11652500938685412c1d1ba9b34c958eaf96509af009adb07e258d5ea9347112765c72 + checksum: 10/c0330a18728c5f0ee8b6756b01b75e0bb66ff225b45e4556702cff2fdf199584d6435f8b66a1e10c0200678d64182be3ef7d1d2d55cd5db9b0618b247420dc02 languageName: node linkType: hard -"@opentelemetry/instrumentation-mongoose@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-mongoose@npm:0.42.0" +"@opentelemetry/instrumentation-mongoose@npm:0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-mongoose@npm:0.46.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/58c3ba89ce43830451dcc105a2ebf352b296cf6b1b8f6194ac69c1fa39c18e50ee0092f8e514a27046cf35e0ade391425f7adf0e6e6b1fd8dbbec2b01f393be2 + checksum: 10/349848f3f2213f2818186774ade0b7933659fac3346adbb8bd731ff606117e261fbcca479eb7077bac10ae0204bff0e79d06ffed6752a6cd220be1282fea10d3 languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql2@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql2@npm:0.41.0" +"@opentelemetry/instrumentation-mysql2@npm:0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-mysql2@npm:0.45.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@opentelemetry/sql-common": "npm:^0.40.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/40f48b3f87bda347db2332020f0880223f49a894e0312d03e1f86aa48b8335b6db65955ea775b8bec2a687672bdbd9c0997294acdd4cf51765da0e22e1d98a35 + checksum: 10/30f1a9d9fb8d926a2330aa05ac0ca689564b557b0caa7ee404b69a9a4930e8c1444fe4115bb1419ca061ae5dce79900c8fbcd82902fe252edaf81f252945f0aa languageName: node linkType: hard -"@opentelemetry/instrumentation-mysql@npm:0.41.0": - version: 0.41.0 - resolution: "@opentelemetry/instrumentation-mysql@npm:0.41.0" +"@opentelemetry/instrumentation-mysql@npm:0.45.0": + version: 0.45.0 + resolution: "@opentelemetry/instrumentation-mysql@npm:0.45.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" "@types/mysql": "npm:2.15.26" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/20ff56edc0b74cf8be2dd5960e210a6c20568169af5768fd78bb33f5a626e271fe2ac6cf7ad0e9629ff932a18feac04db99fffa3c867b27c679523dd2f4570d3 + checksum: 10/b5cf28df774b5718a7845741c8facc3a532f63fcc1ef193a0706ee09e28aeea73a010a0095450553b84194e067c3932e135c7c2475fa98c336a80b06b93283c7 languageName: node linkType: hard -"@opentelemetry/instrumentation-nestjs-core@npm:0.40.0": - version: 0.40.0 - resolution: "@opentelemetry/instrumentation-nestjs-core@npm:0.40.0" +"@opentelemetry/instrumentation-pg@npm:0.51.0": + version: 0.51.0 + resolution: "@opentelemetry/instrumentation-pg@npm:0.51.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/core": "npm:^1.26.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@opentelemetry/sql-common": "npm:^0.40.1" + "@types/pg": "npm:8.6.1" + "@types/pg-pool": "npm:2.0.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/421f3e18c651b74383d5cd6a231431ecda3e49262f934dca27bf2272fe58334cbe2acf2f62ce5d82c0893d6f899e2921dfc6a6f78ab27f84a35bd8bfb77df9e4 + checksum: 10/2d6869a78763227c352776f74580db6a81d7df3bf1014be2a0864582843528847631234d2c0289d86ad757642383cc0728a368304738251332e1aef166d72701 languageName: node linkType: hard -"@opentelemetry/instrumentation-pg@npm:0.44.0": - version: 0.44.0 - resolution: "@opentelemetry/instrumentation-pg@npm:0.44.0" +"@opentelemetry/instrumentation-redis-4@npm:0.46.0": + version: 0.46.0 + resolution: "@opentelemetry/instrumentation-redis-4@npm:0.46.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" + "@opentelemetry/redis-common": "npm:^0.36.2" "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@opentelemetry/sql-common": "npm:^0.40.1" - "@types/pg": "npm:8.6.1" - "@types/pg-pool": "npm:2.0.6" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d902682a3630ff1ef392624165b46a2b4fe0fd696f42a588030f2c4ba73ccd2631792cf6b122bad0dfddb929044b96c285f63517704e7ccaf699a77150f5f3d9 + checksum: 10/e5853a906e268e3ad09cb1a18ac8e8d52ffe0cadf7bec81b2ed61eae99a6d8538798e3321091460b6cebff081c9329e04ca0d3c26d7d993f4939faf55b741775 languageName: node linkType: hard -"@opentelemetry/instrumentation-redis-4@npm:0.42.0": - version: 0.42.0 - resolution: "@opentelemetry/instrumentation-redis-4@npm:0.42.0" +"@opentelemetry/instrumentation-tedious@npm:0.18.0": + version: 0.18.0 + resolution: "@opentelemetry/instrumentation-tedious@npm:0.18.0" dependencies: - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/redis-common": "npm:^0.36.2" + "@opentelemetry/instrumentation": "npm:^0.57.0" "@opentelemetry/semantic-conventions": "npm:^1.27.0" + "@types/tedious": "npm:^4.0.14" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/d5ff240b826525cdc9935ab2885f65ea5c5d77ad31e9ee8142e6840b1c1603db025370b67fb828580a242fe7ff815d1335ff3845c48d8b94070f3683f71b0898 + checksum: 10/ad39241c25cce81461967590cb389a891cacfed83ec008a35ffac4322a99d8c6db07a5daea50c1db4015faeba69becc92769c9cf60f6500d8d1754c9f8ff021f languageName: node linkType: hard -"@opentelemetry/instrumentation-undici@npm:0.6.0": - version: 0.6.0 - resolution: "@opentelemetry/instrumentation-undici@npm:0.6.0" +"@opentelemetry/instrumentation-undici@npm:0.10.0": + version: 0.10.0 + resolution: "@opentelemetry/instrumentation-undici@npm:0.10.0" dependencies: "@opentelemetry/core": "npm:^1.8.0" - "@opentelemetry/instrumentation": "npm:^0.53.0" + "@opentelemetry/instrumentation": "npm:^0.57.0" peerDependencies: "@opentelemetry/api": ^1.7.0 - checksum: 10/97291ecca9ff936dc4a418b380542f4dbb1f891692df44292dd61dc9e39aa1c347b70666cda5c30fbd78969d3b6ea602a6bafb30566b65eec0e00bcac459b2c4 + checksum: 10/eb96ed916eb95504641a0ec3425aa4de91bdea5659b3cc8333e6bc2ffd0e4198999fdb2454969b5d37d30c04183b4da64c3659b2b8abe6370371174a89a0a8ad languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:0.53.0, @opentelemetry/instrumentation@npm:^0.53.0": - version: 0.53.0 - resolution: "@opentelemetry/instrumentation@npm:0.53.0" +"@opentelemetry/instrumentation@npm:0.57.1": + version: 0.57.1 + resolution: "@opentelemetry/instrumentation@npm:0.57.1" dependencies: - "@opentelemetry/api-logs": "npm:0.53.0" + "@opentelemetry/api-logs": "npm:0.57.1" "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" @@ -3591,31 +3548,31 @@ __metadata: shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/4b994c8568a503a15655cba249b1dbdef3f67dfda37938abba6267ba75b6d72a9aa276be4b0c8874e86f98ab89d92877e1874e0565a7e67f062c43dfcbbb16a5 + checksum: 10/8f21a1b69aab5b48f8d85da2dd944d12f498757b890d4da062f7736a2254b19fb2c678db1807889e0526d3bbb653455c24c0d89523662d358fdb4e615f099fcf languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0": - version: 0.52.1 - resolution: "@opentelemetry/instrumentation@npm:0.52.1" +"@opentelemetry/instrumentation@npm:^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0": + version: 0.56.0 + resolution: "@opentelemetry/instrumentation@npm:0.56.0" dependencies: - "@opentelemetry/api-logs": "npm:0.52.1" - "@types/shimmer": "npm:^1.0.2" + "@opentelemetry/api-logs": "npm:0.56.0" + "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" semver: "npm:^7.5.2" shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/87761bd593f2b905d88d0531a3a2a7f4b0186334ae413b4c172a86bd4de0fd6d2f906a1bfd9dd7bd172a228a44fa7a680f5802a1570dfe2fadad0768e80bd7a8 + checksum: 10/7c3802eb6b55b39b6904526d052b918619c9cde0f71a35bc2f23ac6c3a10ea66b08a65adf6862cdbaac7b231fb5119204b4d5531be25b96933a9d8b91a9ce062 languageName: node linkType: hard -"@opentelemetry/instrumentation@npm:^0.54.0": - version: 0.54.0 - resolution: "@opentelemetry/instrumentation@npm:0.54.0" +"@opentelemetry/instrumentation@npm:^0.57.0, @opentelemetry/instrumentation@npm:^0.57.1": + version: 0.57.2 + resolution: "@opentelemetry/instrumentation@npm:0.57.2" dependencies: - "@opentelemetry/api-logs": "npm:0.54.0" + "@opentelemetry/api-logs": "npm:0.57.2" "@types/shimmer": "npm:^1.2.0" import-in-the-middle: "npm:^1.8.1" require-in-the-middle: "npm:^7.1.1" @@ -3623,7 +3580,7 @@ __metadata: shimmer: "npm:^1.2.1" peerDependencies: "@opentelemetry/api": ^1.3.0 - checksum: 10/bd42bb41a26423d3948156dfc9b51297bd365a70081fadf73cccd4b5fc9741a63bfb4e1f7ceb04fc632aa0a8a957a27cc7c3be5f1d4aaf2cea3dbd249aa86e40 + checksum: 10/b66b840e87976a5edf551a7011a395df8df5985571ac0506412943d07b4309fcc78fe71d3f55217a00f44384fbf61f59f1e54d544ab12f5490f6a7a56b71e02a languageName: node linkType: hard @@ -3634,40 +3591,28 @@ __metadata: languageName: node linkType: hard -"@opentelemetry/resources@npm:1.26.0, @opentelemetry/resources@npm:^1.26.0": - version: 1.26.0 - resolution: "@opentelemetry/resources@npm:1.26.0" +"@opentelemetry/resources@npm:1.30.1, @opentelemetry/resources@npm:^1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/resources@npm:1.30.1" dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/ce60dbf2bd424b01824b72f533724eaf64418e01c43bef952b87dbff6d2a0f28cdcbea0d3d95c5e324f609e58721bf52ea91b5518b0e30d6bb03fb95af85cc33 - languageName: node - linkType: hard - -"@opentelemetry/sdk-metrics@npm:^1.9.1": - version: 1.26.0 - resolution: "@opentelemetry/sdk-metrics@npm:1.26.0" - dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/resources": "npm:1.26.0" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/e48e4dd1fed1e501750460e1320f89507c19287c5059cfaccc8268ad8cc3e1de40feeee6584b23626e01f9cde0f10301d08edf6a65bbd1346ef94f70ae8844f5 + checksum: 10/9b7544b639e8fee41315e2646615676ffb1020dba0f6c81e6ec1dd2daf5409fc6ce3d2b629bbd9cd32f85decc3a8bfa5dc8cc52bb72bd84c1777ca25b4301aa0 languageName: node linkType: hard -"@opentelemetry/sdk-trace-base@npm:^1.22, @opentelemetry/sdk-trace-base@npm:^1.26.0": - version: 1.26.0 - resolution: "@opentelemetry/sdk-trace-base@npm:1.26.0" +"@opentelemetry/sdk-trace-base@npm:^1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-trace-base@npm:1.30.1" dependencies: - "@opentelemetry/core": "npm:1.26.0" - "@opentelemetry/resources": "npm:1.26.0" - "@opentelemetry/semantic-conventions": "npm:1.27.0" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" peerDependencies: "@opentelemetry/api": ">=1.0.0 <1.10.0" - checksum: 10/e4a3d296ad908b9f58d7aefdcc1f7383fb0eb64fc85b0b5d18c4a7d829ce3d0efa5e53f5fe1a23185d9b5d97b782431384efe01aba8ba788922260a9dbbdb662 + checksum: 10/3ba794622c9ff1d147b77fcd0c8547a6a1356edb5af884cf1d09838c71a004a044ea55d4c742b956e9247e46053583bdbda533836686b2f54ee1ecfc527254ff languageName: node linkType: hard @@ -3678,6 +3623,20 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/semantic-conventions@npm:1.28.0": + version: 1.28.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" + checksum: 10/c182a3206769b5d5a8ab89a5c674d046fd789421cef27ea55af179990e314732433c98e5017aa23e99f15fd2b0e13cb129bb6c2282da6860ce9419adf32b2e87 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:^1.28.0": + version: 1.30.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.30.0" + checksum: 10/78df5976f5bcfd00acaea3e609cf06fdd34517ae8db994ae216aaac16c51af97ac22c534bfcbac5218e0086db83ec5ef6cc045b95626cc6ea807686bea549a41 + languageName: node + linkType: hard + "@opentelemetry/sql-common@npm:^0.40.1": version: 0.40.1 resolution: "@opentelemetry/sql-common@npm:0.40.1" @@ -3754,14 +3713,14 @@ __metadata: languageName: node linkType: hard -"@prisma/instrumentation@npm:5.19.1": - version: 5.19.1 - resolution: "@prisma/instrumentation@npm:5.19.1" +"@prisma/instrumentation@npm:6.2.1": + version: 6.2.1 + resolution: "@prisma/instrumentation@npm:6.2.1" dependencies: - "@opentelemetry/api": "npm:^1.8" - "@opentelemetry/instrumentation": "npm:^0.49 || ^0.50 || ^0.51 || ^0.52.0" - "@opentelemetry/sdk-trace-base": "npm:^1.22" - checksum: 10/62029ace33406901d1dfee136d4ae83b51d5787fbcdb104378edc890310e1989a0b0c95c1eb28fe8bfc314565aebee48189aebee600486859383d8981993045b + "@opentelemetry/instrumentation": "npm:^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0" + peerDependencies: + "@opentelemetry/api": ^1.8 + checksum: 10/d97fc1384d6167722a85065be73f20c6ab9165a92026026c627800b4947fc5b5d655e6f9e0a4727ebd6b01aee19babf273800df7eba3995903c0d6ea566e4208 languageName: node linkType: hard @@ -3774,22 +3733,23 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-commonjs@npm:26.0.1": - version: 26.0.1 - resolution: "@rollup/plugin-commonjs@npm:26.0.1" +"@rollup/plugin-commonjs@npm:28.0.1": + version: 28.0.1 + resolution: "@rollup/plugin-commonjs@npm:28.0.1" dependencies: "@rollup/pluginutils": "npm:^5.0.1" commondir: "npm:^1.0.1" estree-walker: "npm:^2.0.2" - glob: "npm:^10.4.1" + fdir: "npm:^6.2.0" is-reference: "npm:1.2.1" magic-string: "npm:^0.30.3" + picomatch: "npm:^4.0.2" peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 peerDependenciesMeta: rollup: optional: true - checksum: 10/d9846fbf9c279259b5bf508da6264e18b2572e8bbd6df2c4fd96f1ae40153b231b7864426e62bff6f2f53b5a73b6db2246cacc31d4eecdaf469cc16d683c2392 + checksum: 10/e01d26ce411cec587eeac805aaa181f042a30bac1cf7f714b65028ed2abab7907d67de835e3fe99fd38f26eee17a60373d5c37518b29829de79b7c1b24a29e0d languageName: node linkType: hard @@ -3823,150 +3783,140 @@ __metadata: languageName: node linkType: hard -"@sentry-internal/browser-utils@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry-internal/browser-utils@npm:8.36.0" +"@sentry-internal/browser-utils@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry-internal/browser-utils@npm:9.1.0" dependencies: - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/f886260292e22dd936fbd7d2f4bb1325e3197a418a88b352f064517286677b68e26a993a9cac908faac344f4ea460074578b73fff91746a9f65e18b7c44e0dc6 + "@sentry/core": "npm:9.1.0" + checksum: 10/d69ce2f8bd6fad76435b8d0f5747b523883ead1faaf8717f03f8392ab42cc040195cbd35ba9ea70556e04ec361ebea302aa23a989683de5e03cf070ff19e91d8 languageName: node linkType: hard -"@sentry-internal/feedback@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry-internal/feedback@npm:8.36.0" +"@sentry-internal/feedback@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry-internal/feedback@npm:9.1.0" dependencies: - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/72cb38adae9939ce90963044cfc39051de0f2c8e037efb6c416299afd7ce66d63374f63c549ed054c245bb93ff337bb2a3eaba532dbad2a712b5a9910af3e4a5 + "@sentry/core": "npm:9.1.0" + checksum: 10/0d068eb1987618ba84cc2007d7a1ea4a661e3f5fcb24c23293e61b1f6d0c4d69deaa9f3afe9d2c895c76652540c7599d2a4ad197ee0627fd328a2a0cbf1fa9a4 languageName: node linkType: hard -"@sentry-internal/replay-canvas@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry-internal/replay-canvas@npm:8.36.0" +"@sentry-internal/replay-canvas@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry-internal/replay-canvas@npm:9.1.0" dependencies: - "@sentry-internal/replay": "npm:8.36.0" - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/f7725523339dfadadd55a2c025de82d73b2035b65b1bf34395997204bbd69e4665fcf94e90fb53b279c2bb1c005729f0ef9fb4d9946a20fa542b6cd9d2d2e9ea + "@sentry-internal/replay": "npm:9.1.0" + "@sentry/core": "npm:9.1.0" + checksum: 10/7601200b8f6d3dc08f6038804b4fef7632cf7fb03ee2e0243db93a62232d24686243ea17b4c41ce34cb8ffb7015066873c5f71d4abef23b040cc7a366833bcd9 languageName: node linkType: hard -"@sentry-internal/replay@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry-internal/replay@npm:8.36.0" +"@sentry-internal/replay@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry-internal/replay@npm:9.1.0" dependencies: - "@sentry-internal/browser-utils": "npm:8.36.0" - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/558f9f277aef5232c43ce0711d85aa8ca8ed584435c9bbe7e08e59c3af2ed1da137d104b47e6f5c32b69b5b78ed516e627ec096ee4a950f2492c43fcf4a78170 + "@sentry-internal/browser-utils": "npm:9.1.0" + "@sentry/core": "npm:9.1.0" + checksum: 10/c6a59eb184e5101ea1e442be0d24d544bf92a1cf7dce806d150af632756a97d3e27641f401b0703956f6b94704ca704a2028dbfb072565e39430352abf0a398f languageName: node linkType: hard -"@sentry/babel-plugin-component-annotate@npm:2.22.6": - version: 2.22.6 - resolution: "@sentry/babel-plugin-component-annotate@npm:2.22.6" - checksum: 10/895c9e03a576721805494f1292ed1282027cdd81e0963b621be94c613451f628767ea28e5b5a2bc803df86de6b95a5835209ac30d81486916b4da87cc9853ac7 +"@sentry/babel-plugin-component-annotate@npm:3.1.2": + version: 3.1.2 + resolution: "@sentry/babel-plugin-component-annotate@npm:3.1.2" + checksum: 10/753de4d5552389767dfd3b6f026d636ad2665118e368d21a28e38d5c3534abbe2cd015449e13830c5a0d0301b377158176767936a99ee2263409e06514fa1901 languageName: node linkType: hard -"@sentry/browser@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/browser@npm:8.36.0" +"@sentry/browser@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/browser@npm:9.1.0" dependencies: - "@sentry-internal/browser-utils": "npm:8.36.0" - "@sentry-internal/feedback": "npm:8.36.0" - "@sentry-internal/replay": "npm:8.36.0" - "@sentry-internal/replay-canvas": "npm:8.36.0" - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/46af4ade98628d93dd184b42deb5bec267407ebdd4a42c5803ca97cbb25f96c03db06c1c724438238fe0ae89d84ea64b686cae63b156d5aefe7e6c07b7c6ebaa + "@sentry-internal/browser-utils": "npm:9.1.0" + "@sentry-internal/feedback": "npm:9.1.0" + "@sentry-internal/replay": "npm:9.1.0" + "@sentry-internal/replay-canvas": "npm:9.1.0" + "@sentry/core": "npm:9.1.0" + checksum: 10/194b8a13e9ea61cf0f1f20f9a9fd9fd380e7b2e1040cc6201668f7c1d162933b92d75e5360937167d3d1945b152cd50f74618ba4ea617dc5cca1ea150729166d languageName: node linkType: hard -"@sentry/bundler-plugin-core@npm:2.22.6": - version: 2.22.6 - resolution: "@sentry/bundler-plugin-core@npm:2.22.6" +"@sentry/bundler-plugin-core@npm:3.1.2": + version: 3.1.2 + resolution: "@sentry/bundler-plugin-core@npm:3.1.2" dependencies: "@babel/core": "npm:^7.18.5" - "@sentry/babel-plugin-component-annotate": "npm:2.22.6" - "@sentry/cli": "npm:^2.36.1" + "@sentry/babel-plugin-component-annotate": "npm:3.1.2" + "@sentry/cli": "npm:2.41.1" dotenv: "npm:^16.3.1" find-up: "npm:^5.0.0" glob: "npm:^9.3.2" magic-string: "npm:0.30.8" unplugin: "npm:1.0.1" - checksum: 10/a5fbc2814d621d7b3885c20bf3ea99698a1ee3889af5ba34876864bc87ff36dbc39f0d5a0894433e18899b0c444cc177ffd4228b136cdd705445b582b68d5396 + checksum: 10/9b1d70b0770833d5570b60461c0364a16c84c5594fe65986cbd49a4bd8aaf3b47b8cd584c36bdd4d3c125566b59b094b2e17db3b775fd60dd11e9669c95a1ee4 languageName: node linkType: hard -"@sentry/cli-darwin@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-darwin@npm:2.38.1" +"@sentry/cli-darwin@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-darwin@npm:2.41.1" conditions: os=darwin languageName: node linkType: hard -"@sentry/cli-linux-arm64@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-linux-arm64@npm:2.38.1" +"@sentry/cli-linux-arm64@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-linux-arm64@npm:2.41.1" conditions: (os=linux | os=freebsd) & cpu=arm64 languageName: node linkType: hard -"@sentry/cli-linux-arm@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-linux-arm@npm:2.38.1" +"@sentry/cli-linux-arm@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-linux-arm@npm:2.41.1" conditions: (os=linux | os=freebsd) & cpu=arm languageName: node linkType: hard -"@sentry/cli-linux-i686@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-linux-i686@npm:2.38.1" +"@sentry/cli-linux-i686@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-linux-i686@npm:2.41.1" conditions: (os=linux | os=freebsd) & (cpu=x86 | cpu=ia32) languageName: node linkType: hard -"@sentry/cli-linux-x64@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-linux-x64@npm:2.38.1" +"@sentry/cli-linux-x64@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-linux-x64@npm:2.41.1" conditions: (os=linux | os=freebsd) & cpu=x64 languageName: node linkType: hard -"@sentry/cli-win32-i686@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-win32-i686@npm:2.38.1" +"@sentry/cli-win32-i686@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-win32-i686@npm:2.41.1" conditions: os=win32 & (cpu=x86 | cpu=ia32) languageName: node linkType: hard -"@sentry/cli-win32-x64@npm:2.38.1": - version: 2.38.1 - resolution: "@sentry/cli-win32-x64@npm:2.38.1" +"@sentry/cli-win32-x64@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli-win32-x64@npm:2.41.1" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@sentry/cli@npm:^2.36.1": - version: 2.38.1 - resolution: "@sentry/cli@npm:2.38.1" +"@sentry/cli@npm:2.41.1": + version: 2.41.1 + resolution: "@sentry/cli@npm:2.41.1" dependencies: - "@sentry/cli-darwin": "npm:2.38.1" - "@sentry/cli-linux-arm": "npm:2.38.1" - "@sentry/cli-linux-arm64": "npm:2.38.1" - "@sentry/cli-linux-i686": "npm:2.38.1" - "@sentry/cli-linux-x64": "npm:2.38.1" - "@sentry/cli-win32-i686": "npm:2.38.1" - "@sentry/cli-win32-x64": "npm:2.38.1" + "@sentry/cli-darwin": "npm:2.41.1" + "@sentry/cli-linux-arm": "npm:2.41.1" + "@sentry/cli-linux-arm64": "npm:2.41.1" + "@sentry/cli-linux-i686": "npm:2.41.1" + "@sentry/cli-linux-x64": "npm:2.41.1" + "@sentry/cli-win32-i686": "npm:2.41.1" + "@sentry/cli-win32-x64": "npm:2.41.1" https-proxy-agent: "npm:^5.0.0" node-fetch: "npm:^2.6.7" progress: "npm:^2.0.3" @@ -3989,160 +3939,132 @@ __metadata: optional: true bin: sentry-cli: bin/sentry-cli - checksum: 10/79cde14f65be6ce14b3d1e788190695c72f0f286983216527330750de89d4ffce643b8b6dcb4d661a710b95be597255be84204c0f8b2a9d327656b7d4ef010a5 + checksum: 10/acfec8a360293f06bd0e5f2f3d066b16dda517df7feffd161351bc1797f6e68f0ffe89849ed59f30d2df18654f5b0d9950031ec5c2cde2b4dcc72d0dd618e19f languageName: node linkType: hard -"@sentry/core@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/core@npm:8.36.0" - dependencies: - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/26d9a926c6a76526cc3ed895370604c88a7fb9f152866362cb0eef348fcada9ae78706a678f081ed26b0bb29fd0293627d458dbc598b630962550ce4924c584d +"@sentry/core@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/core@npm:9.1.0" + checksum: 10/ff1761202fb98facf567514fee5c9fcc1dce1937b00acc9484e27d30effc59a6b0bf4d2dccdd9749eec75b6d8a94f38b3012cb1a673d9ae25ba1d3e29e706c93 languageName: node linkType: hard -"@sentry/nextjs@npm:^8.36.0": - version: 8.36.0 - resolution: "@sentry/nextjs@npm:8.36.0" +"@sentry/nextjs@npm:^9.0.0": + version: 9.1.0 + resolution: "@sentry/nextjs@npm:9.1.0" dependencies: "@opentelemetry/api": "npm:^1.9.0" - "@opentelemetry/instrumentation-http": "npm:0.53.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@rollup/plugin-commonjs": "npm:26.0.1" - "@sentry-internal/browser-utils": "npm:8.36.0" - "@sentry/core": "npm:8.36.0" - "@sentry/node": "npm:8.36.0" - "@sentry/opentelemetry": "npm:8.36.0" - "@sentry/react": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - "@sentry/vercel-edge": "npm:8.36.0" - "@sentry/webpack-plugin": "npm:2.22.6" + "@opentelemetry/semantic-conventions": "npm:^1.28.0" + "@rollup/plugin-commonjs": "npm:28.0.1" + "@sentry-internal/browser-utils": "npm:9.1.0" + "@sentry/core": "npm:9.1.0" + "@sentry/node": "npm:9.1.0" + "@sentry/opentelemetry": "npm:9.1.0" + "@sentry/react": "npm:9.1.0" + "@sentry/vercel-edge": "npm:9.1.0" + "@sentry/webpack-plugin": "npm:3.1.2" chalk: "npm:3.0.0" resolve: "npm:1.22.8" rollup: "npm:3.29.5" stacktrace-parser: "npm:^0.1.10" peerDependencies: next: ^13.2.0 || ^14.0 || ^15.0.0-rc.0 - checksum: 10/9b23465582ce0e29574d312d47ff8c2ce2b4aa186d28b980a8769641435a0d84eb0dd80fa3ac69d10a43a4fdf5a5f58fbbb7d691d35bc4b6ea9ded722d975a14 + checksum: 10/f90db244132eca6de7b1cf9666ab92e85365798a9a0fbb9e699534ba32e47279b34e211c6b1295976e8e9e7a35a51afb8d73e44f57f5bdebdedb72b6d81752e7 languageName: node linkType: hard -"@sentry/node@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/node@npm:8.36.0" +"@sentry/node@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/node@npm:9.1.0" dependencies: "@opentelemetry/api": "npm:^1.9.0" - "@opentelemetry/context-async-hooks": "npm:^1.25.1" - "@opentelemetry/core": "npm:^1.25.1" - "@opentelemetry/instrumentation": "npm:^0.53.0" - "@opentelemetry/instrumentation-amqplib": "npm:^0.42.0" - "@opentelemetry/instrumentation-connect": "npm:0.40.0" - "@opentelemetry/instrumentation-dataloader": "npm:0.12.0" - "@opentelemetry/instrumentation-express": "npm:0.44.0" - "@opentelemetry/instrumentation-fastify": "npm:0.40.0" - "@opentelemetry/instrumentation-fs": "npm:0.16.0" - "@opentelemetry/instrumentation-generic-pool": "npm:0.39.0" - "@opentelemetry/instrumentation-graphql": "npm:0.43.0" - "@opentelemetry/instrumentation-hapi": "npm:0.41.0" - "@opentelemetry/instrumentation-http": "npm:0.53.0" - "@opentelemetry/instrumentation-ioredis": "npm:0.43.0" - "@opentelemetry/instrumentation-kafkajs": "npm:0.4.0" - "@opentelemetry/instrumentation-koa": "npm:0.43.0" - "@opentelemetry/instrumentation-lru-memoizer": "npm:0.40.0" - "@opentelemetry/instrumentation-mongodb": "npm:0.47.0" - "@opentelemetry/instrumentation-mongoose": "npm:0.42.0" - "@opentelemetry/instrumentation-mysql": "npm:0.41.0" - "@opentelemetry/instrumentation-mysql2": "npm:0.41.0" - "@opentelemetry/instrumentation-nestjs-core": "npm:0.40.0" - "@opentelemetry/instrumentation-pg": "npm:0.44.0" - "@opentelemetry/instrumentation-redis-4": "npm:0.42.0" - "@opentelemetry/instrumentation-undici": "npm:0.6.0" - "@opentelemetry/resources": "npm:^1.26.0" - "@opentelemetry/sdk-trace-base": "npm:^1.26.0" - "@opentelemetry/semantic-conventions": "npm:^1.27.0" - "@prisma/instrumentation": "npm:5.19.1" - "@sentry/core": "npm:8.36.0" - "@sentry/opentelemetry": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - import-in-the-middle: "npm:^1.11.2" - checksum: 10/ee70d919de725502e58664257074c29f60dc212c85b699ff30f6252c6be20771a38cf614295a6d9c533c9a9361df9bd0346be0476fd3005e567efa5058510acf - languageName: node - linkType: hard - -"@sentry/opentelemetry@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/opentelemetry@npm:8.36.0" + "@opentelemetry/context-async-hooks": "npm:^1.30.1" + "@opentelemetry/core": "npm:^1.30.1" + "@opentelemetry/instrumentation": "npm:^0.57.1" + "@opentelemetry/instrumentation-amqplib": "npm:^0.46.0" + "@opentelemetry/instrumentation-connect": "npm:0.43.0" + "@opentelemetry/instrumentation-dataloader": "npm:0.16.0" + "@opentelemetry/instrumentation-express": "npm:0.47.0" + "@opentelemetry/instrumentation-fastify": "npm:0.44.1" + "@opentelemetry/instrumentation-fs": "npm:0.19.0" + "@opentelemetry/instrumentation-generic-pool": "npm:0.43.0" + "@opentelemetry/instrumentation-graphql": "npm:0.47.0" + "@opentelemetry/instrumentation-hapi": "npm:0.45.1" + "@opentelemetry/instrumentation-http": "npm:0.57.1" + "@opentelemetry/instrumentation-ioredis": "npm:0.47.0" + "@opentelemetry/instrumentation-kafkajs": "npm:0.7.0" + "@opentelemetry/instrumentation-knex": "npm:0.44.0" + "@opentelemetry/instrumentation-koa": "npm:0.47.0" + "@opentelemetry/instrumentation-lru-memoizer": "npm:0.44.0" + "@opentelemetry/instrumentation-mongodb": "npm:0.51.0" + "@opentelemetry/instrumentation-mongoose": "npm:0.46.0" + "@opentelemetry/instrumentation-mysql": "npm:0.45.0" + "@opentelemetry/instrumentation-mysql2": "npm:0.45.0" + "@opentelemetry/instrumentation-pg": "npm:0.51.0" + "@opentelemetry/instrumentation-redis-4": "npm:0.46.0" + "@opentelemetry/instrumentation-tedious": "npm:0.18.0" + "@opentelemetry/instrumentation-undici": "npm:0.10.0" + "@opentelemetry/resources": "npm:^1.30.1" + "@opentelemetry/sdk-trace-base": "npm:^1.30.1" + "@opentelemetry/semantic-conventions": "npm:^1.28.0" + "@prisma/instrumentation": "npm:6.2.1" + "@sentry/core": "npm:9.1.0" + "@sentry/opentelemetry": "npm:9.1.0" + import-in-the-middle: "npm:^1.12.0" + checksum: 10/1e3f9b98d84825d8a375e2b922d83b1d1988bbb3e23abfe3ebcb8078e2552d1527a1e37f82444efdb21eafa118d7948c7c1ae4baa8540d8e42325dc24663c1d8 + languageName: node + linkType: hard + +"@sentry/opentelemetry@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/opentelemetry@npm:9.1.0" dependencies: - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" + "@sentry/core": "npm:9.1.0" peerDependencies: "@opentelemetry/api": ^1.9.0 - "@opentelemetry/core": ^1.25.1 - "@opentelemetry/instrumentation": ^0.53.0 - "@opentelemetry/sdk-trace-base": ^1.26.0 - "@opentelemetry/semantic-conventions": ^1.27.0 - checksum: 10/fea1e9ed77925b0dbac4c2e63aed63f37dc413bea397ee75dd952f16bff465f0b82706b79d6d1a2f030cc0b0f2158acfd539bf29db7a15851dd418c1d47aeee7 + "@opentelemetry/context-async-hooks": ^1.30.1 + "@opentelemetry/core": ^1.30.1 + "@opentelemetry/instrumentation": ^0.57.1 + "@opentelemetry/sdk-trace-base": ^1.30.1 + "@opentelemetry/semantic-conventions": ^1.28.0 + checksum: 10/a4deeaa37cde7eb848330731b7637ee8782d5432aa626a1c574a6d6cdc16576beb0dfd61dbc78dd89fb4d9b344be5e5646ec7361f2c88733d99544436283d117 languageName: node linkType: hard -"@sentry/react@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/react@npm:8.36.0" +"@sentry/react@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/react@npm:9.1.0" dependencies: - "@sentry/browser": "npm:8.36.0" - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" + "@sentry/browser": "npm:9.1.0" + "@sentry/core": "npm:9.1.0" hoist-non-react-statics: "npm:^3.3.2" peerDependencies: react: ^16.14.0 || 17.x || 18.x || 19.x - checksum: 10/c5edf3f4cb53e7fcecd3aa11512e125b90531ae41f04c2ade26f052e3138f217b3e254433eb28776d5b845f98fe7678e7961dc7770c063e62f8eda61868bc191 - languageName: node - linkType: hard - -"@sentry/types@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/types@npm:8.36.0" - checksum: 10/6c91218f5355e5d9396cf863d66c21edd305075ea5408e31ca52dbc0eae5e39a1247882d515856f9ad05fb7c5f0509c184c048a26086f23dfd41ef4a4eeeb38b - languageName: node - linkType: hard - -"@sentry/utils@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/utils@npm:8.36.0" - dependencies: - "@sentry/types": "npm:8.36.0" - checksum: 10/5b58bb34ed4e13b71f322a4455702ae5dab7b597410ac7774abce0dd67dcc84ee088be05e1c8d694ec795b8a84baad0467e31afd0c8026aaacac93f8b5a3701f + checksum: 10/7cf6401de342af271f454417251c386cad6b7e9613c3988da37e88f96fc9c0c6663303d18800fcfeda744528d8fee304c89c1ad4858bfe1392b231c130ca8966 languageName: node linkType: hard -"@sentry/vercel-edge@npm:8.36.0": - version: 8.36.0 - resolution: "@sentry/vercel-edge@npm:8.36.0" +"@sentry/vercel-edge@npm:9.1.0": + version: 9.1.0 + resolution: "@sentry/vercel-edge@npm:9.1.0" dependencies: "@opentelemetry/api": "npm:^1.9.0" - "@sentry/core": "npm:8.36.0" - "@sentry/types": "npm:8.36.0" - "@sentry/utils": "npm:8.36.0" - checksum: 10/b0ff6da7b9ea379aca71844c6ae9689df95c3e38135d4fa14d686a2462a2650e055b69dd51c9ffddcaf2ce9dd5eda840314fc8c0c85db817795b67ff6365e707 + "@sentry/core": "npm:9.1.0" + checksum: 10/b98b994d497a5c773a41f5c990a1561d4e699c08f3d526f57e21795089cd51817ab616069ecf7d8cc68eaa1113ea0b6af50dbfbfaa06c40fa1bc6e227d49c740 languageName: node linkType: hard -"@sentry/webpack-plugin@npm:2.22.6": - version: 2.22.6 - resolution: "@sentry/webpack-plugin@npm:2.22.6" +"@sentry/webpack-plugin@npm:3.1.2": + version: 3.1.2 + resolution: "@sentry/webpack-plugin@npm:3.1.2" dependencies: - "@sentry/bundler-plugin-core": "npm:2.22.6" + "@sentry/bundler-plugin-core": "npm:3.1.2" unplugin: "npm:1.0.1" uuid: "npm:^9.0.0" peerDependencies: webpack: ">=4.40.0" - checksum: 10/dd701dba4037eed458c80cc4b8f5fd0e90b1e6221436148d5e9d20944b69c6572ce05943cf70317631499bf3c22f624c47d7207e4fa02e3a383e4bba1898356d + checksum: 10/a752f91e6eac47983b054736a5664bb7b2ccc75e581ac973058945e9243625515cc7bcb96895afb0303cecc6e10e545657fc47e2e03279e4aec94d3f9034ae81 languageName: node linkType: hard @@ -5568,7 +5490,7 @@ __metadata: languageName: node linkType: hard -"@types/shimmer@npm:^1.0.2, @types/shimmer@npm:^1.2.0": +"@types/shimmer@npm:^1.2.0": version: 1.2.0 resolution: "@types/shimmer@npm:1.2.0" checksum: 10/f081a31d826ce7bfe8cc7ba8129d2b1dffae44fd580eba4fcf741237646c4c2494ae6de2cada4b7713d138f35f4bc512dbf01311d813dee82020f97d7d8c491c @@ -5605,6 +5527,15 @@ __metadata: languageName: node linkType: hard +"@types/tedious@npm:^4.0.14": + version: 4.0.14 + resolution: "@types/tedious@npm:4.0.14" + dependencies: + "@types/node": "npm:*" + checksum: 10/c8f6480cf68d95b5e9f64fa6210f50915e8ff124638965a2c5a4c87641cc7f762155b9a8e01e3e517d48f8931e2d3920a40c4e677398e8b93c9cf1c8a36d2fbb + languageName: node + linkType: hard + "@types/tinycolor2@npm:^1.4.6": version: 1.4.6 resolution: "@types/tinycolor2@npm:1.4.6" @@ -6342,6 +6273,15 @@ __metadata: languageName: node linkType: hard +"acorn@npm:^8.14.0": + version: 8.14.0 + resolution: "acorn@npm:8.14.0" + bin: + acorn: bin/acorn + checksum: 10/6df29c35556782ca9e632db461a7f97947772c6c1d5438a81f0c873a3da3a792487e83e404d1c6c25f70513e91aa18745f6eafb1fcc3a43ecd1920b21dd173d2 + languageName: node + linkType: hard + "adjust-sourcemap-loader@npm:^4.0.0": version: 4.0.0 resolution: "adjust-sourcemap-loader@npm:4.0.0" @@ -9833,6 +9773,18 @@ __metadata: languageName: node linkType: hard +"fdir@npm:^6.2.0": + version: 6.4.3 + resolution: "fdir@npm:6.4.3" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10/8e6d20f4590dc168de1374a9cadaa37e20ca6e0b822aa247c230e7ea1d9e9674a68cd816146435e4ecc98f9285091462ab7e5e56eebc9510931a1794e4db68b2 + languageName: node + linkType: hard + "fflate@npm:^0.4.8": version: 0.4.8 resolution: "fflate@npm:0.4.8" @@ -10079,6 +10031,13 @@ __metadata: languageName: node linkType: hard +"forwarded-parse@npm:2.1.2": + version: 2.1.2 + resolution: "forwarded-parse@npm:2.1.2" + checksum: 10/fca4df8898248d123d9d29a9fdf48005dd757366c2c17c1e195e8311a9aa89caf9f5e592f58f7d3d635087675ff39e85c32c6205838510f6f1fa4109de519930 + languageName: node + linkType: hard + "forwarded@npm:0.2.0": version: 0.2.0 resolution: "forwarded@npm:0.2.0" @@ -10360,7 +10319,7 @@ __metadata: languageName: node linkType: hard -"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10, glob@npm:^10.4.1": +"glob@npm:^10.0.0, glob@npm:^10.2.2, glob@npm:^10.3.10": version: 10.4.5 resolution: "glob@npm:10.4.5" dependencies: @@ -10970,7 +10929,19 @@ __metadata: languageName: node linkType: hard -"import-in-the-middle@npm:^1.11.2, import-in-the-middle@npm:^1.8.1": +"import-in-the-middle@npm:^1.12.0": + version: 1.13.0 + resolution: "import-in-the-middle@npm:1.13.0" + dependencies: + acorn: "npm:^8.14.0" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10/bf51e7845b8cc2808b254ad5404ea505893854f1e2d1e51f4b54df29f0b1c1ab5adbc99b3c18ec206c69b19ca648cc819cb59bd37e030033c1610134d20cf60c + languageName: node + linkType: hard + +"import-in-the-middle@npm:^1.8.1": version: 1.11.2 resolution: "import-in-the-middle@npm:1.11.2" dependencies: @@ -12745,7 +12716,7 @@ __metadata: "@mitodl/smoot-design": "npm:^3.3.0" "@next/bundle-analyzer": "npm:^14.2.15" "@remixicon/react": "npm:^4.2.0" - "@sentry/nextjs": "npm:^8.36.0" + "@sentry/nextjs": "npm:^9.0.0" "@tanstack/react-query": "npm:^5.66" "@testing-library/jest-dom": "npm:^6.4.8" "@testing-library/react": "npm:^16.1.0" @@ -14390,17 +14361,17 @@ __metadata: dependencies: "@chromatic-com/storybook": "npm:^3.0.0" "@dnd-kit/core": "npm:^6.0.8" - "@dnd-kit/sortable": "npm:^8.0.0" + "@dnd-kit/sortable": "npm:^10.0.0" "@dnd-kit/utilities": "npm:^3.2.1" "@emotion/react": "npm:^11.11.1" "@emotion/styled": "npm:^11.11.0" "@faker-js/faker": "npm:^9.0.0" "@mitodl/smoot-design": "npm:^3.3.0" "@mui/base": "npm:5.0.0-beta.69" - "@mui/lab": "npm:6.0.0-beta.26" - "@mui/material": "npm:^6.4.1" - "@mui/material-nextjs": "npm:^6.3.1" - "@mui/system": "npm:^6.4.1" + "@mui/lab": "npm:6.0.0-beta.28" + "@mui/material": "npm:^6.4.5" + "@mui/material-nextjs": "npm:^6.4.3" + "@mui/system": "npm:^6.4.3" "@remixicon/react": "npm:^4.2.0" "@storybook/addon-actions": "npm:^8.2.9" "@storybook/addon-essentials": "npm:^8.2.9" @@ -14465,7 +14436,7 @@ __metadata: resolution: "ol-utilities@workspace:frontends/ol-utilities" dependencies: "@dnd-kit/core": "npm:^6.0.8" - "@dnd-kit/sortable": "npm:^8.0.0" + "@dnd-kit/sortable": "npm:^10.0.0" "@dnd-kit/utilities": "npm:^3.2.1" "@faker-js/faker": "npm:^9.0.0" "@testing-library/react": "npm:^16.1.0" @@ -14945,6 +14916,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.2": + version: 4.0.2 + resolution: "picomatch@npm:4.0.2" + checksum: 10/ce617b8da36797d09c0baacb96ca8a44460452c89362d7cb8f70ca46b4158ba8bc3606912de7c818eb4a939f7f9015cef3c766ec8a0c6bfc725fdc078e39c717 + languageName: node + linkType: hard + "pirates@npm:^4.0.4": version: 4.0.6 resolution: "pirates@npm:4.0.6"