From d47aa29c8f06cce754785218551fe310105fed13 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:22:27 -0500 Subject: [PATCH 01/32] Update dependency tldextract to v5 (#2031) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 17 +++++++++++------ pyproject.toml | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) 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/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" From d507c1fbb13aa690e8b2d2099c9a1a146ae90c41 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:32:36 -0500 Subject: [PATCH 02/32] Update dependency @mui/lab to v6.0.0-beta.28 (#2051) * Update dependency @mui/lab to v6.0.0-beta.28 * update lockfile --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: shankar ambady --- frontends/ol-components/package.json | 2 +- yarn.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index 413e47881b..5ffc9be496 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -19,7 +19,7 @@ "@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/lab": "6.0.0-beta.28", "@mui/material": "^6.4.1", "@mui/material-nextjs": "^6.3.1", "@mui/system": "^6.4.1", diff --git a/yarn.lock b/yarn.lock index e69fb9f3bf..594774a0d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2746,9 +2746,9 @@ __metadata: 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,7 +2774,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: 10/1d3f2f51bbd92eaa9955dbec063aa90b25954bd4f120141cfeda08c0a4f4248fb2fb8737fd96b864cc604c7fe56e48336208ec72e2d581880ffd0aba0e133bc5 + checksum: 10/12f2eeb66571992003526f6e6aac25ac1cbc90ab1e12871129d5ba581f5e902715681f50f575e3297a50c7e547acca6d609a627d08211d4d4e5bd2be1ceccb3a languageName: node linkType: hard @@ -14397,7 +14397,7 @@ __metadata: "@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/lab": "npm:6.0.0-beta.28" "@mui/material": "npm:^6.4.1" "@mui/material-nextjs": "npm:^6.3.1" "@mui/system": "npm:^6.4.1" From b3d1242263044a88493b6d37f3a9678443a2fabb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 10:33:19 -0500 Subject: [PATCH 03/32] Update dependency @sentry/nextjs to v9 (#2034) * Update dependency @sentry/nextjs to v9 * update lockfile --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: shankar ambady --- frontends/main/package.json | 2 +- yarn.lock | 822 +++++++++++++++++++----------------- 2 files changed, 435 insertions(+), 389 deletions(-) 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/yarn.lock b/yarn.lock index 594774a0d8..0c14bf9d77 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3247,50 +3247,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 +3312,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 +3616,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 +3648,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 +3659,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 + checksum: 10/9b7544b639e8fee41315e2646615676ffb1020dba0f6c81e6ec1dd2daf5409fc6ce3d2b629bbd9cd32f85decc3a8bfa5dc8cc52bb72bd84c1777ca25b4301aa0 languageName: node linkType: hard -"@opentelemetry/sdk-metrics@npm:^1.9.1": - version: 1.26.0 - resolution: "@opentelemetry/sdk-metrics@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" - peerDependencies: - "@opentelemetry/api": ">=1.3.0 <1.10.0" - checksum: 10/e48e4dd1fed1e501750460e1320f89507c19287c5059cfaccc8268ad8cc3e1de40feeee6584b23626e01f9cde0f10301d08edf6a65bbd1346ef94f70ae8844f5 - 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" - 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 +3691,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 +3781,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 +3801,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 +3851,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 +4007,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 + checksum: 10/7cf6401de342af271f454417251c386cad6b7e9613c3988da37e88f96fc9c0c6663303d18800fcfeda744528d8fee304c89c1ad4858bfe1392b231c130ca8966 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 - 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 +5558,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 +5595,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 +6341,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 +9841,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 +10099,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 +10387,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 +10997,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 +12784,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" @@ -14945,6 +14984,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" From be430a1ce5b34f1c14b966b5239e3c3cfeeae9b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:18:23 -0500 Subject: [PATCH 04/32] Update akhileshns/heroku-deploy digest to e3eb99d (#2068) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/production.yml | 2 +- .github/workflows/release-candidate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 06bc32ddfc..e58b8d0888 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 diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 068bd4ef26..07dd1e2b40 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 From aeb59d30b8a6b2dca6dab0e784ab12ac5ee3f23f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 11:50:25 -0500 Subject: [PATCH 05/32] Update dependency @dnd-kit/sortable to v10 (#1974) * Update dependency @dnd-kit/sortable to v10 * updating lockfile --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: shankar ambady --- frontends/ol-components/package.json | 2 +- frontends/ol-utilities/package.json | 2 +- yarn.lock | 14 +++++++------- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontends/ol-components/package.json b/frontends/ol-components/package.json index 5ffc9be496..c18360d010 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/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", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", 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/yarn.lock b/yarn.lock index 0c14bf9d77..d8876b9aa2 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 @@ -14429,7 +14429,7 @@ __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" @@ -14504,7 +14504,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" From af38efac93a6130e98c036b04a14b2947a996f39 Mon Sep 17 00:00:00 2001 From: Arslan Ashraf <34372316+arslanashraf7@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:57:10 +0500 Subject: [PATCH 06/32] fix: Opensearch container on ARM64 based architecture (#2069) --- docker-compose.opensearch.base.yml | 1 + 1 file changed, 1 insertion(+) 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 From e394d1dd0266dbf8ab87169fa7dda5b049f3fce2 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Tue, 25 Feb 2025 11:19:27 -0500 Subject: [PATCH 07/32] Fix SCIM search API sort and pagination (#2066) --- scim/urls.py | 7 ++-- scim/views.py | 56 ++++++++++++++++++++++++++- scim/views_test.py | 96 +++++++++++++++++++++++++++++++++++++--------- 3 files changed, 135 insertions(+), 24 deletions(-) diff --git a/scim/urls.py b/scim/urls.py index 88221d982e..19691cb577 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"^\.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..3249477ae6 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,54 @@ 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", None) + sort_order = body.get("sortOrder", "ascending") + query = body.get("filter", None) + + if sort_by is not None and sort_by not in ("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 + + if sort_by is not None: + 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..0389c5056e 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -415,28 +415,86 @@ 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), + ("email", None), + ("email", "ascending"), + ("email", "descending"), + ("username", None), + ("username", "ascending"), + ("username", "descending"), + ], +) +@pytest.mark.parametrize("count", [None, 100, 500]) +def test_user_search(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]] + large_user_set = UserFactory.create_batch(1100) + 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_order = sort_order or "ascending" - data = resp.json() + if sort_by is not None: + expected = sorted( + expected, + # postgres sort is case-insensitive + key=lambda user: getattr(user, sort_by).lower(), + 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.profile.scim_id, + "active": user.is_active, + "userName": user.username, + "displayName": user.profile.name, + "emails": [{"value": user.email, "primary": True}], + "externalId": str(user.profile.scim_external_id), + "name": { + "givenName": user.first_name, + "familyName": user.last_name, + }, + "meta": { + "resourceType": "User", + "location": f"https://localhost/scim/v2/Users/{user.profile.scim_id}", + "lastModified": user.profile.updated_at.isoformat( + timespec="milliseconds" + ), + "created": user.date_joined.isoformat(timespec="milliseconds"), + }, + "groups": [], + "schemas": [djs_constants.SchemaURI.USER], + } + for user in expected_in_resp + ], + } From 6c9a8d207be80b44087762124ace70e4020e9f82 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Tue, 25 Feb 2025 15:09:23 -0500 Subject: [PATCH 08/32] Added SCIM fields to User and populate (#2062) --- profiles/admin.py | 3 - profiles/forms.py | 3 - scim/adapters.py | 18 +++-- scim/views_test.py | 16 ++--- users/admin.py | 24 +++++++ .../0004_add_scim_and_timestamp_fields.py | 65 +++++++++++++++++++ users/migrations/0005_set_user_scim_id.py | 62 ++++++++++++++++++ .../migrations/0006_remove_auth_user_view.py | 17 +++++ users/models.py | 5 +- 9 files changed, 188 insertions(+), 25 deletions(-) create mode 100644 users/admin.py create mode 100644 users/migrations/0004_add_scim_and_timestamp_fields.py create mode 100644 users/migrations/0005_set_user_scim_id.py create mode 100644 users/migrations/0006_remove_auth_user_view.py 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/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/views_test.py b/scim/views_test.py index 0389c5056e..0e90e0d5f7 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -30,7 +30,7 @@ def scim_client(staff_user): 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 +71,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 +107,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 +208,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 +218,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 +241,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 +268,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 +280,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, 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..1fe84fd0a0 --- /dev/null +++ b/users/migrations/0004_add_scim_and_timestamp_fields.py @@ -0,0 +1,65 @@ +# 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.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), + ), + ] 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..bd930329c6 --- /dev/null +++ b/users/migrations/0005_set_user_scim_id.py @@ -0,0 +1,62 @@ +# 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 becaus eif we're rolling back the columns + # just get dropped in the previous migration + operations = [ + migrations.RunPython(_set_scim_and_timestamps, migrations.RunPython.noop) + ] diff --git a/users/migrations/0006_remove_auth_user_view.py b/users/migrations/0006_remove_auth_user_view.py new file mode 100644 index 0000000000..2b33f7b224 --- /dev/null +++ b/users/migrations/0006_remove_auth_user_view.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.19 on 2025-02-24 20:23 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("users", "0005_set_user_scim_id"), + ] + + operations = [ + migrations.RunSQL( + sql="DROP VIEW auth_user;", + reverse_sql="CREATE VIEW auth_user AS SELECT * FROM users_user;", + elidable=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""" From 131c04e4dc0f865052ce9d5f6e2230ce387f551b Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Tue, 25 Feb 2025 15:24:43 -0500 Subject: [PATCH 09/32] APISIX integration (#2061) --- .pre-commit-config.yaml | 4 + .secrets.baseline | 3 +- Dockerfile | 2 +- Dockerfile-litellm | 18 - README-keycloak.md | 35 + config/apisix/apisix.yaml | 79 + config/apisix/config.yaml | 11 + config/apisix/debug.yaml | 35 + config/keycloak/providers/README.md | 1 + config/keycloak/realms/default-realm.json | 2469 +++++++++++++++++ config/keycloak/tls/README.md | 14 + config/keycloak/tls/tls.crt | 34 + config/keycloak/tls/tls.crt.default | 34 + config/keycloak/tls/tls.key | 52 + config/keycloak/tls/tls.key.default | 52 + config/litellm_config.yml | 15 - config/postgres/init-keycloak.sql | 2 + docker-compose.litellm.yml | 23 - docker-compose.services.yml | 49 +- docker-compose.yml | 7 +- env/backend.env | 2 +- env/backend.local.example.env | 29 +- env/frontend.env | 1 + env/shared.env | 11 + env/shared.local.example.env | 1 + .../main/src/app-pages/ChatPage/ChatPage.tsx | 1 + .../ChatSyllabusPage/ChatSyllabusPage.tsx | 1 + frontends/main/src/common/urls.ts | 3 +- .../AiChat/AiRecommendationBotDrawer.tsx | 1 + .../AiChatSyllabusSlideDown.tsx | 1 + main/middleware/apisix_user.py | 52 + main/middleware/apisix_user_test.py | 36 + main/settings.py | 2 + 33 files changed, 3015 insertions(+), 65 deletions(-) delete mode 100644 Dockerfile-litellm create mode 100644 README-keycloak.md create mode 100644 config/apisix/apisix.yaml create mode 100644 config/apisix/config.yaml create mode 100755 config/apisix/debug.yaml create mode 100644 config/keycloak/providers/README.md create mode 100644 config/keycloak/realms/default-realm.json create mode 100644 config/keycloak/tls/README.md create mode 100644 config/keycloak/tls/tls.crt create mode 100644 config/keycloak/tls/tls.crt.default create mode 100644 config/keycloak/tls/tls.key create mode 100644 config/keycloak/tls/tls.key.default delete mode 100644 config/litellm_config.yml create mode 100644 config/postgres/init-keycloak.sql delete mode 100644 docker-compose.litellm.yml create mode 100644 main/middleware/apisix_user.py create mode 100644 main/middleware/apisix_user_test.py 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/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.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/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/common/urls.ts b/frontends/main/src/common/urls.ts index 8d8fd01d4b..b070f9b48f 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 diff --git a/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx b/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx index 8975b68a1e..146fa934bd 100644 --- a/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx +++ b/frontends/main/src/page-components/AiChat/AiRecommendationBotDrawer.tsx @@ -97,6 +97,7 @@ const AiRecommendationBotDrawer = ({ headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ message: messages[messages.length - 1].content, diff --git a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx index 3b94c148ce..8da3c7afd2 100644 --- a/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx +++ b/frontends/main/src/page-components/LearningResourceExpanded/AiChatSyllabusSlideDown.tsx @@ -157,6 +157,7 @@ const AiChatSyllabusSlideDown = ({ headers: { "X-CSRFToken": getCsrfToken(), }, + credentials: "include", }, transformBody: (messages) => ({ collection_name: "content_files", 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 b95404e456..dd0c1e2212 100644 --- a/main/settings.py +++ b/main/settings.py @@ -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", From 0bed63b3f22504983c1e9cb54d66979c21b78336 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Wed, 26 Feb 2025 11:35:15 -0500 Subject: [PATCH 10/32] Accessibility improvements (#2071) * workaround for mui select focusVisible issue tmp * use legend for checkboxfield overall description * make focus outlines a bit more visible * add a comment about the typecasting --- .../app-pages/DashboardPage/DashboardPage.tsx | 4 + frontends/ol-components/package.json | 6 +- .../src/components/Checkbox/Checkbox.tsx | 5 + .../Checkbox/CheckboxChoiceField.tsx | 4 +- .../SelectField/SelectField.test.tsx | 54 ++++++++- .../components/SelectField/SelectField.tsx | 28 +++++ .../components/SimpleSelect/SimpleSelect.tsx | 1 - yarn.lock | 106 ++++-------------- 8 files changed, 113 insertions(+), 95 deletions(-) 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/ol-components/package.json b/frontends/ol-components/package.json index c18360d010..f8c01f3e0f 100644 --- a/frontends/ol-components/package.json +++ b/frontends/ol-components/package.json @@ -20,9 +20,9 @@ "@mitodl/smoot-design": "^3.3.0", "@mui/base": "5.0.0-beta.69", "@mui/lab": "6.0.0-beta.28", - "@mui/material": "^6.4.1", - "@mui/material-nextjs": "^6.3.1", - "@mui/system": "^6.4.1", + "@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/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/yarn.lock b/yarn.lock index d8876b9aa2..025ff8744f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2739,10 +2739,10 @@ __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 @@ -2778,9 +2778,9 @@ __metadata: 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" @@ -14437,9 +14369,9 @@ __metadata: "@mitodl/smoot-design": "npm:^3.3.0" "@mui/base": "npm:5.0.0-beta.69" "@mui/lab": "npm:6.0.0-beta.28" - "@mui/material": "npm:^6.4.1" - "@mui/material-nextjs": "npm:^6.3.1" - "@mui/system": "npm:^6.4.1" + "@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" From 798ef52373276a4e81fc7d90bcf3bec55c23de1b Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Wed, 26 Feb 2025 13:06:36 -0500 Subject: [PATCH 11/32] Fix SCIM view tests (#2073) --- main/factories.py | 12 +++++++++++- scim/views.py | 11 +++++------ scim/views_test.py | 43 +++++++++++++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 21 deletions(-) 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/scim/views.py b/scim/views.py index 3249477ae6..40517d9f54 100644 --- a/scim/views.py +++ b/scim/views.py @@ -174,11 +174,11 @@ def post(self, request, *args, **kwargs): # noqa: ARG002 start = body.get("startIndex", 1) count = body.get("count", 50) - sort_by = body.get("sortBy", None) + 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 ("email", "username"): + 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) @@ -196,11 +196,10 @@ def post(self, request, *args, **kwargs): # noqa: ARG002 msg = "Invalid filter/search query: " + str(e) raise exceptions.BadRequestError(msg) from e - if sort_by is not None: - qs = qs.order_by(sort_by) + qs = qs.order_by(sort_by) - if sort_order == "descending": - qs = qs.reverse() + if sort_order == "descending": + qs = qs.reverse() response = self._build_response(request, qs, start, count) diff --git a/scim/views_test.py b/scim/views_test.py index 0e90e0d5f7..b35f261263 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -28,6 +28,14 @@ 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(scim_external_id="1") @@ -419,6 +427,9 @@ def test_bulk_post(scim_client, bulk_test_data): ("sort_by", "sort_order"), [ (None, None), + ("id", None), + ("id", "ascending"), + ("id", "descending"), ("email", None), ("email", "ascending"), ("email", "descending"), @@ -428,24 +439,28 @@ def test_bulk_post(scim_client, bulk_test_data): ], ) @pytest.mark.parametrize("count", [None, 100, 500]) -def test_user_search(scim_client, sort_by, sort_order, count): +def test_user_search(large_user_set, scim_client, sort_by, sort_order, count): """Test the user search endpoint""" - large_user_set = UserFactory.create_batch(1100) search_users = large_user_set[:1000] emails = [user.email for user in search_users] expected = search_users effective_count = count or 50 + effective_sort_by = sort_by or "id" effective_sort_order = sort_order or "ascending" - if sort_by is not None: - expected = sorted( - expected, - # postgres sort is case-insensitive - key=lambda user: getattr(user, sort_by).lower(), - reverse=effective_sort_order == "descending", - ) + def _sort(user): + value = getattr(user, effective_sort_by) + + # postgres sort is case-insensitive + return value.lower() if isinstance(value, str) else value + + expected = sorted( + expected, + key=_sort, + reverse=effective_sort_order == "descending", + ) for page in range(int(len(emails) / effective_count)): start_index = page * effective_count # zero based index @@ -474,23 +489,23 @@ def test_user_search(scim_client, sort_by, sort_order, count): "schemas": [djs_constants.SchemaURI.LIST_RESPONSE], "Resources": [ { - "id": user.profile.scim_id, + "id": user.scim_id, "active": user.is_active, "userName": user.username, "displayName": user.profile.name, "emails": [{"value": user.email, "primary": True}], - "externalId": str(user.profile.scim_external_id), + "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.profile.scim_id}", - "lastModified": user.profile.updated_at.isoformat( + "location": f"https://localhost/scim/v2/Users/{user.scim_id}", + "lastModified": user.updated_on.isoformat( timespec="milliseconds" ), - "created": user.date_joined.isoformat(timespec="milliseconds"), + "created": user.created_on.isoformat(timespec="milliseconds"), }, "groups": [], "schemas": [djs_constants.SchemaURI.USER], From 736b8105430d7b994232c178ab28f7647c7be92c Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Thu, 27 Feb 2025 15:10:02 -0500 Subject: [PATCH 12/32] Fix user migrations for SCIM (#2078) * Fix SCIM view tests * Fix user migrations for scim fields --- .../0004_add_scim_and_timestamp_fields.py | 7 ++++++- users/migrations/0005_set_user_scim_id.py | 9 +++++++-- users/migrations/0006_remove_auth_user_view.py | 17 ----------------- 3 files changed, 13 insertions(+), 20 deletions(-) delete mode 100644 users/migrations/0006_remove_auth_user_view.py diff --git a/users/migrations/0004_add_scim_and_timestamp_fields.py b/users/migrations/0004_add_scim_and_timestamp_fields.py index 1fe84fd0a0..1ab22e5a9b 100644 --- a/users/migrations/0004_add_scim_and_timestamp_fields.py +++ b/users/migrations/0004_add_scim_and_timestamp_fields.py @@ -10,6 +10,11 @@ class Migration(migrations.Migration): ] 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", @@ -60,6 +65,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name="user", name="updated_on", - field=models.DateTimeField(auto_now=True), + 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 index bd930329c6..6370dd7b78 100644 --- a/users/migrations/0005_set_user_scim_id.py +++ b/users/migrations/0005_set_user_scim_id.py @@ -55,8 +55,13 @@ class Migration(migrations.Migration): ("users", "0004_add_scim_and_timestamp_fields"), ] - # we don't bother to undo these changes becaus eif we're rolling back the columns + # 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.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/migrations/0006_remove_auth_user_view.py b/users/migrations/0006_remove_auth_user_view.py deleted file mode 100644 index 2b33f7b224..0000000000 --- a/users/migrations/0006_remove_auth_user_view.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 4.2.19 on 2025-02-24 20:23 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("users", "0005_set_user_scim_id"), - ] - - operations = [ - migrations.RunSQL( - sql="DROP VIEW auth_user;", - reverse_sql="CREATE VIEW auth_user AS SELECT * FROM users_user;", - elidable=True, - ), - ] From 01fc6b66b78cc53cdce5fc5a242bc9d3815000ef Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Thu, 27 Feb 2025 15:18:25 -0500 Subject: [PATCH 13/32] add MITOL_LOGOUT_SUFFIX to github actions (#2079) * add MITOL_LOGOUT_SUFFIX to github actions * add dockerfile build arg * fix typo --- .github/workflows/production.yml | 2 ++ .github/workflows/release-candidate.yml | 2 ++ frontends/main/Dockerfile.web | 3 +++ 3 files changed, 7 insertions(+) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index e58b8d0888..739d60e3e4 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -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 \ @@ -84,6 +85,7 @@ jobs: 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_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 07dd1e2b40..da17cdd686 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -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 \ @@ -84,6 +85,7 @@ jobs: 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_MITOL_API_LOGOUT_SUFFIX=$MITOL_API_LOGOUT_SUFFIX \ --context-path . - name: Release Frontend on Heroku 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" From 1001aeeff5af9eb4379c636720f4c71094a32ada Mon Sep 17 00:00:00 2001 From: Shankar Ambady Date: Thu, 27 Feb 2025 15:40:50 -0500 Subject: [PATCH 14/32] Make embedding generation task use correct run (#2074) * switch to using next_run * adding test * adding fallback for missing next runs * adding test * checking published * fixing test flakiness --- vector_search/tasks.py | 8 +++- vector_search/tasks_test.py | 75 +++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) 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 + ) From aa0263041acc505046347af454d41cddf80de709 Mon Sep 17 00:00:00 2001 From: Shankar Ambady Date: Thu, 27 Feb 2025 15:41:31 -0500 Subject: [PATCH 15/32] Add all Contentfile metadata to chunk responses (#2075) * serialize contentfiles like we do with learning resources * fixing contentfile serialization * optimize loop and data fetch Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * fixing n+1 queries * adding block id to embedded metadata * adding block id as filter parameter * regenerate spec * fixing test: * some consolidation --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- frontends/api/src/generated/v0/api.ts | 18 ++++++++++ learning_resources/models.py | 10 +++++- openapi/specs/v0.yaml | 8 +++++ vector_search/constants.py | 6 ++++ vector_search/serializers.py | 5 +++ vector_search/utils.py | 50 +++++++++++++++++---------- 6 files changed, 78 insertions(+), 19 deletions(-) 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/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/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/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/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( From 7239c2309a30b8d114e5280f66422e7eaef923e2 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Fri, 28 Feb 2025 10:11:01 -0500 Subject: [PATCH 16/32] Tie chatbots to URL parameters (#2076) * make LinkAdapter use shallow * tie AskTim to drawer query param * display syllabus chat based on query param --- frontends/main/src/common/metadata.ts | 6 +- frontends/main/src/common/urls.ts | 7 +- .../AiChat/AiChatWithEntryScreen.tsx | 2 +- .../AiChat/AiRecommendationBotDrawer.tsx | 60 ++++++------- .../AiChat/AskTimDrawerButton.test.tsx | 42 +++++++++ .../AiChat/AskTimDrawerButton.tsx | 14 +-- .../LearningResourceDrawer.test.tsx | 87 ++++++++++++++++++- .../LearningResourceDrawer.tsx | 20 +++-- .../useResourceDrawerHref.ts | 4 +- .../AiChatSyllabusSlideDown.test.tsx | 12 ++- .../AiChatSyllabusSlideDown.tsx | 13 ++- .../LearningResourceExpanded.test.tsx | 35 ++++++-- .../LearningResourceExpanded.tsx | 33 ++++++- .../ResourceCard/ResourceCard.test.tsx | 4 +- frontends/main/src/test-utils/index.tsx | 4 +- .../src/components/Link/Link.tsx | 45 +++------- .../components/LinkAdapter/LinkAdapter.tsx | 51 +++++++++++ .../components/RoutedDrawer/RoutedDrawer.tsx | 7 ++ .../ThemeProvider/ThemeProvider.tsx | 12 ++- 19 files changed, 347 insertions(+), 111 deletions(-) create mode 100644 frontends/main/src/page-components/AiChat/AskTimDrawerButton.test.tsx create mode 100644 frontends/ol-components/src/components/LinkAdapter/LinkAdapter.tsx 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 b070f9b48f..9611dd62ef 100644 --- a/frontends/main/src/common/urls.ts +++ b/frontends/main/src/common/urls.ts @@ -112,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 146fa934bd..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%", - }, - }), - }, - }} - > + <> @@ -104,7 +83,30 @@ const AiRecommendationBotDrawer = ({ }), }} /> - + + ) +} + +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 8da3c7afd2..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)} > 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/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/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: { From d8a8758c36c43954d564ac1575b4adac254ecd79 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Fri, 28 Feb 2025 10:49:49 -0500 Subject: [PATCH 17/32] Fix the user search URL (#2084) --- scim/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scim/urls.py b/scim/urls.py index 19691cb577..f5fd81c52c 100644 --- a/scim/urls.py +++ b/scim/urls.py @@ -7,7 +7,7 @@ ol_scim_urls = ( [ re_path(r"^Bulk$", views.BulkView.as_view(), name="bulk"), - re_path(r"^\.search$", views.SearchView.as_view(), name="users-search"), + re_path(r"^Users/\.search$", views.SearchView.as_view(), name="users-search"), ], "ol-scim", ) From 6862b2b35922890df2405439015b637d4712a108 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Fri, 28 Feb 2025 11:39:16 -0500 Subject: [PATCH 18/32] Add comma between build args (#2083) --- .github/workflows/production.yml | 2 +- .github/workflows/release-candidate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 739d60e3e4..a19db50dca 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -84,7 +84,7 @@ 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 . diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index da17cdd686..8822dec324 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -84,7 +84,7 @@ 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 . From 1fcda6400ae72aa4351443171570d3a59d3cd40d Mon Sep 17 00:00:00 2001 From: Arslan Ashraf <34372316+arslanashraf7@users.noreply.github.com> Date: Fri, 28 Feb 2025 23:10:26 +0500 Subject: [PATCH 19/32] fix: env based _JAVA_OPTIONS for opensearch container (#2082) * fix: env based _JAVA_OPTIONS for opensearch container --- docker-compose.opensearch.base.yml | 2 +- env/backend.local.example.env | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docker-compose.opensearch.base.yml b/docker-compose.opensearch.base.yml index 947efbc783..503c0aebbf 100644 --- a/docker-compose.opensearch.base.yml +++ b/docker-compose.opensearch.base.yml @@ -4,7 +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 + - "_JAVA_OPTIONS=${JAVA_OPTIONS:-}" # Load _JAVA_OPTIONS from env, fallback to empty string - "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/env/backend.local.example.env b/env/backend.local.example.env index 4df2d8cff5..4cc276bd43 100644 --- a/env/backend.local.example.env +++ b/env/backend.local.example.env @@ -43,3 +43,7 @@ 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 + +# _JAVA_OPTIONS for Opensearch container are not consistent between different CPU architectures e.g ARM and x86. +# Here, anyone can set options like -XX:UseSVE=0 based on their CPU. +JAVA_OPTIONS= From 32dd57e39eca8645431427e2f88a72c2c7ffb089 Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Fri, 28 Feb 2025 14:39:20 -0500 Subject: [PATCH 20/32] remove next prefix from app origin (#2087) --- .github/workflows/ci.yml | 2 +- .github/workflows/release-candidate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628548d0bf..d325fa6f4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -151,7 +151,7 @@ jobs: - name: Build the Docker image env: - ORIGIN: https://next.rc.learn.mit.edu + ORIGIN: https://rc.learn.mit.edu MITOL_API_BASE_URL: https://api.rc.learn.mit.edu SITE_NAME: MIT Learn SUPPORT_EMAIL: mitlearn-support@mit.edu diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 8822dec324..b07b273973 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -42,7 +42,7 @@ jobs: env: HEROKU_APP_NAME: mitopen-rc-nextjs HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }} - ORIGIN: https://next.rc.learn.mit.edu + ORIGIN: https://rc.learn.mit.edu MITOL_API_BASE_URL: https://api.rc.learn.mit.edu SITE_NAME: MIT Learn SUPPORT_EMAIL: mitlearn-support@mit.edu From 678061d84b7f685661b14a2cda6ddf2a3eda132d Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Fri, 28 Feb 2025 15:53:04 -0500 Subject: [PATCH 21/32] remove an erroneous space (#2090) --- .github/workflows/production.yml | 2 +- .github/workflows/release-candidate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index a19db50dca..654fc24c4b 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -84,7 +84,7 @@ 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 . diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index b07b273973..6ed9731f3d 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -84,7 +84,7 @@ 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 . From 5d25380163b79c665f1d9b9396320f173f1f2095 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Fri, 28 Feb 2025 16:19:37 -0500 Subject: [PATCH 22/32] Fix the casing of the sort field for SCIM search (#2089) --- scim/constants.py | 9 +++++++++ scim/views.py | 8 +++++--- scim/views_test.py | 8 ++++---- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/scim/constants.py b/scim/constants.py index c51546dabe..135c616131 100644 --- a/scim/constants.py +++ b/scim/constants.py @@ -5,3 +5,12 @@ class SchemaURI: BULK_REQUEST = "urn:ietf:params:scim:api:messages:2.0:BulkRequest" BULK_RESPONSE = "urn:ietf:params:scim:api:messages:2.0:BulkResponse" + + +SORT_MAPPING = { + "id": "id", + "userName": "username", + "email": "email", +} + +VALID_SORTS = SORT_MAPPING.keys() diff --git a/scim/views.py b/scim/views.py index 40517d9f54..66d81edcba 100644 --- a/scim/views.py +++ b/scim/views.py @@ -178,11 +178,13 @@ def post(self, request, *args, **kwargs): # noqa: ARG002 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" + if sort_by not in constants.VALID_SORTS: + msg = f"Sorting only supports: {', '.join(constants.VALID_SORTS)}" raise exceptions.BadRequestError(msg) + else: + sort_by = constants.SORT_MAPPING[sort_by] - if sort_order is not None and sort_order not in ("ascending", "descending"): + if sort_order not in ("ascending", "descending"): msg = "Sorting only supports ascending or descending" raise exceptions.BadRequestError(msg) diff --git a/scim/views_test.py b/scim/views_test.py index b35f261263..dc88c75c2b 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -433,9 +433,9 @@ def test_bulk_post(scim_client, bulk_test_data): ("email", None), ("email", "ascending"), ("email", "descending"), - ("username", None), - ("username", "ascending"), - ("username", "descending"), + ("userName", None), + ("userName", "ascending"), + ("userName", "descending"), ], ) @pytest.mark.parametrize("count", [None, 100, 500]) @@ -447,7 +447,7 @@ def test_user_search(large_user_set, scim_client, sort_by, sort_order, count): expected = search_users effective_count = count or 50 - effective_sort_by = sort_by or "id" + effective_sort_by = constants.SORT_MAPPING[sort_by or "id"] effective_sort_order = sort_order or "ascending" def _sort(user): From b9bb89549ac2fd79cd4dbde0a68b1601481d990c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:49:15 +0000 Subject: [PATCH 23/32] fix(deps): update dependency litellm to v1.61.20 (#2096) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 8 ++++---- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1f2bba49bb..9c0fff680f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3245,13 +3245,13 @@ langsmith-pyo3 = ["langsmith-pyo3 (>=0.1.0rc2,<0.2.0)"] [[package]] name = "litellm" -version = "1.61.5" +version = "1.61.20" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.61.5-py3-none-any.whl", hash = "sha256:0f842fe96d8354fce71bdb50b7b8d86b329653161885a047de4a9c78e0cfbe82"}, - {file = "litellm-1.61.5.tar.gz", hash = "sha256:0fda7698e64933e100398d2b3ed5d1dc096021adf699c885f8baf5a7b0d7589c"}, + {file = "litellm-1.61.20-py3-none-any.whl", hash = "sha256:8158f96ceda0d76bb59a59d868686e888e32d66b2380e149c6a7a0746f7a5bc9"}, + {file = "litellm-1.61.20.tar.gz", hash = "sha256:0b0204f56e08c92efd2f9e4bfb850c25eaa95fb03a56aaa21e5e29b2391c9067"}, ] [package.dependencies] @@ -7849,4 +7849,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.6" -content-hash = "720f25b86477babc8dc3b3d70c6483f6789187f30c9ec1d7343d1f146e1e6dd5" +content-hash = "056533c1d31f54a8c9006f993e35448bf7dbfe7391c383a5fb0af0ba7655602b" diff --git a/pyproject.toml b/pyproject.toml index 4f3ba8fd7f..dc2a4cdb7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,7 +83,7 @@ pycountry = "^24.6.1" qdrant-client = {extras = ["fastembed"], version = "^1.12.0"} onnxruntime = "1.20.1" openai = "^1.55.3" -litellm = "1.61.5" +litellm = "1.61.20" langchain = "^0.3.11" tiktoken = "^0.9.0" llama-index = "^0.12.6" From 7dd0b328dafa46cc903b0bbb1c144d87849b42d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 1 Mar 2025 20:30:56 +0000 Subject: [PATCH 24/32] chore(deps): update opensearchproject/opensearch docker tag to v2.19.1 (#2094) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- docker-compose.opensearch.base.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.opensearch.base.yml b/docker-compose.opensearch.base.yml index 503c0aebbf..4a228ffb75 100644 --- a/docker-compose.opensearch.base.yml +++ b/docker-compose.opensearch.base.yml @@ -1,6 +1,6 @@ services: opensearch: - image: opensearchproject/opensearch:2.19.0 + image: opensearchproject/opensearch:2.19.1 environment: - "cluster.name=opensearch-cluster" - "bootstrap.memory_lock=true" # along with the memlock settings below, disables swapping From eb061021470f8dc4c6aadde1f42322407b032b1b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 00:44:57 +0000 Subject: [PATCH 25/32] fix(deps): update dependency ruff to v0.9.9 (#2097) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- poetry.lock | 40 ++++++++++++++++++++-------------------- pyproject.toml | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/poetry.lock b/poetry.lock index 9c0fff680f..efe119d569 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6614,29 +6614,29 @@ files = [ [[package]] name = "ruff" -version = "0.9.6" +version = "0.9.9" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.9.6-py3-none-linux_armv6l.whl", hash = "sha256:2f218f356dd2d995839f1941322ff021c72a492c470f0b26a34f844c29cdf5ba"}, - {file = "ruff-0.9.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b908ff4df65dad7b251c9968a2e4560836d8f5487c2f0cc238321ed951ea0504"}, - {file = "ruff-0.9.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b109c0ad2ececf42e75fa99dc4043ff72a357436bb171900714a9ea581ddef83"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de4367cca3dac99bcbd15c161404e849bb0bfd543664db39232648dc00112dc"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac3ee4d7c2c92ddfdaedf0bf31b2b176fa7aa8950efc454628d477394d35638b"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5dc1edd1775270e6aa2386119aea692039781429f0be1e0949ea5884e011aa8e"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:4a091729086dffa4bd070aa5dab7e39cc6b9d62eb2bef8f3d91172d30d599666"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1bbc6808bf7b15796cef0815e1dfb796fbd383e7dbd4334709642649625e7c5"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:589d1d9f25b5754ff230dce914a174a7c951a85a4e9270613a2b74231fdac2f5"}, - {file = "ruff-0.9.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc61dd5131742e21103fbbdcad683a8813be0e3c204472d520d9a5021ca8b217"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5e2d9126161d0357e5c8f30b0bd6168d2c3872372f14481136d13de9937f79b6"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:68660eab1a8e65babb5229a1f97b46e3120923757a68b5413d8561f8a85d4897"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c4cae6c4cc7b9b4017c71114115db0445b00a16de3bcde0946273e8392856f08"}, - {file = "ruff-0.9.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:19f505b643228b417c1111a2a536424ddde0db4ef9023b9e04a46ed8a1cb4656"}, - {file = "ruff-0.9.6-py3-none-win32.whl", hash = "sha256:194d8402bceef1b31164909540a597e0d913c0e4952015a5b40e28c146121b5d"}, - {file = "ruff-0.9.6-py3-none-win_amd64.whl", hash = "sha256:03482d5c09d90d4ee3f40d97578423698ad895c87314c4de39ed2af945633caa"}, - {file = "ruff-0.9.6-py3-none-win_arm64.whl", hash = "sha256:0e2bb706a2be7ddfea4a4af918562fdc1bcb16df255e5fa595bbd800ce322a5a"}, - {file = "ruff-0.9.6.tar.gz", hash = "sha256:81761592f72b620ec8fa1068a6fd00e98a5ebee342a3642efd84454f3031dca9"}, + {file = "ruff-0.9.9-py3-none-linux_armv6l.whl", hash = "sha256:628abb5ea10345e53dff55b167595a159d3e174d6720bf19761f5e467e68d367"}, + {file = "ruff-0.9.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b6cd1428e834b35d7493354723543b28cc11dc14d1ce19b685f6e68e07c05ec7"}, + {file = "ruff-0.9.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5ee162652869120ad260670706f3cd36cd3f32b0c651f02b6da142652c54941d"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3aa0f6b75082c9be1ec5a1db78c6d4b02e2375c3068438241dc19c7c306cc61a"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:584cc66e89fb5f80f84b05133dd677a17cdd86901d6479712c96597a3f28e7fe"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf3369325761a35aba75cd5c55ba1b5eb17d772f12ab168fbfac54be85cf18c"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:3403a53a32a90ce929aa2f758542aca9234befa133e29f4933dcef28a24317be"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:18454e7fa4e4d72cffe28a37cf6a73cb2594f81ec9f4eca31a0aaa9ccdfb1590"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fadfe2c88724c9617339f62319ed40dcdadadf2888d5afb88bf3adee7b35bfb"}, + {file = "ruff-0.9.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6df104d08c442a1aabcfd254279b8cc1e2cbf41a605aa3e26610ba1ec4acf0b0"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d7c62939daf5b2a15af48abbd23bea1efdd38c312d6e7c4cedf5a24e03207e17"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9494ba82a37a4b81b6a798076e4a3251c13243fc37967e998efe4cce58c8a8d1"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4efd7a96ed6d36ef011ae798bf794c5501a514be369296c672dab7921087fa57"}, + {file = "ruff-0.9.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ab90a7944c5a1296f3ecb08d1cbf8c2da34c7e68114b1271a431a3ad30cb660e"}, + {file = "ruff-0.9.9-py3-none-win32.whl", hash = "sha256:6b4c376d929c25ecd6d87e182a230fa4377b8e5125a4ff52d506ee8c087153c1"}, + {file = "ruff-0.9.9-py3-none-win_amd64.whl", hash = "sha256:837982ea24091d4c1700ddb2f63b7070e5baec508e43b01de013dc7eff974ff1"}, + {file = "ruff-0.9.9-py3-none-win_arm64.whl", hash = "sha256:3ac78f127517209fe6d96ab00f3ba97cafe38718b23b1db3e96d8b2d39e37ddf"}, + {file = "ruff-0.9.9.tar.gz", hash = "sha256:0062ed13f22173e85f8f7056f9a24016e692efeea8704d1a5e8011b8aa850933"}, ] [[package]] @@ -7849,4 +7849,4 @@ testing = ["coverage[toml]", "zope.event", "zope.testing"] [metadata] lock-version = "2.0" python-versions = "3.12.6" -content-hash = "056533c1d31f54a8c9006f993e35448bf7dbfe7391c383a5fb0af0ba7655602b" +content-hash = "0926e02041904a1d0ba58f9663e974de58e1988b399b1860167f819e7a163b45" diff --git a/pyproject.toml b/pyproject.toml index dc2a4cdb7e..1497603a71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,7 +75,7 @@ django-scim2 = "^0.19.1" django-oauth-toolkit = "^2.3.0" youtube-transcript-api = "^0.6.2" posthog = "^3.5.0" -ruff = "0.9.6" +ruff = "0.9.9" dateparser = "^1.2.0" uwsgitop = "^0.12" pytest-lazy-fixtures = "^1.1.1" From 841d72c6ae248fd83b13ff72a025d58f6efcf97f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 2 Mar 2025 04:52:01 +0000 Subject: [PATCH 26/32] chore(deps): update codecov/codecov-action action to v5.4.0 (#2098) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d325fa6f4b..1afff0c1f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,7 +81,7 @@ jobs: MITOL_COOKIE_NAME: cookie_monster - name: Upload coverage to CodeCov - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: file: ./coverage.xml @@ -138,7 +138,7 @@ jobs: NODE_ENV: test - name: Upload coverage to CodeCov - uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # v5.3.1 + uses: codecov/codecov-action@0565863a31f2c772f9f0395002a31e3f06189574 # v5.4.0 with: file: coverage/lcov.info From 761212268894eb93ef6668d173f66aaa2d52d84c Mon Sep 17 00:00:00 2001 From: Chris Chudzicki Date: Mon, 3 Mar 2025 09:55:17 -0500 Subject: [PATCH 27/32] add a comment in release actions about spaces (#2093) --- .github/workflows/production.yml | 2 +- .github/workflows/release-candidate.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/production.yml b/.github/workflows/production.yml index 654fc24c4b..ef58a874c4 100644 --- a/.github/workflows/production.yml +++ b/.github/workflows/production.yml @@ -62,7 +62,7 @@ jobs: LEARN_AI_SYLLABUS_ENDPOINT: ${{ secrets.LEARN_AI_SYLLABUS_ENDPOINT_PROD }} VERSION: ${{ github.sha }} MITOL_API_LOGOUT_SUFFIX: ${{ secrets.MITOL_API_LOGOUT_SUFFIX_PROD }} - run: | + run: | # NOTE: The --args must be comma separated and NOT have spaces heroku container:push web \ --app $HEROKU_APP_NAME \ --recursive \ diff --git a/.github/workflows/release-candidate.yml b/.github/workflows/release-candidate.yml index 6ed9731f3d..7592270b65 100644 --- a/.github/workflows/release-candidate.yml +++ b/.github/workflows/release-candidate.yml @@ -62,7 +62,7 @@ jobs: LEARN_AI_SYLLABUS_ENDPOINT: ${{ secrets.LEARN_AI_SYLLABUS_ENDPOINT_RC }} VERSION: ${{ github.sha }} MITOL_API_LOGOUT_SUFFIX: ${{ secrets.MITOL_API_LOGOUT_SUFFIX_RC }} - run: | + run: | # NOTE: The --args must be comma separated and NOT have spaces heroku container:push web \ --app $HEROKU_APP_NAME \ --recursive \ From 4e812b4f63d7b89782dde811180406f8d063bb94 Mon Sep 17 00:00:00 2001 From: Matt Bertrand Date: Mon, 3 Mar 2025 10:52:26 -0500 Subject: [PATCH 28/32] Update README to point to separate keycloak readme (#2103) --- README.md | 22 +++------------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 78b156c80c..d971646285 100644 --- a/README.md +++ b/README.md @@ -211,26 +211,10 @@ This repo includes a config for running a [Jupyter notebook](https://jupyter.org From there, you should be able to run code snippets with a live Django app just like you would in a Django shell. -### Connecting with an OpenID Connect provider for authentication +### Connecting with Keycloak for authentication -The MIT Learn application relies on an OpenID Connect client provided by Keycloak for authentication. - -The following environment variables must be defined using values from a Keycloak instance: - -- SOCIAL_AUTH_OL_OIDC_OIDC_ENDPOINT - The base URI for OpenID Connect discovery, https:/// without .well-known/openid-configuration. -- OIDC_ENDPOINT - The base URI for OpenID Connect discovery, https:/// without .well-known/openid-configuration. - -- SOCIAL_AUTH_OL_OIDC_KEY - The client ID provided by the OpenID Connect provider. -- SOCIAL_AUTH_OL_OIDC_SECRET - The client secret provided by the OpenID Connect provider. -- AUTHORIZATION_URL - Provider endpoint where the user is asked to authenticate. -- ACCESS_TOKEN_URL - Provider endpoint where client exchanges the authorization code for tokens. -- USERINFO_URL - Provder endpoint where client sends requests for identity claims. -- KEYCLOAK_BASE_URL - The base URL of the Keycloak instance. Used for generating the -- KEYCLOAK_REALM_NAME - The Keycloak realm that the OpenID Connect client exists in. - -To login via the Keycloak client, open http://od.odl.local:8063/login/ol-oidc in your browser. - -Additional details can be found at https://docs.google.com/document/d/17tJ-C2EwWoSpJWZKjuhMVgsqGtyPH0IN9KakXvSKU0M/edit +Please read [the Keycloak README](README-keycloak.md) for instructions on authenticating via +local Keycloak and APISIX containers. ### Configuring PostHog Support From 2b319ede27f55e42ff8e91c97048ff53ddb900bf Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Mon, 3 Mar 2025 16:51:06 +0000 Subject: [PATCH 29/32] Fix SCIM startIndex parsing (#2105) --- scim/views.py | 5 +++-- scim/views_test.py | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/scim/views.py b/scim/views.py index 66d81edcba..4acc710ea6 100644 --- a/scim/views.py +++ b/scim/views.py @@ -172,8 +172,9 @@ def post(self, request, *args, **kwargs): # noqa: ARG002 msg = "Invalid schema uri. Must be SearchRequest." raise exceptions.BadRequestError(msg) - start = body.get("startIndex", 1) - count = body.get("count", 50) + # cast to ints because scim-for-keycloak sends strings + start = int(body.get("startIndex", 1)) + count = int(body.get("count", 50)) sort_by = body.get("sortBy", "id") sort_order = body.get("sortOrder", "ascending") query = body.get("filter", None) diff --git a/scim/views_test.py b/scim/views_test.py index dc88c75c2b..b99e62aed7 100644 --- a/scim/views_test.py +++ b/scim/views_test.py @@ -471,10 +471,12 @@ def _sort(user): { "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 + # SCIM API is 1-based index + # Additionally, scim-for-keycloak sends this as a string, but spec examples have ints + "startIndex": str(start_index + 1), **({"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 {}), + **({"count": str(count)} if count is not None else {}), } ), ) From 06544f61ab13d3eea670ab7f6045331b63f4a912 Mon Sep 17 00:00:00 2001 From: Carey P Gumaer Date: Mon, 3 Mar 2025 13:46:23 -0500 Subject: [PATCH 30/32] Handle "next" query string param in CustomLogoutView (#2064) * if the "next" query string value is sent to the logout view, pass that along to keycloak as "post_logout_redirect_uri" instead of settings.LOGOUT_REDIRECT_URL * sanitize the url before allowing redirect --- authentication/views.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/authentication/views.py b/authentication/views.py index 55f7cdd3cc..dd7abd61e7 100644 --- a/authentication/views.py +++ b/authentication/views.py @@ -5,6 +5,7 @@ from django.conf import settings from django.contrib.auth import views from django.shortcuts import redirect +from social_core.utils import sanitize_redirect from social_django.utils import load_strategy from authentication.backends.ol_open_id_connect import OlOpenIdConnectAuth @@ -33,12 +34,16 @@ def _keycloak_logout_url(self, user): user, provider=OlOpenIdConnectAuth.name ).first() id_token = user_social_auth_record.extra_data.get("id_token") + qs_next = self.request.GET.get("next") + if qs_next: + allowed_hosts = settings.SOCIAL_AUTH_ALLOWED_REDIRECT_HOSTS or [] + qs_next = sanitize_redirect(allowed_hosts, qs_next) qs = urlencode( { "id_token_hint": id_token, - "post_logout_redirect_uri": self.request.build_absolute_uri( - settings.LOGOUT_REDIRECT_URL - ), + "post_logout_redirect_uri": qs_next + if qs_next + else self.request.build_absolute_uri(settings.LOGOUT_REDIRECT_URL), } ) @@ -55,7 +60,7 @@ def get( **kwargs, # noqa: ARG002 ): """ - GET endpoint for loggin a user out. + GET endpoint for logging a user out. The logout redirect path the user follows is: From e89b27a9d9d20827cff003f479cf1c5a44340f63 Mon Sep 17 00:00:00 2001 From: Nathan Levesque Date: Mon, 3 Mar 2025 21:36:09 +0000 Subject: [PATCH 31/32] Increase nginx header size limit to 12k (#2107) --- config/nginx.conf.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/config/nginx.conf.erb b/config/nginx.conf.erb index 04eb208e10..891c1d8b14 100644 --- a/config/nginx.conf.erb +++ b/config/nginx.conf.erb @@ -14,6 +14,7 @@ http { gzip on; gzip_comp_level 2; gzip_min_length 512; + large_client_header_buffers 4 12k; server_tokens off; From 9d935cc2d444fe3a62729edc1adb83befb4d625b Mon Sep 17 00:00:00 2001 From: Doof Date: Mon, 3 Mar 2025 21:37:04 +0000 Subject: [PATCH 32/32] Release 0.30.7 --- RELEASE.rst | 35 +++++++++++++++++++++++++++++++++++ main/settings.py | 2 +- 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/RELEASE.rst b/RELEASE.rst index 167e9af078..01566e30ba 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,6 +1,41 @@ Release Notes ============= +Version 0.30.7 +-------------- + +- Increase nginx header size limit to 12k (#2107) +- Handle "next" query string param in CustomLogoutView (#2064) +- Fix SCIM startIndex parsing (#2105) +- Update README to point to separate keycloak readme (#2103) +- add a comment in release actions about spaces (#2093) +- chore(deps): update codecov/codecov-action action to v5.4.0 (#2098) +- fix(deps): update dependency ruff to v0.9.9 (#2097) +- chore(deps): update opensearchproject/opensearch docker tag to v2.19.1 (#2094) +- fix(deps): update dependency litellm to v1.61.20 (#2096) +- Fix the casing of the sort field for SCIM search (#2089) +- remove an erroneous space (#2090) +- remove next prefix from app origin (#2087) +- fix: env based _JAVA_OPTIONS for opensearch container (#2082) +- 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/main/settings.py b/main/settings.py index e446ad7406..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()