diff --git a/.gitignore b/.gitignore index 983b6ebadba..af800d7ed51 100644 --- a/.gitignore +++ b/.gitignore @@ -43,7 +43,7 @@ videos/ # autogenerated files platform/app/src/pluginImports.js /Viewers.iml -platform/app/.recipes/Nginx-Dcm4Che/dcm4che/dcm4che-arc/* +platform/app/.recipes/Nginx-Dcm4Chee/logs/* platform/app/.recipes/OpenResty-Orthanc/logs/* .vercel diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/.gitignore b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/.gitignore new file mode 100644 index 00000000000..088f9a97f7f --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/.gitignore @@ -0,0 +1,6 @@ +logs/* +volumes/* +config/letsencrypt/* +config/certbot/* +!config/letsencrypt/.gitkeep +!config/certbot/.gitkeep diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/entrypoint.sh b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/entrypoint.sh new file mode 100644 index 00000000000..8648d7c7ca3 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Start oauth2-proxy +oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg & + +# Start nginx +nginx -g "daemon off;" diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/nginx.conf b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/nginx.conf new file mode 100644 index 00000000000..30b0089c451 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/nginx.conf @@ -0,0 +1,240 @@ +worker_processes auto; +error_log /var/logs/nginx/error.log debug; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include '/etc/nginx/mime.types'; + default_type application/octet-stream; + + keepalive_timeout 65; + keepalive_requests 100000; + tcp_nopush on; + tcp_nodelay on; + + proxy_buffers 16 16k; + proxy_buffer_size 32k; + proxy_busy_buffers_size 64k; + proxy_max_temp_file_size 128k; + + + gzip on; + gzip_disable "msie6"; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + + server { + listen 80; + server_name YOUR_DOMAIN; + + client_max_body_size 0; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + server_name YOUR_DOMAIN; + ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem; + root /var/www/html; + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; + gzip_comp_level 9; + etag on; + + location /sw.js { + add_header Cache-Control "no-cache"; + proxy_cache_bypass $http_pragma; + proxy_cache_revalidate on; + expires off; + access_log off; + } + + + location /oauth2 { + expires -1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Auth-Request-Redirect $request_uri; + proxy_pass http://localhost:4180$uri$is_args$args; + } + + location /oauth2/callback { + proxy_pass http://localhost:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /oauth2/sign_out { + expires -1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Auth-Request-Redirect /oauth2/sign_in; + proxy_pass http://localhost:4180; + } + + + location /pacs/ { + auth_request /oauth2/auth; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + expires 0; + add_header Cache-Control private; + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + rewrite ^/pacs/(.*) /dcm4chee-arc/aets/DCM4CHEE/rs/$1 break; + proxy_pass http://arc:8080; + } + + location /pacs-admin { + return 301 /pacs-admin/; + } + + # Redirect /pacs-admin to /dcm4chee-arc/ui2/ + location = /pacs-admin { + return 301 $scheme://$host/dcm4chee-arc/ui2/; + } + + # Handle /pacs-admin/ requests + location /pacs-admin/ { + return 301 $scheme://$host/dcm4chee-arc/ui2/; + } + + # Proxy pass for /dcm4chee-arc/ui2/ + location /dcm4chee-arc/ui2/ { + error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri; + auth_request /oauth2/auth?allowed_groups=pacsadmin; + + auth_request_set $user $upstream_http_x_auth_request_user; + auth_request_set $token $upstream_http_x_auth_request_access_token; + auth_request_set $auth_cookie $upstream_http_set_cookie; + + proxy_set_header X-User $user; + proxy_set_header X-Access-Token $token; + add_header Set-Cookie $auth_cookie; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + expires 0; + add_header Cache-Control private; + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + proxy_pass http://arc:8080; + } + + # Proxy pass for other /dcm4chee-arc/ requests + location /dcm4chee-arc/ { + proxy_pass http://arc:8080; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + + location /pacs { + return 301 /pacs/; + } + + + location /ohif-viewer/ { + expires -1; + error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri; + auth_request /oauth2/auth; + + auth_request_set $user $upstream_http_x_auth_request_user; + auth_request_set $token $upstream_http_x_auth_request_access_token; + auth_request_set $auth_cookie $upstream_http_set_cookie; + + proxy_set_header X-User $user; + proxy_set_header X-Access-Token $token; + add_header Set-Cookie $auth_cookie; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Proto $scheme; + + index index.html; + try_files $uri $uri/ /index.html; + } + + + location /ohif-viewer { + return 301 /ohif-viewer/; + } + + location = / { + return 301 /ohif-viewer/; + } + + location / { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; + } + + location /keycloak/ { + proxy_pass http://keycloak:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /keycloak { + return 301 /keycloak/; + } + } +} diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/oauth2-proxy.cfg b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/oauth2-proxy.cfg new file mode 100644 index 00000000000..985525af912 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/oauth2-proxy.cfg @@ -0,0 +1,22 @@ +http_address="0.0.0.0:4180" +cookie_secret="GENERATEACOOKIESECRET----------------------=" +email_domains=["*"] +cookie_secure="false" +cookie_expire="9m30s" +cookie_refresh="5m" +client_secret="2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg" +client_id="ohif_viewer" +redirect_url="http://YOUR_DOMAIN/oauth2/callback" + +ssl_insecure_skip_verify = true +insecure_oidc_allow_unverified_email = true +pass_access_token = true +provider="keycloak-oidc" +provider_display_name="Keycloak" +user_id_claim="oid" +oidc_email_claim="sub" +scope="openid" +pass_host_header=true +code_challenge_method="S256" +oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif" +insecure_oidc_skip_issuer_verification = true diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/ohif-keycloak-realm.json b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/ohif-keycloak-realm.json new file mode 100644 index 00000000000..3064ee70e42 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/ohif-keycloak-realm.json @@ -0,0 +1,2315 @@ +{ + "id": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "realm": "ohif", + "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": false, + "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": "b886fa27-974b-446f-adaa-a2c96342ce05", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + }, + { + "id": "d8bba2d8-ac65-46cb-a1b3-bbee4850333f", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + }, + { + "id": "4d80d451-18c2-4982-b2b7-f43aad1b54aa", + "name": "default-roles-ohif", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "0c749b40-bda4-463a-b1c2-edd014606c8c", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "70605217-ff53-4895-8686-2f87bb81cecd", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "1482af22-41e6-4850-b5a3-3d479edd334b", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "7b2b0fcd-3539-4322-bd12-379398ff9c63", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "f5856ab0-17fe-49bb-8e04-27d1f37c53c7", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "5cd89fbb-7860-4505-90b8-ad99b28a7ce2", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "c53a6ea6-9c79-485f-9402-b2607df09f53", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "696a4591-d966-446c-a1f6-9a8a8de41f41", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "3b0c1a20-0692-4594-973f-bd7c0d42631b", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "3279ff3a-ae5c-4d59-99a3-3b2791391745", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "56767ac1-1098-41c9-8b94-b9efe47fa17a", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "e231ed30-1c79-4786-80dc-c16d87866b03", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "388304d0-befe-4498-99cd-c9821dfe5ff6", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-identity-providers", + "view-realm", + "view-users", + "query-groups", + "manage-realm", + "manage-users", + "view-authorization", + "query-clients", + "manage-authorization", + "query-realms", + "create-client", + "query-users", + "view-events", + "view-identity-providers", + "manage-clients", + "manage-events", + "view-clients", + "impersonation" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "901b8b02-4525-4ed9-b6c5-ee822a874236", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "d9c612fa-421a-4463-8837-4bcc059649ea", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "efbd010a-67a1-472c-b308-a91723ebe819", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "60cbee1d-9fa4-47ee-8f86-f71ae22482c1", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "84219236-4eaa-4ea7-a94c-52d6f8e1bb79", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "d46f77b0-ed82-4580-994f-8a6a8fc22480", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + } + ], + "security-admin-console": [], + "ohif_viewer": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "3e2b2c4a-416e-463d-8907-b919d17a4592", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "8d52074d-c27d-4917-8280-a33bcae47f59", + "attributes": {} + } + ], + "account": [ + { + "id": "1bd17278-c076-41df-9559-7f3524d5cd5e", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "302c47ca-5e53-4cbe-9670-4e10a281d2bf", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "a6bd8131-df31-4922-b7da-7011d7e967db", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "eed205fe-b606-4562-8b65-503e3d4d7d89", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "ea20e235-92da-4409-9a82-2dc4244bc342", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "ccca6524-fbc9-4712-a703-f4311b94e757", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "1b2fa98f-7ff0-4bf6-974c-c3e8b36d8dc3", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "dc213956-175c-4ef1-99e8-0033818761f4", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + } + ] + } + }, + "groups": [ + { + "id": "3249b4f1-6572-4b59-a206-7e707d1e45f6", + "name": "pacsadmin", + "path": "/pacsadmin", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + } + ], + "defaultRole": { + "id": "4d80d451-18c2-4982-b2b7-f43aad1b54aa", + "name": "default-roles-ohif", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d" + }, + "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": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "clientId": "account", + "name": "${client_account}", + "description": "", + "rootUrl": "${authAdminUrl}", + "adminUrl": "", + "baseUrl": "/realms/ohif/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/ohif/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5c719a4e-2eed-4b9a-9536-edafb7763e6e", + "clientId": "account-console", + "name": "${client_account-console}", + "description": "", + "rootUrl": "${authAdminUrl}", + "adminUrl": "", + "baseUrl": "/realms/ohif/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/ohif/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "pkce.code.challenge.method": "S256", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "2a172f00-5ae2-400a-8aba-0c8f267844a3", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5f86b71b-9a75-465a-9007-9cadd861a1c5", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "8d52074d-c27d-4917-8280-a33bcae47f59", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ceddadb2-f4b6-4a1d-a0c8-efdd95fd2e9a", + "clientId": "ohif_viewer", + "name": "", + "description": "", + "rootUrl": "http://127.0.0.1", + "adminUrl": "http://127.0.0.1", + "baseUrl": "http://127.0.0.1", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg", + "redirectUris": [ + "http://127.0.0.1/oauth2/callback" + ], + "webOrigins": [ + "http://127.0.0.1" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1718045868", + "backchannel.logout.session.required": "true", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "b405129c-ed1b-4c4e-b712-1f736be6440b", + "name": "Audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "ohif_viewer", + "id.token.claim": "true", + "lightweight.claim": "false", + "access.token.claim": "true", + "introspection.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "groups", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ace68c1f-eae4-4d93-ab98-0005a071177d", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/ohif/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/ohif/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": "fdee0223-2998-486a-b591-ae6712ae7831", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "73b70709-cb2f-4d06-86af-0b04425970ee", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0c8df10b-097f-4b13-85b8-3ec3b4d3ef03", + "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": "6cff88ad-73ed-4699-86f9-c7ac5e473a53", + "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": "4098872d-e896-4042-a715-be23f0a1437c", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "06ba9538-581f-4c0b-a7ca-b56eaa8c1e59", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d992a9b9-ec09-49fd-b052-65942ff5ba58", + "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": "9b2089a4-ce6f-4dcb-abd8-67844a8e17b5", + "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": "0e37cc54-2ff9-4976-9f66-f6f949652d40", + "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": "f2d87384-cc84-4d8a-bef7-379491ba1430", + "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": "03c9b60a-32a9-4bd2-9173-ee7048f8face", + "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": "6db8e846-d71d-4573-89be-cc249c93c4fd", + "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": "aacde81e-9462-446f-abf9-25bb83d3c442", + "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": "44dc5e2c-de20-4e46-94ed-e5fbecee8021", + "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": "74ab1492-ecbb-4a52-a5dd-820d7bc1dec5", + "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": "69aab70e-94d5-45f0-ab8f-14c973492636", + "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": "611957f1-cdfe-43b9-acc6-d2d37b5b6908", + "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": "90e9af6c-7a44-4b8b-bdea-020ccd7cf79b", + "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": "d1ceeaef-bebc-4be6-b5b7-85e361b91ce5", + "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": "d74b3e90-c391-4fea-806d-98b24e598ade", + "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": "077a40b6-ae18-440a-9858-57cb93483f4d", + "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", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "f7a704cc-7466-41b4-ada4-3efe4dceb599", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "16085075-08fa-4fe8-aa9e-c429b26df40e", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "1cb74178-9ba0-4225-8ede-a263f0cf0f9d", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "020299f1-b469-41fb-b80d-5cea4ceb4e7d", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "id": "6759edaf-7d37-44b0-ad7b-50a3383e9cd9", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "false", + "user.attribute": "foo", + "lightweight.claim": "false", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}. roles", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "905660b4-380a-4a8c-81a9-bd16b3b3ce3f", + "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": "4e2f2bd8-602f-4ae1-a431-fa92bdedd386", + "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": "f1e69dc4-f19e-4c21-bbf9-609016339710", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "24748647-8a70-422e-8532-f8d231a006db", + "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": "fd884f9e-e345-4823-b463-beb39020ca23", + "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" + } + } + ] + }, + { + "id": "db5c28fd-e142-40e1-926c-017d0d58c04a", + "name": "groups", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "1f5afdb5-1de2-40c7-8526-d1ad0585720b", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "consentRequired": false, + "config": { + "full.path": "false", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "lightweight.claim": "false", + "access.token.claim": "true", + "claim.name": "groups" + } + } + ] + }, + { + "id": "c1678c18-641f-4eeb-af7e-c5327adee009", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "bec886da-1100-4af1-a83f-b322a23e9856", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "ead6084e-d9f1-4388-80c6-aead5b3ddfff", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3a4ceb4e-e597-49e4-8d0c-d41a46a2ab38", + "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": "df3a89a6-3ed7-45ad-97e1-598b93a643e7", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "9bbff1aa-ee20-4c33-b40f-d0076a2fe305", + "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": "726d1ea0-ee59-49fe-bd01-416b06276ae9", + "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" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "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": "e037fff2-9457-47c7-81c7-762fbe02d23c", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "cbd25133-ce87-483c-b2e3-a3c2947b23e9", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "2fa7da7b-1138-44f9-8236-7e31f5f72695", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "d2e6c9ff-9c36-40cc-bc12-0055d5ea61c3", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "269704c3-de60-4233-a845-6efd83408eb0", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "28cb7c8a-2fd1-4a3d-8ab8-17e4e83c2648", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "3a77be15-e696-49aa-9dac-64a79d8e443f", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "f662e962-d5bd-4bbe-bc43-bddc7f4faf57", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "5a307075-97e6-4c78-8e05-10e21c097d53", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "78a25e8e-b46c-44c2-8fd4-4b09529890e7", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "bf0f6364-43f0-4b1c-bde2-7646ceb56a63", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "id": "28485c9c-96bc-49c7-a9a2-8941c9067f7d", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "bbb2d80e-a134-4933-afe9-7cb655d70791", + "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": "651f23c0-4ef1-4762-b186-fc2b985ead9d", + "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": "e8dd531a-b8b7-4416-984f-9226cfcc8800", + "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": "e4fc59c9-1048-45b5-9069-57d578d0f125", + "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": "12ea6433-d1b6-4094-b3e2-1ef356b91d92", + "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": "5d6c3ca7-3581-4200-b592-9c714129044f", + "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": "b7712f6e-fbe8-4f15-936d-939e28efd279", + "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": "61a450b5-3007-4e98-a7b2-dba743ea54a7", + "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": "d7566b94-731b-4e84-a2de-a1c8130af38b", + "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": "399c5ce6-4308-4524-89bc-4dee1afd6cbb", + "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": "c8e80f39-d8d7-4fa5-8944-33fcdd366721", + "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": "79251c55-7de3-457e-99e3-92395f2b69a7", + "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": "60a12c1d-d4ce-40c1-a83f-faba71145f0c", + "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": "e36da2db-a596-4680-a5ca-16c4e5a1a9c1", + "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": "2b1b59ff-5417-48cd-8d7a-2ab5ecfedc9f", + "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": "3e4f2042-a809-490c-a1a6-9bb63b4b0e4a", + "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": "0b59d69a-95b0-4c82-bd21-908c019a4974", + "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": "accf37f2-1a52-40b0-a92f-f5a931c57126", + "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": "4eaad8bb-f86b-483d-99a4-4cba184ef573", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "eb0a326f-17e0-49b7-b25c-575b0cd3888f", + "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", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "24.0.5", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + }, + "users": [ + { + "username": "viewer", + "enabled": true, + "emailVerified": true, + "firstName": "viewer", + "lastName": "viewer", + "email": "viewer@mail.com", + "credentials": [ + { + "type": "password", + "value": "viewer" + } + ] + }, + { + "username": "pacsadmin", + "enabled": true, + "emailVerified": true, + "firstName": "pacsadmin", + "lastName": "pacsadmin", + "email": "pacsadmin@mail.com", + "credentials": [ + { + "type": "password", + "value": "pacsadmin" + } + ], + "groups": ["pacsadmin"] + } + ] +} diff --git a/platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/docker-compose.env similarity index 100% rename from platform/app/.recipes/Nginx-Dcm4chee/docker-compose-dcm4che.env rename to platform/app/.recipes/Nginx-Dcm4chee-Keycloak/docker-compose.env diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/docker-compose.yml b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/docker-compose.yml new file mode 100644 index 00000000000..1c3aa0ec2e5 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/docker-compose.yml @@ -0,0 +1,161 @@ +version: '3.8' + +services: + ldap: + image: dcm4che/slapd-dcm4chee:2.6.3-29.0 + logging: + driver: json-file + options: + max-size: "10m" + ports: + - "389:389" + env_file: docker-compose.env + volumes: + - ~/dcm4chee-arc/ldap:/var/lib/ldap + - ~/dcm4chee-arc/slapd.d:/etc/ldap/slapd.d + + db: + image: dcm4che/postgres-dcm4chee:14.5-29 + logging: + driver: json-file + options: + max-size: "10m" + ports: + - "5432:5432" + env_file: docker-compose.env + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + - ~/dcm4chee-arc/db:/var/lib/postgresql/data + + arc: + image: dcm4che/dcm4chee-arc-psql:5.29.0 + logging: + driver: json-file + options: + max-size: "10m" + ports: + - "8080:8080" + - "8443:8443" + - "9990:9990" + - "9993:9993" + - "11112:11112" + - "2762:2762" + - "2575:2575" + - "12575:12575" + env_file: docker-compose.env + environment: + WILDFLY_CHOWN: /opt/wildfly/standalone /storage + WILDFLY_WAIT_FOR: ldap:389 db:5432 + depends_on: + - ldap + - db + volumes: + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + - ~/dcm4chee-arc/wildfly:/opt/wildfly/standalone + - ~/dcm4chee-arc/storage:/storage + + ohif_viewer: + build: + context: ./../../../../ + dockerfile: ./platform/app/.recipes/Nginx-Dcm4chee-Keycloak/dockerfile + image: webapp:latest + container_name: webapp + ports: + - '443:443' # SSL + - '80:80' # Web + depends_on: + keycloak: + condition: service_healthy + restart: on-failure + networks: + - default + extra_hosts: + - 'host.docker.internal:host-gateway' + environment: + - OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true + volumes: + - ./config/nginx.conf:/etc/nginx/nginx.conf + - ./config/oauth2-proxy.cfg:/etc/oauth2-proxy/oauth2-proxy.cfg + - ./config/letsencrypt:/etc/letsencrypt + - ./config/certbot:/var/www/certbot + + keycloak: + image: quay.io/keycloak/keycloak:24.0.5 + command: 'start-dev --import-realm' + hostname: keycloak + container_name: keycloak + volumes: + - ./config/ohif-keycloak-realm.json:/opt/keycloak/data/import/ohif-keycloak-realm.json + environment: + KC_DB_URL_HOST: postgres + KC_DB: postgres + KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak' + KC_DB_SCHEMA: public + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: password + KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/ + KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/ + KC_HOSTNAME_STRICT_BACKCHANNEL: true + KC_HOSTNAME_STRICT_HTTPS: false + KC_HTTP_ENABLED: true + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + KC_PROXY: edge + KC_PROXY_HEADERS: xforwarded + KEYCLOAK_JDBC_PARAMS: connectTimeout=40000 + KC_LOG_LEVEL: INFO + KC_HOSTNAME_DEBUG: true + PROXY_ADDRESS_FORWARDING: true + ports: + - 8081:8080 + depends_on: + - postgres + restart: unless-stopped + networks: + - default + extra_hosts: + - 'host.docker.internal:host-gateway' + healthcheck: + test: + [ + "CMD-SHELL", + "exec 3<>/dev/tcp/YOUR_DOMAIN/8080;echo -e \"GET /health/ready HTTP/1.1\r\nhost: http://localhost\r\nConnection: close\r\n\r\n\" >&3;grep \"HTTP/1.1 200 OK\" <&3" + ] + interval: 1s + timeout: 5s + retries: 10 + start_period: 60s + + postgres: + image: postgres:15 + hostname: postgres + container_name: postgres + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: password + restart: unless-stopped + networks: + - default + + certbot: + image: certbot/certbot + container_name: certbot + volumes: + - ./config/letsencrypt:/etc/letsencrypt + - ./config/certbot:/var/www/certbot + entrypoint: /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;" + +volumes: + postgres_data: + driver: local + +networks: + default: + driver: bridge diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/dockerfile b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/dockerfile new file mode 100644 index 00000000000..bf610e8bd0b --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/dockerfile @@ -0,0 +1,50 @@ +# Stage 1: Build the application +FROM node:18.16.1-slim as builder + +# Setup the working directory +RUN mkdir /usr/src/app +WORKDIR /usr/src/app + +# Install dependencies +RUN apt-get update && apt-get install -y build-essential python3 + +# Copy the entire project +COPY ./ /usr/src/app/ + +# Install node dependencies +RUN yarn config set workspaces-experimental true +RUN yarn install + +# Set the environment for the build +ENV APP_CONFIG=config/docker-nginx-dcm4chee-keycloak.js + +# Build the application +RUN yarn run build + +# Stage 2: Setup the NGINX environment with OAuth2 Proxy +FROM nginx:alpine + +# Install dependencies for oauth2-proxy +RUN apk add --no-cache curl + +# Create necessary directories +RUN mkdir -p /var/logs/nginx /var/www/html /etc/oauth2-proxy + +# Download and install oauth2-proxy +RUN curl -L https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.4.0/oauth2-proxy-v7.4.0.linux-amd64.tar.gz -o oauth2-proxy.tar.gz && \ + tar -xvzf oauth2-proxy.tar.gz && \ + mv oauth2-proxy-v7.4.0.linux-amd64/oauth2-proxy /usr/local/bin/ && \ + rm -rf oauth2-proxy-v7.4.0.linux-amd64 oauth2-proxy.tar.gz + +# Copy the built application +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html + +# Copy the entrypoint script +COPY ./platform/app/.recipes/Nginx-Dcm4chee-Keycloak/config/entrypoint.sh /entrypoint.sh + +# Expose necessary ports +EXPOSE 80 443 4180 + +# Set the entrypoint script as the entrypoint +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/account/.githold b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/etc/localtime similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/account/.githold rename to platform/app/.recipes/Nginx-Dcm4chee-Keycloak/etc/localtime diff --git a/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/etc/timezone b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/etc/timezone new file mode 100644 index 00000000000..27f725e7757 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee-Keycloak/etc/timezone @@ -0,0 +1 @@ +America/New_York \ No newline at end of file diff --git a/platform/app/.recipes/Nginx-Dcm4chee/config/nginx.conf b/platform/app/.recipes/Nginx-Dcm4chee/config/nginx.conf new file mode 100644 index 00000000000..8d9ca8f5495 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee/config/nginx.conf @@ -0,0 +1,86 @@ +worker_processes auto; +error_log /var/logs/nginx/error.log debug; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/logs/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + gzip on; + gzip_disable "msie6"; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml; + + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name _; + + client_max_body_size 0; + + + # Handle /pacs requests and rewrite them to the correct dcm4chee-arc UI path + # This allows accessing the dcm4chee-arc UI through the /pacs URL + location /pacs { + rewrite ^/pacs(.*)$ /dcm4chee-arc/ui2$1 break; + proxy_pass http://arc:8080; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + expires 0; + add_header Cache-Control private; + } + + # Proxy all dcm4chee-arc requests + # This block handles all API requests and general dcm4chee-arc paths + location /dcm4chee-arc/ { + proxy_pass http://arc:8080/dcm4chee-arc/; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + proxy_request_buffering off; + } + + + location /sw.js { + add_header Cache-Control "no-cache"; + proxy_cache_bypass $http_pragma; + proxy_cache_revalidate on; + expires off; + access_log off; + } + + location / { + root /var/www/html; + index index.html; + try_files $uri $uri/ /index.html; + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header Cross-Origin-Opener-Policy 'same-origin' always; + add_header Cross-Origin-Embedder-Policy 'require-corp' always; + } + } +} diff --git a/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.env b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.env new file mode 100644 index 00000000000..54961c37601 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.env @@ -0,0 +1,4 @@ +STORAGE_DIR=/storage/fs1 +POSTGRES_DB=pacsdb +POSTGRES_USER=pacs +POSTGRES_PASSWORD=pacs diff --git a/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml index 40b6d8efe8a..88aad58daa4 100644 --- a/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml +++ b/platform/app/.recipes/Nginx-Dcm4chee/docker-compose.yml @@ -1,50 +1,45 @@ -version: '3.5' - services: ldap: - image: dcm4che/slapd-dcm4chee:2.4.44-15.0 + image: dcm4che/slapd-dcm4chee:2.6.3-29.0 logging: driver: json-file options: - max-size: '10m' + max-size: "10m" ports: - - '389:389' - env_file: ./dcm4che/docker-compose-dcm4che.env + - "389:389" + env_file: docker-compose.env volumes: - - ./dcm4che/etc/localtime:/etc/localtime:ro - - ./dcm4che/etc/timezone:/etc/timezone:ro - - ./dcm4che/dcm4che-arc/ldap:/var/lib/ldap - - ./dcm4che/dcm4che-arc/slapd.d:/etc/ldap/slapd.d - networks: - - dcm4che_default + - ~/dcm4chee-arc/ldap:/var/lib/ldap + - ~/dcm4chee-arc/slapd.d:/etc/ldap/slapd.d db: - image: dcm4che/postgres-dcm4chee:11.1-15 + image: dcm4che/postgres-dcm4chee:14.5-29 logging: driver: json-file options: - max-size: '10m' + max-size: "10m" ports: - - '5432:5432' - env_file: ./dcm4che/docker-compose-dcm4che.env + - "5432:5432" + env_file: docker-compose.env volumes: - - ./dcm4che/etc/localtime:/etc/localtime:ro - - ./dcm4che/etc/timezone:/etc/timezone:ro - - ./dcm4che/dcm4che-arc/db:/var/lib/postgresql/data - networks: - - dcm4che_default + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + - ~/dcm4chee-arc/db:/var/lib/postgresql/data arc: - image: dcm4che/dcm4chee-arc-psql:5.15.0 + image: dcm4che/dcm4chee-arc-psql:5.29.0 logging: driver: json-file options: - max-size: '10m' + max-size: "10m" ports: - - '8080:8080' - - '8443:8443' - - '9990:9990' - - '11112:11112' - - '2575:2575' - env_file: ./dcm4che/docker-compose-dcm4che.env + - "8080:8080" + - "8443:8443" + - "9990:9990" + - "9993:9993" + - "11112:11112" + - "2762:2762" + - "2575:2575" + - "12575:12575" + env_file: docker-compose.env environment: WILDFLY_CHOWN: /opt/wildfly/standalone /storage WILDFLY_WAIT_FOR: ldap:389 db:5432 @@ -52,26 +47,27 @@ services: - ldap - db volumes: - - ./dcm4che/etc/localtime:/etc/localtime:ro - - ./dcm4che/etc/timezone:/etc/timezone:ro - - ./dcm4che/dcm4che-arc/wildfly:/opt/wildfly/standalone - - ./dcm4che/dcm4che-arc/storage:/storage - networks: - - dcm4che_default - viewer: - container_name: ohif-viewer + - /etc/localtime:/etc/localtime:ro + - /etc/timezone:/etc/timezone:ro + - ~/dcm4chee-arc/wildfly:/opt/wildfly/standalone + - ~/dcm4chee-arc/storage:/storage + ohif_viewer: build: - context: ../ - dockerfile: Dockerfile + # Project root + context: ./../../../../ + # Relative to context + dockerfile: ./platform/app/.recipes/Nginx-Dcm4chee/dockerfile + image: webapp:latest + container_name: ohif_dcm4chee + volumes: + # Nginx config + - ./config/nginx.conf:/etc/nginx/nginx.conf + # Logs + - ./logs/nginx:/var/logs/nginx + # Let's Encrypt + # - letsencrypt_certificates:/etc/letsencrypt + # - letsencrypt_challenges:/var/www/letsencrypt ports: - - '80:80' - # depends_on: - # - orthanc - environment: - - NODE_ENV=production - - APP_CONFIG=config/local_dcm4chee - restart: always - networks: - - dcm4che_default - -networks: dcm4che_default: + - '443:443' # SSL + - '80:80' # Web + restart: on-failure diff --git a/platform/app/.recipes/Nginx-Dcm4chee/dockerfile b/platform/app/.recipes/Nginx-Dcm4chee/dockerfile new file mode 100644 index 00000000000..d28e9605603 --- /dev/null +++ b/platform/app/.recipes/Nginx-Dcm4chee/dockerfile @@ -0,0 +1,43 @@ +# Stage 1: Build the application +FROM node:18.16.1-slim as builder + +# Setup the working directory +RUN mkdir /usr/src/app +WORKDIR /usr/src/app + +# Install dependencies +# apt-get update is combined with apt-get install to avoid using outdated packages +RUN apt-get update && apt-get install -y build-essential python3 + +# Copy package.json and other dependency-related files first +# Assuming your package.json and yarn.lock or similar are located in the project root + +COPY ./ /usr/src/app/ + +# Install node dependencies +RUN yarn config set workspaces-experimental true +RUN yarn install + +# Copy the rest of the application code + +# set QUICK_BUILD to true to make the build faster for dev +ENV APP_CONFIG=config/docker-nginx-dcm4chee.js + +# Build the application +RUN yarn run build + +# # Stage 2: Bundle the built application into a Docker container which runs NGINX using Alpine Linux +FROM nginx:alpine + +# # Create directories for logs and html content if they don't already exist +RUN mkdir -p /var/log/nginx /var/www/html + + +# # Copy build output to serve static files +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html + +# # Expose HTTP and HTTPS ports +EXPOSE 80 443 + +# # Start NGINX +CMD ["nginx", "-g", "daemon off;"] diff --git a/platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf b/platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf deleted file mode 100644 index de38c7f0069..00000000000 --- a/platform/app/.recipes/Nginx-Dcm4chee/nginx-proxy/conf/nginx.conf +++ /dev/null @@ -1,49 +0,0 @@ -events { - worker_connections 4096; ## Default: 1024 -} - -http { - server { - listen 80 default_server; - server_name localhost; - - # - # Wide-open CORS config for nginx - # - location / { - if ($request_method = 'OPTIONS') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - # - # Custom headers and headers various browsers *should* be OK with but aren't - # - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; - # - # Tell client that this pre-flight info is valid for 20 days - # - add_header 'Access-Control-Allow-Headers' 'Authorization'; - add_header 'Access-Control-Allow-Credentials' true; - add_header 'Access-Control-Max-Age' 1728000; - add_header 'Content-Length' 0; - return 204; - } - if ($request_method = 'POST') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; - add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; - } - if ($request_method = 'GET') { - add_header 'Access-Control-Allow-Origin' '*'; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; - add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; - add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range'; - add_header 'Access-Control-Allow-Headers' 'Authorization'; - add_header 'Access-Control-Allow-Credentials' true; - } - - proxy_pass http://orthanc:8042; - } - - } -} \ No newline at end of file diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/.gitignore b/platform/app/.recipes/Nginx-Orthanc-Keycloak/.gitignore new file mode 100644 index 00000000000..088f9a97f7f --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/.gitignore @@ -0,0 +1,6 @@ +logs/* +volumes/* +config/letsencrypt/* +config/certbot/* +!config/letsencrypt/.gitkeep +!config/certbot/.gitkeep diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/admin/.githold b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/certbot/.gitkeep similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/admin/.githold rename to platform/app/.recipes/Nginx-Orthanc-Keycloak/config/certbot/.gitkeep diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/entrypoint.sh b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/entrypoint.sh new file mode 100644 index 00000000000..8648d7c7ca3 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/entrypoint.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +# Start oauth2-proxy +oauth2-proxy --config=/etc/oauth2-proxy/oauth2-proxy.cfg & + +# Start nginx +nginx -g "daemon off;" diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/email/.githold b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/letsencrypt/.gitkeep similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/email/.githold rename to platform/app/.recipes/Nginx-Orthanc-Keycloak/config/letsencrypt/.gitkeep diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/nginx.conf b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/nginx.conf new file mode 100644 index 00000000000..1ad9f512dc4 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/nginx.conf @@ -0,0 +1,210 @@ +worker_processes 2; +error_log /var/logs/nginx/mydomain.error.log; +pid /var/run/nginx.pid; +include /usr/share/nginx/modules/*.conf; + +events { + worker_connections 1024; + use epoll; + multi_accept on; +} + +http { + include '/etc/nginx/mime.types'; + default_type application/octet-stream; + + keepalive_timeout 65; + keepalive_requests 100000; + tcp_nopush on; + tcp_nodelay on; + + proxy_buffers 16 16k; + proxy_buffer_size 32k; + proxy_busy_buffers_size 64k; + proxy_max_temp_file_size 128k; + + server { + listen 80; + server_name YOUR_DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } + } + + server { + listen 443 ssl; + server_name YOUR_DOMAIN; + + ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem; + + root /var/www/html; + + gzip on; + gzip_types text/css application/javascript application/json image/svg+xml; + gzip_comp_level 9; + etag on; + + location /sw.js { + add_header Cache-Control "no-cache"; + proxy_cache_bypass $http_pragma; + proxy_cache_revalidate on; + expires off; + access_log off; + } + + location /oauth2 { + expires -1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Auth-Request-Redirect $request_uri; + proxy_pass http://localhost:4180$uri$is_args$args; + } + + location /oauth2/callback { + proxy_pass http://localhost:4180; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /oauth2/sign_out { + expires -1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Auth-Request-Redirect /oauth2/sign_in; + proxy_pass http://localhost:4180; + } + + location /pacs-admin/ { + error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri; + auth_request /oauth2/auth?allowed_groups=pacsadmin; + + auth_request_set $user $upstream_http_x_auth_request_user; + auth_request_set $token $upstream_http_x_auth_request_access_token; + auth_request_set $auth_cookie $upstream_http_set_cookie; + + proxy_set_header X-User $user; + proxy_set_header X-Access-Token $token; + add_header Set-Cookie $auth_cookie; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + expires 0; + add_header Cache-Control private; + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + proxy_pass http://orthanc:8042/; + } + + location /pacs-admin { + return 301 /pacs-admin/; + } + + location /pacs/ { + auth_request /oauth2/auth; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + expires 0; + add_header Cache-Control private; + + add_header 'Access-Control-Allow-Origin' '*' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept' always; + + if ($request_method = OPTIONS) { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'Authorization, Origin, X-Requested-With, Content-Type, Accept'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } + + proxy_pass http://orthanc:8042/dicom-web/; + } + + location /pacs { + return 301 /pacs/; + } + + location /ohif-viewer/ { + expires -1; + error_page 401 = /oauth2/sign_in?rd=$scheme://$host$request_uri; + auth_request /oauth2/auth; + + auth_request_set $user $upstream_http_x_auth_request_user; + auth_request_set $token $upstream_http_x_auth_request_access_token; + auth_request_set $auth_cookie $upstream_http_set_cookie; + + proxy_set_header X-User $user; + proxy_set_header X-Access-Token $token; + add_header Set-Cookie $auth_cookie; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header X-Forwarded-Proto $scheme; + + index index.html; + try_files $uri $uri/ /index.html; + } + + location /ohif-viewer { + return 301 /ohif-viewer/; + } + + location = / { + return 301 /ohif-viewer/; + } + + location / { + add_header Cache-Control "no-store, no-cache, must-revalidate"; + add_header 'Cross-Origin-Opener-Policy' 'same-origin' always; + add_header 'Cross-Origin-Embedder-Policy' 'require-corp' always; + } + + location /keycloak/ { + proxy_pass http://keycloak:8080/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /keycloak { + return 301 /keycloak/; + } + } +} diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/oauth2-proxy.cfg b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/oauth2-proxy.cfg new file mode 100644 index 00000000000..985525af912 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/oauth2-proxy.cfg @@ -0,0 +1,22 @@ +http_address="0.0.0.0:4180" +cookie_secret="GENERATEACOOKIESECRET----------------------=" +email_domains=["*"] +cookie_secure="false" +cookie_expire="9m30s" +cookie_refresh="5m" +client_secret="2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg" +client_id="ohif_viewer" +redirect_url="http://YOUR_DOMAIN/oauth2/callback" + +ssl_insecure_skip_verify = true +insecure_oidc_allow_unverified_email = true +pass_access_token = true +provider="keycloak-oidc" +provider_display_name="Keycloak" +user_id_claim="oid" +oidc_email_claim="sub" +scope="openid" +pass_host_header=true +code_challenge_method="S256" +oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif" +insecure_oidc_skip_issuer_verification = true diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/ohif-keycloak-realm.json b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/ohif-keycloak-realm.json new file mode 100644 index 00000000000..3064ee70e42 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/ohif-keycloak-realm.json @@ -0,0 +1,2315 @@ +{ + "id": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "realm": "ohif", + "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": false, + "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": "b886fa27-974b-446f-adaa-a2c96342ce05", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + }, + { + "id": "d8bba2d8-ac65-46cb-a1b3-bbee4850333f", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + }, + { + "id": "4d80d451-18c2-4982-b2b7-f43aad1b54aa", + "name": "default-roles-ohif", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "manage-account", + "view-profile" + ] + } + }, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "0c749b40-bda4-463a-b1c2-edd014606c8c", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "70605217-ff53-4895-8686-2f87bb81cecd", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "1482af22-41e6-4850-b5a3-3d479edd334b", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "7b2b0fcd-3539-4322-bd12-379398ff9c63", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "f5856ab0-17fe-49bb-8e04-27d1f37c53c7", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "5cd89fbb-7860-4505-90b8-ad99b28a7ce2", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "c53a6ea6-9c79-485f-9402-b2607df09f53", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "696a4591-d966-446c-a1f6-9a8a8de41f41", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "3b0c1a20-0692-4594-973f-bd7c0d42631b", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "3279ff3a-ae5c-4d59-99a3-3b2791391745", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "56767ac1-1098-41c9-8b94-b9efe47fa17a", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "e231ed30-1c79-4786-80dc-c16d87866b03", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "388304d0-befe-4498-99cd-c9821dfe5ff6", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-identity-providers", + "view-realm", + "view-users", + "query-groups", + "manage-realm", + "manage-users", + "view-authorization", + "query-clients", + "manage-authorization", + "query-realms", + "create-client", + "query-users", + "view-events", + "view-identity-providers", + "manage-clients", + "manage-events", + "view-clients", + "impersonation" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "901b8b02-4525-4ed9-b6c5-ee822a874236", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "d9c612fa-421a-4463-8837-4bcc059649ea", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "efbd010a-67a1-472c-b308-a91723ebe819", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "60cbee1d-9fa4-47ee-8f86-f71ae22482c1", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "84219236-4eaa-4ea7-a94c-52d6f8e1bb79", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + }, + { + "id": "d46f77b0-ed82-4580-994f-8a6a8fc22480", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "attributes": {} + } + ], + "security-admin-console": [], + "ohif_viewer": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "3e2b2c4a-416e-463d-8907-b919d17a4592", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "8d52074d-c27d-4917-8280-a33bcae47f59", + "attributes": {} + } + ], + "account": [ + { + "id": "1bd17278-c076-41df-9559-7f3524d5cd5e", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "302c47ca-5e53-4cbe-9670-4e10a281d2bf", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "a6bd8131-df31-4922-b7da-7011d7e967db", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "eed205fe-b606-4562-8b65-503e3d4d7d89", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "ea20e235-92da-4409-9a82-2dc4244bc342", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "ccca6524-fbc9-4712-a703-f4311b94e757", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "1b2fa98f-7ff0-4bf6-974c-c3e8b36d8dc3", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + }, + { + "id": "dc213956-175c-4ef1-99e8-0033818761f4", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "attributes": {} + } + ] + } + }, + "groups": [ + { + "id": "3249b4f1-6572-4b59-a206-7e707d1e45f6", + "name": "pacsadmin", + "path": "/pacsadmin", + "subGroups": [], + "attributes": {}, + "realmRoles": [], + "clientRoles": {} + } + ], + "defaultRole": { + "id": "4d80d451-18c2-4982-b2b7-f43aad1b54aa", + "name": "default-roles-ohif", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "37c16268-9c83-41c3-b452-3fbc86e6966d" + }, + "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": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "50a0e32a-f503-497b-b3c3-ff127cc56a56", + "clientId": "account", + "name": "${client_account}", + "description": "", + "rootUrl": "${authAdminUrl}", + "adminUrl": "", + "baseUrl": "/realms/ohif/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/ohif/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5c719a4e-2eed-4b9a-9536-edafb7763e6e", + "clientId": "account-console", + "name": "${client_account-console}", + "description": "", + "rootUrl": "${authAdminUrl}", + "adminUrl": "", + "baseUrl": "/realms/ohif/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/ohif/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "pkce.code.challenge.method": "S256", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "2a172f00-5ae2-400a-8aba-0c8f267844a3", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "5f86b71b-9a75-465a-9007-9cadd861a1c5", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "8d52074d-c27d-4917-8280-a33bcae47f59", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ceddadb2-f4b6-4a1d-a0c8-efdd95fd2e9a", + "clientId": "ohif_viewer", + "name": "", + "description": "", + "rootUrl": "http://127.0.0.1", + "adminUrl": "http://127.0.0.1", + "baseUrl": "http://127.0.0.1", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "2Xtlde7aozdkzzYHdIxQNfPDr0wNPTgg", + "redirectUris": [ + "http://127.0.0.1/oauth2/callback" + ], + "webOrigins": [ + "http://127.0.0.1" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": "1718045868", + "backchannel.logout.session.required": "true", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "b405129c-ed1b-4c4e-b712-1f736be6440b", + "name": "Audience", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-mapper", + "consentRequired": false, + "config": { + "included.client.audience": "ohif_viewer", + "id.token.claim": "true", + "lightweight.claim": "false", + "access.token.claim": "true", + "introspection.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "groups", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "fac6a1ed-5b50-4e83-b4a5-dff4f6499abc", + "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": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "ace68c1f-eae4-4d93-ab98-0005a071177d", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/ohif/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/ohif/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": "fdee0223-2998-486a-b591-ae6712ae7831", + "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", + "profile", + "roles", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "73b70709-cb2f-4d06-86af-0b04425970ee", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "0c8df10b-097f-4b13-85b8-3ec3b4d3ef03", + "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": "6cff88ad-73ed-4699-86f9-c7ac5e473a53", + "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": "4098872d-e896-4042-a715-be23f0a1437c", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "06ba9538-581f-4c0b-a7ca-b56eaa8c1e59", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "d992a9b9-ec09-49fd-b052-65942ff5ba58", + "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": "9b2089a4-ce6f-4dcb-abd8-67844a8e17b5", + "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": "0e37cc54-2ff9-4976-9f66-f6f949652d40", + "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": "f2d87384-cc84-4d8a-bef7-379491ba1430", + "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": "03c9b60a-32a9-4bd2-9173-ee7048f8face", + "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": "6db8e846-d71d-4573-89be-cc249c93c4fd", + "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": "aacde81e-9462-446f-abf9-25bb83d3c442", + "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": "44dc5e2c-de20-4e46-94ed-e5fbecee8021", + "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": "74ab1492-ecbb-4a52-a5dd-820d7bc1dec5", + "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": "69aab70e-94d5-45f0-ab8f-14c973492636", + "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": "611957f1-cdfe-43b9-acc6-d2d37b5b6908", + "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": "90e9af6c-7a44-4b8b-bdea-020ccd7cf79b", + "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": "d1ceeaef-bebc-4be6-b5b7-85e361b91ce5", + "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": "d74b3e90-c391-4fea-806d-98b24e598ade", + "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": "077a40b6-ae18-440a-9858-57cb93483f4d", + "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", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "f7a704cc-7466-41b4-ada4-3efe4dceb599", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "16085075-08fa-4fe8-aa9e-c429b26df40e", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "1cb74178-9ba0-4225-8ede-a263f0cf0f9d", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "access.token.claim": "true" + } + }, + { + "id": "020299f1-b469-41fb-b80d-5cea4ceb4e7d", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String" + } + }, + { + "id": "6759edaf-7d37-44b0-ad7b-50a3383e9cd9", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "userinfo.token.claim": "false", + "user.attribute": "foo", + "lightweight.claim": "false", + "id.token.claim": "false", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}. roles", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "905660b4-380a-4a8c-81a9-bd16b3b3ce3f", + "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": "4e2f2bd8-602f-4ae1-a431-fa92bdedd386", + "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": "f1e69dc4-f19e-4c21-bbf9-609016339710", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "introspection.token.claim": "true", + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "24748647-8a70-422e-8532-f8d231a006db", + "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": "fd884f9e-e345-4823-b463-beb39020ca23", + "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" + } + } + ] + }, + { + "id": "db5c28fd-e142-40e1-926c-017d0d58c04a", + "name": "groups", + "description": "", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "gui.order": "", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "1f5afdb5-1de2-40c7-8526-d1ad0585720b", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-group-membership-mapper", + "consentRequired": false, + "config": { + "full.path": "false", + "introspection.token.claim": "true", + "userinfo.token.claim": "true", + "multivalued": "true", + "id.token.claim": "true", + "lightweight.claim": "false", + "access.token.claim": "true", + "claim.name": "groups" + } + } + ] + }, + { + "id": "c1678c18-641f-4eeb-af7e-c5327adee009", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "bec886da-1100-4af1-a83f-b322a23e9856", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "ead6084e-d9f1-4388-80c6-aead5b3ddfff", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "3a4ceb4e-e597-49e4-8d0c-d41a46a2ab38", + "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": "df3a89a6-3ed7-45ad-97e1-598b93a643e7", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "9bbff1aa-ee20-4c33-b40f-d0076a2fe305", + "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": "726d1ea0-ee59-49fe-bd01-416b06276ae9", + "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" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "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": "e037fff2-9457-47c7-81c7-762fbe02d23c", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "cbd25133-ce87-483c-b2e3-a3c2947b23e9", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-user-attribute-mapper", + "oidc-usermodel-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "saml-user-property-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "2fa7da7b-1138-44f9-8236-7e31f5f72695", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "d2e6c9ff-9c36-40cc-bc12-0055d5ea61c3", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "269704c3-de60-4233-a845-6efd83408eb0", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "28cb7c8a-2fd1-4a3d-8ab8-17e4e83c2648", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "3a77be15-e696-49aa-9dac-64a79d8e443f", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "f662e962-d5bd-4bbe-bc43-bddc7f4faf57", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "5a307075-97e6-4c78-8e05-10e21c097d53", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "78a25e8e-b46c-44c2-8fd4-4b09529890e7", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "bf0f6364-43f0-4b1c-bde2-7646ceb56a63", + "name": "hmac-generated-hs512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS512" + ] + } + }, + { + "id": "28485c9c-96bc-49c7-a9a2-8941c9067f7d", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "bbb2d80e-a134-4933-afe9-7cb655d70791", + "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": "651f23c0-4ef1-4762-b186-fc2b985ead9d", + "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": "e8dd531a-b8b7-4416-984f-9226cfcc8800", + "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": "e4fc59c9-1048-45b5-9069-57d578d0f125", + "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": "12ea6433-d1b6-4094-b3e2-1ef356b91d92", + "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": "5d6c3ca7-3581-4200-b592-9c714129044f", + "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": "b7712f6e-fbe8-4f15-936d-939e28efd279", + "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": "61a450b5-3007-4e98-a7b2-dba743ea54a7", + "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": "d7566b94-731b-4e84-a2de-a1c8130af38b", + "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": "399c5ce6-4308-4524-89bc-4dee1afd6cbb", + "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": "c8e80f39-d8d7-4fa5-8944-33fcdd366721", + "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": "79251c55-7de3-457e-99e3-92395f2b69a7", + "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": "60a12c1d-d4ce-40c1-a83f-faba71145f0c", + "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": "e36da2db-a596-4680-a5ca-16c4e5a1a9c1", + "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": "2b1b59ff-5417-48cd-8d7a-2ab5ecfedc9f", + "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": "3e4f2042-a809-490c-a1a6-9bb63b4b0e4a", + "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": "0b59d69a-95b0-4c82-bd21-908c019a4974", + "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": "accf37f2-1a52-40b0-a92f-f5a931c57126", + "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": "4eaad8bb-f86b-483d-99a4-4cba184ef573", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "eb0a326f-17e0-49b7-b25c-575b0cd3888f", + "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", + "cibaExpiresIn": "120", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DeviceCodeLifespan": "600", + "oauth2DevicePollingInterval": "5", + "parRequestUriLifespan": "60", + "cibaInterval": "5", + "realmReusableOtpCode": "false" + }, + "keycloakVersion": "24.0.5", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + }, + "users": [ + { + "username": "viewer", + "enabled": true, + "emailVerified": true, + "firstName": "viewer", + "lastName": "viewer", + "email": "viewer@mail.com", + "credentials": [ + { + "type": "password", + "value": "viewer" + } + ] + }, + { + "username": "pacsadmin", + "enabled": true, + "emailVerified": true, + "firstName": "pacsadmin", + "lastName": "pacsadmin", + "email": "pacsadmin@mail.com", + "credentials": [ + { + "type": "password", + "value": "pacsadmin" + } + ], + "groups": ["pacsadmin"] + } + ] +} diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json b/platform/app/.recipes/Nginx-Orthanc-Keycloak/config/orthanc.json similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json rename to platform/app/.recipes/Nginx-Orthanc-Keycloak/config/orthanc.json diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/docker-compose.yml b/platform/app/.recipes/Nginx-Orthanc-Keycloak/docker-compose.yml new file mode 100644 index 00000000000..1a44c3013c4 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/docker-compose.yml @@ -0,0 +1,126 @@ +services: + ohif_viewer: + build: + context: ./../../../../ + dockerfile: ./platform/app/.recipes/Nginx-Orthanc-Keycloak/dockerfile + image: webapp:latest + container_name: webapp + ports: + - '443:443' # SSL + - '80:80' # Web + depends_on: + keycloak: + condition: service_healthy + restart: on-failure + networks: + - default + extra_hosts: + - 'host.docker.internal:host-gateway' + environment: + - OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true + volumes: + # - ../../app/dist /var/www/html + - ./config/nginx.conf:/etc/nginx/nginx.conf + - ./config/oauth2-proxy.cfg:/etc/oauth2-proxy/oauth2-proxy.cfg + + - ./config/letsencrypt:/etc/letsencrypt + - ./config/certbot:/var/www/certbot + + orthanc: + image: jodogne/orthanc-plugins + hostname: orthanc + container_name: orthanc + volumes: + - ./config/orthanc.json:/etc/orthanc/orthanc.json:ro + - ./volumes/orthanc-db/:/var/lib/orthanc/db/ + restart: unless-stopped + networks: + - default + + keycloak: + image: quay.io/keycloak/keycloak:24.0.5 + command: 'start-dev --import-realm' + hostname: keycloak + container_name: keycloak + volumes: + - ./config/ohif-keycloak-realm.json:/opt/keycloak/data/import/ohif-keycloak-realm.json + environment: + # Database + KC_DB_URL_HOST: postgres + KC_DB: postgres + KC_DB_URL: 'jdbc:postgresql://postgres:5432/keycloak' + KC_DB_SCHEMA: public + KC_DB_USERNAME: keycloak + KC_DB_PASSWORD: password + KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/ + KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/ + KC_HOSTNAME_STRICT_BACKCHANNEL: true + KC_HOSTNAME_STRICT_HTTPS: false + KC_HTTP_ENABLED: true + KEYCLOAK_ADMIN: admin + KEYCLOAK_ADMIN_PASSWORD: admin + KC_HEALTH_ENABLED: true + KC_METRICS_ENABLED: true + KC_PROXY: edge + KC_PROXY_HEADERS: xforwarded + KEYCLOAK_JDBC_PARAMS: connectTimeout=40000 + KC_LOG_LEVEL: INFO + KC_HOSTNAME_DEBUG: true + # added later + PROXY_ADDRESS_FORWARDING: true + ports: + - 8080:8080 + depends_on: + - postgres + restart: unless-stopped + networks: + - default + extra_hosts: + - 'host.docker.internal:host-gateway' + healthcheck: + test: [ + 'CMD-SHELL', + "exec 3<>/dev/tcp/YOUR_DOMAIN/8080;echo -e \"GET /health/ready HTTP/1.1\r + + host: http://localhost\r + + Connection: close\r + + \r + + \" >&3;grep \"HTTP/1.1 200 OK\" <&3", + ] + interval: 1s + timeout: 5s + retries: 10 + start_period: 60s + + postgres: + image: postgres:15 + hostname: postgres + container_name: postgres + volumes: + - postgres_data:/var/lib/postgresql/data + environment: + POSTGRES_DB: keycloak + POSTGRES_USER: keycloak + POSTGRES_PASSWORD: password + restart: unless-stopped + networks: + - default + + certbot: + image: certbot/certbot + container_name: certbot + volumes: + - ./config/letsencrypt:/etc/letsencrypt + - ./config/certbot:/var/www/certbot + entrypoint: + /bin/sh -c "trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;" +volumes: + postgres_data: + driver: local + +networks: + default: + driver: bridge diff --git a/platform/app/.recipes/Nginx-Orthanc-Keycloak/dockerfile b/platform/app/.recipes/Nginx-Orthanc-Keycloak/dockerfile new file mode 100644 index 00000000000..eba4237b448 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc-Keycloak/dockerfile @@ -0,0 +1,57 @@ +# Stage 1: Build the application +FROM node:18.16.1-slim as builder + +# Setup the working directory +RUN mkdir /usr/src/app +WORKDIR /usr/src/app + +# Install dependencies +# apt-get update is combined with apt-get install to avoid using outdated packages +RUN apt-get update && apt-get install -y build-essential python3 + +# Copy package.json and other dependency-related files first +# Assuming your package.json and yarn.lock or similar are located in the project root +# Todo: this probably can get improved by copying +# only the package json files and running yarn install before +# copying the rest of the files but having a monorepo setup +# makes this a bit more complicated, i wasn't able to get it working +COPY ./ /usr/src/app/ + +# Install node dependencies +RUN yarn config set workspaces-experimental true +RUN yarn install + +# Copy the rest of the application code + +# set QUICK_BUILD to true to make the build faster for dev +ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js + +# Build the application +RUN yarn run build + +# Use nginx as the base image +FROM nginx:alpine + +# Install dependencies for oauth2-proxy +RUN apk add --no-cache curl + +# Create necessary directories +RUN mkdir -p /var/logs/nginx /var/www/html /etc/oauth2-proxy + +# Download and install oauth2-proxy +RUN curl -L https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.4.0/oauth2-proxy-v7.4.0.linux-amd64.tar.gz -o oauth2-proxy.tar.gz && \ + tar -xvzf oauth2-proxy.tar.gz && \ + mv oauth2-proxy-v7.4.0.linux-amd64/oauth2-proxy /usr/local/bin/ && \ + rm -rf oauth2-proxy-v7.4.0.linux-amd64 oauth2-proxy.tar.gz + + +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html + +# Copy the entrypoint script +COPY ./platform/app/.recipes/Nginx-Orthanc-Keycloak/config/entrypoint.sh /entrypoint.sh + +# Expose necessary ports +EXPOSE 80 443 4180 +# Set the entrypoint script as the entrypoint +RUN chmod +x entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/platform/app/.recipes/Nginx-Orthanc/README.md b/platform/app/.recipes/Nginx-Orthanc/README.md new file mode 100644 index 00000000000..14e1a70b8e3 --- /dev/null +++ b/platform/app/.recipes/Nginx-Orthanc/README.md @@ -0,0 +1,26 @@ +# Docker compose files + +# Build + +Using docker compose you can build the image with the following command: + +```bash +docker-compose build +``` + +# Run + +To run the container use the following command: + +```bash +docker-compose up +``` + + +# Routes + +http://localhost/ -> OHIF +localhost/pacs -> Orthanc + + +See [here](../../../docs/docs/deployment/nginx--image-archive.md) for more information about this recipe. diff --git a/platform/app/.recipes/Nginx-Orthanc/dockerfile b/platform/app/.recipes/Nginx-Orthanc/dockerfile index 765f91aa668..ac7433ac6c6 100644 --- a/platform/app/.recipes/Nginx-Orthanc/dockerfile +++ b/platform/app/.recipes/Nginx-Orthanc/dockerfile @@ -12,20 +12,19 @@ RUN apt-get update && apt-get install -y build-essential python3 # Copy package.json and other dependency-related files first # Assuming your package.json and yarn.lock or similar are located in the project root -COPY . . +COPY ./ /usr/src/app/ # Install node dependencies -# RUN yarn config set workspaces-experimental true -# RUN yarn install +RUN yarn config set workspaces-experimental true +RUN yarn install # Copy the rest of the application code # set QUICK_BUILD to true to make the build faster for dev - -ENV APP_CONFIG=config/docker_nginx-orthanc-keycloak.js +ENV APP_CONFIG=config/docker-nginx-orthanc.js # Build the application -# RUN yarn run build +RUN yarn run build # # Stage 2: Bundle the built application into a Docker container which runs NGINX using Alpine Linux FROM nginx:alpine @@ -35,7 +34,7 @@ RUN mkdir -p /var/log/nginx /var/www/html # # Copy build output to serve static files -# COPY --from=builder /usr/src/app/platform/app/dist /var/www/html +COPY --from=builder /usr/src/app/platform/app/dist /var/www/html # # Expose HTTP and HTTPS ports EXPOSE 80 443 diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.env b/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.env deleted file mode 100644 index 6989ff3fc7e..00000000000 --- a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/.env +++ /dev/null @@ -1,5 +0,0 @@ -# Docker ENV and ARG Variables -# ---------------------------- -# https://vsupalov.com/docker-arg-env-variable-guide/ -# -# diff --git a/platform/app/.recipes/OpenResty-Orthanc/.env b/platform/app/.recipes/OpenResty-Orthanc/.env deleted file mode 100644 index 6989ff3fc7e..00000000000 --- a/platform/app/.recipes/OpenResty-Orthanc/.env +++ /dev/null @@ -1,5 +0,0 @@ -# Docker ENV and ARG Variables -# ---------------------------- -# https://vsupalov.com/docker-arg-env-variable-guide/ -# -# diff --git a/platform/app/.recipes/README.md b/platform/app/.recipes/README.md deleted file mode 100644 index 1f3f6f20007..00000000000 --- a/platform/app/.recipes/README.md +++ /dev/null @@ -1,53 +0,0 @@ -# Docker compose files - -This folder contains docker-compose files used to spin up OHIF-Viewer with -different options such as locally or with any PAS you desire to - -## Public Server - -#### build - -`$ docker-compose -f docker-compose-publicserver.yml build` - -#### run - -`$ docker-compose -f docker-compose-publicserver.yml up -d` - -then, access the application at [http://localhost](http://localhost) - -## Local Orthanc - -### Build - -`$ docker-compose -f docker-compose-orthanc.yml build` - -### Run - -Starts containers and leaves them running in the background. - -`$ docker-compose -f docker-compose-orthanc.yml up -d` - -then, access the application at [http://localhost](http://localhost) - -**remember that you have to access orthanc application and include your studies -there** - -## Local Dcm4chee - -#### build - -`$ docker-compose -f docker-compose-dcm4chee.yml build` - -#### run - -`$ docker-compose -f docker-compose-dcm4chee.yml up -d` - -then, access the application at [http://localhost](http://localhost) - -**remember that you have to access dcm4chee application and include your studies -there** You can use the following command to import your studies into dcm4che - -`$ docker run -v {YOUR_STUDY_FOLDER}:/tmp --rm --network=docker_dcm4che_default dcm4che/dcm4che-tools:5.14.0 storescu -cDCM4CHEE@arc:11112 /tmp` - -**make sure that your Docker network name is docker_dcm4chee_default or change -it to the right one** diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/nginx.conf diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/ohif-keycloak-realm.json diff --git a/platform/app/.recipes/OpenResty-Orthanc/config/orthanc.json b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc/config/orthanc.json rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/config/orthanc.json diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/docker-compose.yml diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/dockerfile similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/dockerfile rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/dockerfile diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/welcome/.githold b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/account/.githold similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/welcome/.githold rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/account/.githold diff --git a/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/admin/.githold b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/admin/.githold new file mode 100644 index 00000000000..e69de29bb2d diff --git a/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/email/.githold b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/email/.githold new file mode 100644 index 00000000000..e69de29bb2d diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/css/styles.css diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/img/background.jpg b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/img/background.jpg similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/img/background.jpg rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/resources/img/background.jpg diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/theme.properties b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/theme.properties similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/theme.properties rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/login/theme.properties diff --git a/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/welcome/.githold b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/ohif/welcome/.githold new file mode 100644 index 00000000000..e69de29bb2d diff --git a/platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/orthanc-db/.gitignore b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/orthanc-db/.gitignore similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc-Keycloak/volumes/orthanc-db/.gitignore rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/orthanc-db/.gitignore diff --git a/platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/config/nginx.conf similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc/config/nginx.conf rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/config/nginx.conf diff --git a/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/config/orthanc.json b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/config/orthanc.json new file mode 100644 index 00000000000..2e10723c049 --- /dev/null +++ b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/config/orthanc.json @@ -0,0 +1,89 @@ +{ + "Name": "Orthanc inside Docker", + "StorageDirectory": "/var/lib/orthanc/db", + "IndexDirectory": "/var/lib/orthanc/db", + "StorageCompression": false, + "MaximumStorageSize": 0, + "MaximumPatientCount": 0, + "LuaScripts": [], + "Plugins": ["/usr/share/orthanc/plugins", "/usr/local/share/orthanc/plugins"], + "ConcurrentJobs": 2, + "HttpServerEnabled": true, + "HttpPort": 8042, + "HttpDescribeErrors": true, + "HttpCompressionEnabled": true, + "DicomServerEnabled": true, + "DicomAet": "ORTHANC", + "DicomCheckCalledAet": false, + "DicomPort": 4242, + "DefaultEncoding": "Latin1", + "DeflatedTransferSyntaxAccepted": true, + "JpegTransferSyntaxAccepted": true, + "Jpeg2000TransferSyntaxAccepted": true, + "JpegLosslessTransferSyntaxAccepted": true, + "JpipTransferSyntaxAccepted": true, + "Mpeg2TransferSyntaxAccepted": true, + "RleTransferSyntaxAccepted": true, + "UnknownSopClassAccepted": false, + "DicomScpTimeout": 30, + + "RemoteAccessAllowed": true, + "SslEnabled": false, + "SslCertificate": "certificate.pem", + "AuthenticationEnabled": false, + "RegisteredUsers": { + "test": "test" + }, + "DicomModalities": {}, + "DicomModalitiesInDatabase": false, + "DicomAlwaysAllowEcho": true, + "DicomAlwaysAllowStore": true, + "DicomCheckModalityHost": false, + "DicomScuTimeout": 10, + "OrthancPeers": {}, + "OrthancPeersInDatabase": false, + "HttpProxy": "", + + "HttpVerbose": true, + + "HttpTimeout": 10, + "HttpsVerifyPeers": true, + "HttpsCACertificates": "", + "UserMetadata": {}, + "UserContentType": {}, + "StableAge": 60, + "StrictAetComparison": false, + "StoreMD5ForAttachments": true, + "LimitFindResults": 0, + "LimitFindInstances": 0, + "LimitJobs": 10, + "LogExportedResources": false, + "KeepAlive": true, + "TcpNoDelay": true, + "HttpThreadsCount": 50, + "StoreDicom": true, + "DicomAssociationCloseDelay": 5, + "QueryRetrieveSize": 10, + "CaseSensitivePN": false, + "LoadPrivateDictionary": true, + "Dictionary": {}, + "SynchronousCMove": true, + "JobsHistorySize": 10, + "SaveJobs": true, + "OverwriteInstances": false, + "MediaArchiveSize": 1, + "StorageAccessOnFind": "Always", + "MetricsEnabled": true, + + "DicomWeb": { + "Enable": true, + "Root": "/dicom-web/", + "EnableWado": true, + "WadoRoot": "/wado", + "Host": "127.0.0.1", + "Ssl": false, + "StowMaxInstances": 10, + "StowMaxSize": 10, + "QidoCaseSensitive": false + } +} diff --git a/platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/docker-compose.yml similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc/docker-compose.yml rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/docker-compose.yml diff --git a/platform/app/.recipes/OpenResty-Orthanc/dockerfile b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/dockerfile similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc/dockerfile rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/dockerfile diff --git a/platform/app/.recipes/OpenResty-Orthanc/volumes/orthanc-db/.gitignore b/platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/volumes/orthanc-db/.gitignore similarity index 100% rename from platform/app/.recipes/OpenResty-Orthanc/volumes/orthanc-db/.gitignore rename to platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc/volumes/orthanc-db/.gitignore diff --git a/platform/app/package.json b/platform/app/package.json index ab4db40e45c..0276a4f85bf 100644 --- a/platform/app/package.json +++ b/platform/app/package.json @@ -24,13 +24,13 @@ "build:viewer:ci": "cross-env NODE_ENV=production PUBLIC_URL=/ APP_CONFIG=config/netlify.js QUICK_BUILD=false yarn run build", "build:viewer:qa": "cross-env NODE_ENV=production APP_CONFIG=config/google.js yarn run build", "build:viewer:demo": "cross-env NODE_ENV=production APP_CONFIG=config/demo.js HTML_TEMPLATE=rollbar.html QUICK_BUILD=false yarn run build", - "build": "node --max_old_space_size=4096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js", + "build": "node --max_old_space_size=8096 ./../../node_modules/webpack/bin/webpack.js --progress --config .webpack/webpack.pwa.js", "clean": "shx rm -rf dist", "clean:deep": "yarn run clean && shx rm -rf node_modules", "dev": "cross-env NODE_ENV=development webpack serve --config .webpack/webpack.pwa.js", "dev:no:cache": "cross-env NODE_ENV=development webpack serve --no-cache --config .webpack/webpack.pwa.js", - "dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack serve --config .webpack/webpack.pwa.js", - "dev:orthanc:no:cache": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack serve --no-cache --config .webpack/webpack.pwa.js", + "dev:orthanc": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker-nginx-orthanc.js webpack serve --config .webpack/webpack.pwa.js", + "dev:orthanc:no:cache": "cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker-nginx-orthanc.js webpack serve --no-cache --config .webpack/webpack.pwa.js", "dev:dcm4chee": "cross-env NODE_ENV=development APP_CONFIG=config/local_dcm4chee.js webpack serve --config .webpack/webpack.pwa.js", "dev:static": "cross-env NODE_ENV=development APP_CONFIG=config/local_static.js webpack serve --config .webpack/webpack.pwa.js", "dev:viewer": "yarn run dev", @@ -88,6 +88,7 @@ "i18next-browser-languagedetector": "^3.0.1", "lodash.isequal": "4.5.0", "oidc-client": "1.11.5", + "oidc-client-ts": "^3.0.1", "prop-types": "^15.7.2", "query-string": "^6.12.1", "react": "^18.3.1", diff --git a/platform/app/public/config/docker_openresty-orthanc-keycloak.js b/platform/app/public/config/deprecated/docker_openresty-orthanc-keycloak.js similarity index 69% rename from platform/app/public/config/docker_openresty-orthanc-keycloak.js rename to platform/app/public/config/deprecated/docker_openresty-orthanc-keycloak.js index f3b86852c66..d93a853d148 100644 --- a/platform/app/public/config/docker_openresty-orthanc-keycloak.js +++ b/platform/app/public/config/deprecated/docker_openresty-orthanc-keycloak.js @@ -36,19 +36,4 @@ window.config = { }, ], // This is an array, but we'll only use the first entry for now - oidc: [ - { - // ~ REQUIRED - // Authorization Server URL - authority: 'http://127.0.0.1/auth/realms/ohif', - client_id: 'ohif-viewer', - redirect_uri: 'http://127.0.0.1/callback', // `OHIFStandaloneViewer.js` - // "Authorization Code Flow" - // Resource: https://medium.com/@darutk/diagrams-of-all-the-openid-connect-flows-6968e3990660 - response_type: 'code', - scope: 'openid', // email profile openid - // ~ OPTIONAL - post_logout_redirect_uri: '/logout-redirect.html', - }, - ], }; diff --git a/platform/app/public/config/docker_openresty-orthanc.js b/platform/app/public/config/deprecated/docker_openresty-orthanc.js similarity index 100% rename from platform/app/public/config/docker_openresty-orthanc.js rename to platform/app/public/config/deprecated/docker_openresty-orthanc.js diff --git a/platform/app/public/config/docker-nginx-dcm4chee-keycloak.js b/platform/app/public/config/docker-nginx-dcm4chee-keycloak.js new file mode 100644 index 00000000000..163703b7a3e --- /dev/null +++ b/platform/app/public/config/docker-nginx-dcm4chee-keycloak.js @@ -0,0 +1,35 @@ +/** @type {AppTypes.Config} */ +window.config = { + routerBasename: '/ohif-viewer/', + showStudyList: true, + customizationService: { + dicomUploadComponent: + '@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent', + }, + extensions: [], + modes: [], + // below flag is for performance reasons, but it might not work for all servers + showWarningMessageForCrossOrigin: true, + showCPUFallbackMessage: true, + showLoadingIndicator: true, + strictZSpacingForVolumeViewport: true, + defaultDataSourceName: 'dicomweb', + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + friendlyName: 'Dcm4chee Server', + name: 'Dcm4chee', + wadoUriRoot: 'http://127.0.0.1/pacs', + qidoRoot: 'http://127.0.0.1/pacs', + wadoRoot: 'http://127.0.0.1/pacs', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, + }, + ], +}; diff --git a/platform/app/public/config/docker-nginx-dcm4chee.js b/platform/app/public/config/docker-nginx-dcm4chee.js new file mode 100644 index 00000000000..6d7e9fc473b --- /dev/null +++ b/platform/app/public/config/docker-nginx-dcm4chee.js @@ -0,0 +1,35 @@ +/** @type {AppTypes.Config} */ +window.config = { + routerBasename: '/', + showStudyList: true, + customizationService: { + dicomUploadComponent: + '@ohif/extension-cornerstone.customizationModule.cornerstoneDicomUploadComponent', + }, + extensions: [], + modes: [], + // below flag is for performance reasons, but it might not work for all servers + showWarningMessageForCrossOrigin: true, + showCPUFallbackMessage: true, + showLoadingIndicator: true, + strictZSpacingForVolumeViewport: true, + defaultDataSourceName: 'dicomweb', + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + friendlyName: 'Dcm4chee Server', + name: 'Dcm4chee', + wadoUriRoot: '/dcm4chee-arc/aets/DCM4CHEE/wado', + qidoRoot: '/dcm4chee-arc/aets/DCM4CHEE/rs', + wadoRoot: '/dcm4chee-arc/aets/DCM4CHEE/rs', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + dicomUploadEnabled: true, + omitQuotationForMultipartRequest: true, + }, + }, + ], +}; diff --git a/platform/app/public/config/docker-nginx-orthanc-keycloak.js b/platform/app/public/config/docker-nginx-orthanc-keycloak.js new file mode 100644 index 00000000000..3a8b60a2c45 --- /dev/null +++ b/platform/app/public/config/docker-nginx-orthanc-keycloak.js @@ -0,0 +1,52 @@ +/** @type {AppTypes.Config} */ +window.config = { + routerBasename: '/ohif-viewer', + extensions: [], + modes: [], + customizationService: {}, + showStudyList: true, + maxNumberOfWebWorkers: 3, + showWarningMessageForCrossOrigin: true, + showCPUFallbackMessage: true, + showLoadingIndicator: true, + strictZSpacingForVolumeViewport: true, + groupEnabledModesFirst: true, + maxNumRequests: { + interaction: 100, + thumbnail: 75, + prefetch: 25, + }, + defaultDataSourceName: 'dicomweb', + dataSources: [ + { + namespace: '@ohif/extension-default.dataSourcesModule.dicomweb', + sourceName: 'dicomweb', + configuration: { + friendlyName: 'Local Orthanc', + name: 'Orthanc', + wadoUriRoot: 'http://127.0.0.1/pacs', + qidoRoot: 'http://127.0.0.1/pacs', + wadoRoot: 'http://127.0.0.1/pacs', + qidoSupportsIncludeField: false, + imageRendering: 'wadors', + thumbnailRendering: 'wadors', + enableStudyLazyLoad: true, + supportsFuzzyMatching: false, + supportsWildcard: true, + staticWado: true, + singlepart: 'bulkdata,video', + // whether the data source should use retrieveBulkData to grab metadata, + // and in case of relative path, what would it be relative to, options + // are in the series level or study level (some servers like series some study) + bulkDataURI: { + enabled: true, + }, + omitQuotationForMultipartRequest: true, + }, + }, + ], + httpErrorHandler: error => { + console.warn(error.status); + console.warn('test, navigate to https://ohif.org/'); + }, +}; diff --git a/platform/app/public/config/docker_nginx-orthanc.js b/platform/app/public/config/docker-nginx-orthanc.js similarity index 95% rename from platform/app/public/config/docker_nginx-orthanc.js rename to platform/app/public/config/docker-nginx-orthanc.js index 1d7c12a5d50..7a480ea89ec 100644 --- a/platform/app/public/config/docker_nginx-orthanc.js +++ b/platform/app/public/config/docker-nginx-orthanc.js @@ -29,8 +29,8 @@ window.config = { friendlyName: 'Orthanc Server', name: 'Orthanc', wadoUriRoot: '/wado', - qidoRoot: '/dicom-web', - wadoRoot: '/dicom-web', + qidoRoot: '/pacs/dicom-web', + wadoRoot: '/pacs/dicom-web', qidoSupportsIncludeField: false, imageRendering: 'wadors', thumbnailRendering: 'wadors', diff --git a/platform/app/src/App.tsx b/platform/app/src/App.tsx index 463f4dd5803..13b6fecc4e5 100644 --- a/platform/app/src/App.tsx +++ b/platform/app/src/App.tsx @@ -24,7 +24,7 @@ import { UserAuthenticationProvider, ToolboxProvider, } from '@ohif/ui'; -import { ThemeWrapper as ThemeWrapperNext, NotificationProvider } from '@ohif/ui-next'; +import { ThemeWrapper as ThemeWrapperNext } from '@ohif/ui-next'; // Viewer Project // TODO: Should this influence study list? import { AppConfigProvider } from '@state'; diff --git a/platform/app/src/routes/CallbackPage.tsx b/platform/app/src/routes/CallbackPage.tsx index 74474552fb6..bf938ecaaef 100644 --- a/platform/app/src/routes/CallbackPage.tsx +++ b/platform/app/src/routes/CallbackPage.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; function CallbackPage({ userManager, onRedirectSuccess }) { @@ -6,10 +6,12 @@ function CallbackPage({ userManager, onRedirectSuccess }) { throw new Error(error); }; - userManager - .signinRedirectCallback() - .then(user => onRedirectSuccess(user)) - .catch(error => onRedirectError(error)); + useEffect(() => { + userManager + .signinRedirectCallback() + .then(user => onRedirectSuccess(user)) + .catch(error => onRedirectError(error)); + }, [userManager, onRedirectSuccess]); return null; } diff --git a/platform/app/src/routes/PrivateRoute.tsx b/platform/app/src/routes/PrivateRoute.tsx index 34bd3955b7e..0e9f9279e4f 100644 --- a/platform/app/src/routes/PrivateRoute.tsx +++ b/platform/app/src/routes/PrivateRoute.tsx @@ -1,4 +1,3 @@ -import React from 'react'; import { useUserAuthentication } from '@ohif/ui'; export const PrivateRoute = ({ children, handleUnauthenticated }) => { diff --git a/platform/app/src/routes/buildModeRoutes.tsx b/platform/app/src/routes/buildModeRoutes.tsx index cda7b6b802e..77dfde04e67 100644 --- a/platform/app/src/routes/buildModeRoutes.tsx +++ b/platform/app/src/routes/buildModeRoutes.tsx @@ -81,7 +81,7 @@ export default function buildModeRoutes({ routes.push({ path, children, - private: true, // todo: all mode routes are private for now + private: true, }); }); diff --git a/platform/app/src/routes/index.tsx b/platform/app/src/routes/index.tsx index c4101c76cfe..d67e8f612f7 100644 --- a/platform/app/src/routes/index.tsx +++ b/platform/app/src/routes/index.tsx @@ -138,18 +138,20 @@ const createRoutes = ({ const { userAuthenticationService } = servicesManager.services; - // Note: PrivateRoutes in react-router-dom 6.x should be defined within - // a Route element + // All routes are private by default and then we let the user auth service + // to check if it is enabled or not + // Todo: I think we can remove the second public return below return ( {allRoutes.map((route, i) => { return route.private === true ? ( userAuthenticationService.handleUnauthenticated()}> + userAuthenticationService.handleUnauthenticated()} + > } diff --git a/platform/app/src/utils/OpenIdConnectRoutes.tsx b/platform/app/src/utils/OpenIdConnectRoutes.tsx index 7c83efd2a04..de044161a8e 100644 --- a/platform/app/src/utils/OpenIdConnectRoutes.tsx +++ b/platform/app/src/utils/OpenIdConnectRoutes.tsx @@ -3,7 +3,8 @@ import { useEffect } from 'react'; import { Route, Routes, useLocation, useNavigate } from 'react-router'; import CallbackPage from '../routes/CallbackPage'; import SignoutCallbackComponent from '../routes/SignoutCallbackComponent'; -import getUserManagerForOpenIdConnectClient from './getUserManagerForOpenIdConnectClient.js'; +import LegacyClient from './legacyOIDCClient'; +import NextClient from './nextOIDCClient'; function _isAbsoluteUrl(url) { return url.includes('http://') || url.includes('https://'); @@ -43,7 +44,9 @@ const initUserManager = (oidc, routerBasename) => { post_logout_redirect_uri: _makeAbsoluteIfNecessary(post_logout_redirect_uri, baseUri), }); - return getUserManagerForOpenIdConnectClient(openIdConnectConfiguration); + const client = firstOpenIdClient.useAuthorizationCodeFlow ? NextClient: LegacyClient + + return client(openIdConnectConfiguration); }; function LogoutComponent(props) { @@ -147,12 +150,18 @@ function OpenIdConnectRoutes({ oidc, routerBasename, userAuthenticationService } const location = useLocation(); const { pathname, search } = location; - const redirect_uri = new URL(userManager.settings._redirect_uri).pathname.replace( + const redirectURI = userManager.settings._redirect_uri ?? userManager.settings.redirect_uri; + const silentRedirectURI = + userManager.settings._silent_redirect_uri ?? userManager.settings.silent_redirect_uri; + const postLogoutRedirectURI = + userManager.settings._post_logout_redirect_uri ?? userManager.settings.post_logout_redirect_uri; + + const redirect_uri = new URL(redirectURI).pathname.replace( routerBasename !== '/' ? routerBasename : '', '' ); - const silent_refresh_uri = new URL(userManager.settings._silent_redirect_uri).pathname; //.replace(routerBasename,'') - const post_logout_redirect_uri = new URL(userManager.settings._post_logout_redirect_uri).pathname; //.replace(routerBasename,''); + const silent_refresh_uri = new URL(silentRedirectURI).pathname; //.replace(routerBasename,'') + const post_logout_redirect_uri = new URL(postLogoutRedirectURI).pathname; //.replace(routerBasename,''); // const pathnameRelative = pathname.replace(routerBasename,''); diff --git a/platform/app/src/utils/getUserManagerForOpenIdConnectClient.js b/platform/app/src/utils/legacyOIDCClient.ts similarity index 100% rename from platform/app/src/utils/getUserManagerForOpenIdConnectClient.js rename to platform/app/src/utils/legacyOIDCClient.ts diff --git a/platform/app/src/utils/nextOIDCClient.ts b/platform/app/src/utils/nextOIDCClient.ts new file mode 100644 index 00000000000..24213698e0c --- /dev/null +++ b/platform/app/src/utils/nextOIDCClient.ts @@ -0,0 +1,39 @@ +import { UserManager } from 'oidc-client-ts'; + +/** + * Creates a userManager from oidcSettings + * LINK: https://github.com/IdentityModel/oidc-client-js/wiki#configuration + * + * @param {Object} oidcSettings + * @param {string} oidcSettings.authServerUrl, + * @param {string} oidcSettings.clientId, + * @param {string} oidcSettings.authRedirectUri, + * @param {string} oidcSettings.postLogoutRedirectUri, + * @param {string} oidcSettings.responseType, + * @param {string} oidcSettings.extraQueryParams, + */ +export default function getUserManagerForOpenIdConnectClient(oidcSettings) { + if (!oidcSettings) { + return; + } + + if (!oidcSettings.authority || !oidcSettings.client_id || !oidcSettings.redirect_uri) { + console.error('Missing required oidc settings: authority, client_id, redirect_uri'); + return; + } + + const settings = { + ...oidcSettings, + // The next client always use the code flow with PKCE + response_type: 'code', + revokeTokensOnSignout: oidcSettings.revokeAccessTokenOnSignout ?? true, + filterProtocolClaims: true, + loadUserInfo: true, + // the followings are default values in the lib so no need to set them + // automaticSilentRenew: true, + }; + + const userManager = new UserManager(settings); + + return userManager; +} diff --git a/platform/docs/docs/assets/img/customizable-overlay.jpeg b/platform/docs/docs/assets/img/customizable-overlay.jpeg new file mode 100644 index 00000000000..e166f244068 Binary files /dev/null and b/platform/docs/docs/assets/img/customizable-overlay.jpeg differ diff --git a/platform/docs/docs/assets/img/customizable-overlay.png b/platform/docs/docs/assets/img/customizable-overlay.png deleted file mode 100644 index 05ca4d275be..00000000000 Binary files a/platform/docs/docs/assets/img/customizable-overlay.png and /dev/null differ diff --git a/platform/docs/docs/assets/img/dcm4chee-upload.gif b/platform/docs/docs/assets/img/dcm4chee-upload.gif new file mode 100644 index 00000000000..e0e94f1030e Binary files /dev/null and b/platform/docs/docs/assets/img/dcm4chee-upload.gif differ diff --git a/platform/docs/docs/assets/img/demo-microscopy.png b/platform/docs/docs/assets/img/demo-microscopy.png deleted file mode 100644 index ad548012610..00000000000 Binary files a/platform/docs/docs/assets/img/demo-microscopy.png and /dev/null differ diff --git a/platform/docs/docs/assets/img/large-pt-ct.jpeg b/platform/docs/docs/assets/img/large-pt-ct.jpeg new file mode 100644 index 00000000000..9999e247e7b Binary files /dev/null and b/platform/docs/docs/assets/img/large-pt-ct.jpeg differ diff --git a/platform/docs/docs/assets/img/large-pt-ct.png b/platform/docs/docs/assets/img/large-pt-ct.png deleted file mode 100644 index 16c16bcbae7..00000000000 Binary files a/platform/docs/docs/assets/img/large-pt-ct.png and /dev/null differ diff --git a/platform/docs/docs/assets/img/nginx-image-archive.png b/platform/docs/docs/assets/img/nginx-image-archive.png index bd75479652a..f1ac06119f5 100644 Binary files a/platform/docs/docs/assets/img/nginx-image-archive.png and b/platform/docs/docs/assets/img/nginx-image-archive.png differ diff --git a/platform/docs/docs/assets/img/ohif-pacs-keycloak.png b/platform/docs/docs/assets/img/ohif-pacs-keycloak.png new file mode 100644 index 00000000000..e95d2af3558 Binary files /dev/null and b/platform/docs/docs/assets/img/ohif-pacs-keycloak.png differ diff --git a/platform/docs/docs/configuration/dataSources/dicom-web.md b/platform/docs/docs/configuration/dataSources/dicom-web.md index c5eb7bb9eb3..8e2d492501e 100644 --- a/platform/docs/docs/configuration/dataSources/dicom-web.md +++ b/platform/docs/docs/configuration/dataSources/dicom-web.md @@ -102,13 +102,13 @@ is running the `dev:orthanc` script in our project's `package.json` (inside `platform/app`). That script is: ```js -cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w +cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker-nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w ``` - `cross-env` sets three environment variables - PROXY_TARGET: `/dicom-web` - PROXY_DOMAIN: `http://localhost:8042` - - APP_CONFIG: `config/docker_nginx-orthanc.js` + - APP_CONFIG: `config/docker-nginx-orthanc.js` - `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration file. It will watch for changes and update as we develop. diff --git a/platform/docs/docs/deployment/authorization.md b/platform/docs/docs/deployment/authorization.md index b80a7c96259..b3589b1a97d 100644 --- a/platform/docs/docs/deployment/authorization.md +++ b/platform/docs/docs/deployment/authorization.md @@ -1,32 +1,29 @@ --- sidebar_position: 6 -sidebar_label: Authorization +sidebar_label: Auth --- -# Authorization +# Authorization and Authentication The OHIF Viewer can be configured to work with authorization servers that support one or more of the OpenID-Connect authorization flows. The Viewer finds it's OpenID-Connect settings on the oidc configuration key. You can set these values in your configuration files. For instance you can take a look at our `google.js` configuration file. ```js oidc: [ - { - // ~ REQUIRED - // Authorization Server URL - authority: 'https://accounts.google.com', - client_id: - '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', - redirect_uri: '/callback', - response_type: 'id_token token', - scope: - 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid - // ~ OPTIONAL - post_logout_redirect_uri: '/logout-redirect.html', - revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=', - automaticSilentRenew: true, - revokeAccessTokenOnSignout: true, - }, - ], + { + // ~ REQUIRED + authority: 'https://accounts.google.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + redirect_uri: '/callback', + response_type: 'id_token token', + scope: 'email profile openid https://www.googleapis.com/auth/cloudplatformprojects.readonly https://www.googleapis.com/auth/cloud-healthcare', // email profile openid + // ~ OPTIONAL + post_logout_redirect_uri: '/logout-redirect.html', + revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=', + automaticSilentRenew: true, + revokeAccessTokenOnSignout: true, + }, +], ``` You need to provide the following information: @@ -53,8 +50,7 @@ const userAuthenticationService = servicesManager.services.userAuthenticationSer Then the userAuthenticationService will inject the token as Authorization header in the requests that are sent to the server (both metadata and pixelData). - -## Token based authentication +## Token based authentication in URL Sometimes (although not recommended), some servers like to send the token in the query string. In this case, the viewer will automatically grab the token from the query string and add it to the userAuthenticationService and remove it from the query string (to prevent it from being logged in the console @@ -65,3 +61,35 @@ and example would be ```js http://localhost:3000/viewer?StudyInstanceUIDs=1.2.3.4.5.6.6.7&token=e123125jsdfahsdf ``` + + + +## Implicit Flow vs Authorization Code Flow + +The Viewer supports both the Implicit Flow and the Authorization Code Flow. The Implicit Flow is the default currently, as it is easier to set up and use. However, you can opt for better security by using the Authorization Code Flow. To do so, add `useAuthorizationCodeFlow` to the configuration and change the `response_type` from `id_token token` to `code`. + +Read more about Implicit Flow vs Authorization Code Flow [here](https://documentation.openiddict.com/guides/choosing-the-right-flow.html#:~:text=The%20implicit%20flow%20is%20similar,when%20using%20response_mode%3Dform_post%20) and [here](https://medium.com/@alysachan830/the-basics-of-oauth-2-0-authorization-code-implicit-flow-state-and-pkce-ed95d3478e1c) + +```js +oidc: [ + { + authority: 'https://accounts.google.com', + client_id: '723928408739-k9k9r3i44j32rhu69vlnibipmmk9i57p.apps.googleusercontent.com', + redirect_uri: '/callback', + scope: 'email profile openid', + post_logout_redirect_uri: '/logout-redirect.html', + revoke_uri: 'https://accounts.google.com/o/oauth2/revoke?token=', + revokeAccessTokenOnSignout: true, + automaticSilentRenew: true, + // CHANGE THESE ***************************** + response_type: 'code', + useAuthorizationCodeFlow: true, + }, +], +``` + +In fact, since browsers are blocking third-party cookies, the Implicit Flow will cease functioning in the future (not specific to OHIF). Read more [here](https://support.okta.com/help/s/article/FAQ-How-Blocking-Third-Party-Cookies-Can-Potentially-Impact-Your-Okta-Environment?language=en_US). It is recommended to use the Authorization Code Flow and begin migrating to it. + +:::note +For the Authorization Code Flow, when authenticating against Google, you must add the `client_secret` to the configuration as well. Unfortunately, this seems to occur only with Google. +::: diff --git a/platform/docs/docs/deployment/google-cloud-healthcare.md b/platform/docs/docs/deployment/google-cloud-healthcare.md index 73b37855421..54df8bb029e 100644 --- a/platform/docs/docs/deployment/google-cloud-healthcare.md +++ b/platform/docs/docs/deployment/google-cloud-healthcare.md @@ -1,5 +1,5 @@ --- -sidebar_position: 10 +sidebar_position: 9 --- # Google Cloud Healthcare diff --git a/platform/docs/docs/deployment/nginx--image-archive.md b/platform/docs/docs/deployment/nginx--image-archive.md index 8d865879805..b1a2fd34b89 100644 --- a/platform/docs/docs/deployment/nginx--image-archive.md +++ b/platform/docs/docs/deployment/nginx--image-archive.md @@ -1,11 +1,9 @@ --- -sidebar_position: 9 +sidebar_position: 10 --- # Nginx + Image Archive -> DISCLAIMER! We make no claims or guarantees of this approach's security. If in -> doubt, enlist the help of an expert and conduct proper audits. At a certain point, you may want others to have access to your instance of the OHIF Viewer and its medical imaging data. This post covers one of many potential @@ -16,19 +14,14 @@ Do not use this recipe to host sensitive medical data on the open web. Depending on your company's policies, this may be an appropriate setup on an internal network when protected with a server's basic authentication. -## Overview -Our two biggest hurdles when hosting our image archive and web client are: - -- Risks related to exposing our PACS to the network -- Cross-Origin Resource Sharing (CORS) requests ### Handling Web Requests We mitigate our first issue by allowing [Nginx][nginx] to handle incoming web requests. Nginx is open source software for web serving, reverse proxying, caching, and more. It's designed for maximum performance and stability -- -allowing us to more reliably serve content than Orthanc's built-in server can. +allowing us to more reliably serve content. More specifically, we accomplish this by using a [`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve @@ -38,35 +31,16 @@ resources from our image archive (Orthanc), and when accessing its web admin. > of a client from one or more servers. These resources are then returned to the > client, appearing as if they originated from the proxy server itself. -### CORS Issues - -Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to -tell a browser which web applications have permission to access selected -resources from a server at a different origin (domain, protocol, port). IE. By -default, a Web App located at `http://my-website.com` can't access resources -hosted at `http://not-my-website.com` - -We can solve this one of two ways: - -1. Have our Image Archive located at the same domain as our Web App -2. Add appropriate `Access-Control-Allow-*` HTTP headers -**This solution uses the first approach.** - -You can read more about CORS in this Medium article: [Understanding -CORS][understanding-cors] - -### Diagram This setup allows us to create a setup similar to the one pictured below: ![nginX](../assets/img/nginx-image-archive.png) - -- All web requests are routed through `nginx` on our `OpenResty` image -- `/pacs` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints -- `/pacs-admin` is a reverse proxy for `orthanc`'s Web Admin +- All web requests are routed through `nginx` image +- `/pacs/dicom-web` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints, which handles DICOM requests +- `/pacs` is a reverse proxy for `orthanc`'s Web Admin, which is the UI for managing studies - All static resources for OHIF Viewer are served up by `nginx` when a matching route for that resource is requested @@ -83,36 +57,40 @@ in command prompt or terminal_ ### Setup -- Navigate to `app` folder inside `platform` -- then: `cd .recipes/OpenResty-Orthanc` +- `cd platform/app/.recipes/Nginx-Orthanc` - run: `docker-compose up --build` -- Navigate to `127.0.0.1` for the viewer -- Navigate to `127.0.0.1/pacs-admin` for uploading studies via the UI, or send studies via DIMSE C-STORE to `ORTHANC@127.0.0.1:4242` (hint: you can use utilites like dcm4che's `storescu` to send studies in bulk via the command line) +- Navigate to `127.0.0.1` for the viewer (at first there is no study) +- Navigate to `127.0.0.1/pacs` for uploading studies via the UI, or send studies via DIMSE C-STORE to `ORTHANC@127.0.0.1:4242` (hint: you can use utilizes like dcm4che's `storescu` to send studies in bulk via the command line) +:::note +For subsequent runs, use `docker-compose up -d` to start the services without rebuilding the images. However, ensure you rebuild the images if you make changes to the Dockerfile. If you modify the configurations in the `nginx.conf` or `orthanc.json` files, you can restart the services by running `docker-compose up`, as these files are mounted as volumes. +``` +Inside docker compose file you see the following volumes mounted: -You can see the overview of the mentioned steps: +volumes: + # Nginx config + - ./config/nginx.conf:/etc/nginx/nginx.conf + # Logs + - ./logs/nginx:/var/logs/nginx +``` +::: +You can see the overview of the mentioned steps: -
- -
- ### Troubleshooting @@ -154,10 +132,10 @@ configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution: -`ENV APP_CONFIG=config/docker_openresty-orthanc.js` +`ENV APP_CONFIG=config/docker-nginx-orthanc.js` You can find the configuration we're using here: -`/public/config/docker_openresty-orthanc.js` +`/public/config/docker-nginx-orthanc.js` To rebuild the `webapp` image created by our `dockerfile` after updating the Viewer's configuration, you can run: @@ -167,78 +145,49 @@ Viewer's configuration, you can run: #### Other -All other files are found in: `/docker/OpenResty-Orthanc/` +All other files are found in: `/docker/Nginx-Orthanc/` | Service | Configuration | Docs | | ----------------- | --------------------------------- | ------------------------------------------- | | OHIF Viewer | [dockerfile][dockerfile] | You're reading them now! | -| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] | +| Nginx | [`/nginx.conf`][config-nginx] | | | Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] | ## Next Steps -### Deploying to Production - -While these configuration and docker-compose files model an environment suitable -for production, they are not easy to deploy "as is". You can either: - -- Manually recreate this environment and deploy built application files **OR** -- Deploy to a cloud kubernetes provider like - [Digital Ocean](https://www.digitalocean.com/products/kubernetes/) **OR** - - [See a full list of cloud providers here](https://landscape.cncf.io/category=cloud&format=card-mode&grouping=category) -- Find and follow your preferred provider's guide on setting up - [swarms and stacks](https://docs.docker.com/get-started/) +### OHIF + Dcm4chee -### Adding SSL +You can follow the similar steps above to run OHIF Viewer with Dcm4chee PACS. -Adding SSL registration and renewal for your domain with Let's Encrypt that -terminates at Nginx is an incredibly important step toward securing your data. -Here are some resources, specific to this setup, that may be helpful: +The recipe for this setup can be found at `platform/app/.recipes/Nginx-Dcm4chee`. -- [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl) -- [Let's Encrypt + Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) -While we terminate SSL at Nginx, it may be worth using self-signed certificates -for communication between services. +The routes are as follows: +- `127.0.0.1` for the OHIF viewer +- `127.0.0.1/pacs` for the Dcm4chee UI -- [SSL Termination for TCP Upstream Servers](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) +:::info +For uploading studies, you can see the following gif for the steps: -### Use PostgresSQL w/ Orthanc +![alt text](../assets/img/dcm4chee-upload.gif) -Orthanc can handle a large amount of data and requests, but if you find that -requests start to slow as you add more and more studies, you may want to -configure your Orthanc instance to use PostgresSQL. Instructions on how to do -that can be found in the -[`Orthanc Server Book`](http://book.orthanc-server.com/users/docker.html), under -"PostgreSQL and Orthanc inside Docker" - -### Improving This Guide +::: -Here are some improvements this guide would benefit from, and that we would be -more than happy to accept Pull Requests for: +### Deploying to Production -- SSL Support -- Complete configuration with `.env` file (or something similar) -- Any security issues -- One-click deploy to a cloud provider +While you can deploy this solution to production, there is one main caveat: every user can access the app and the patient portal without any authentication. In the next step, we will add authentication with Keycloak to secure the app. -## Resources -### Misc. Helpful Commands -_Check if `nginx.conf` is valid:_ -```bash -docker run --rm -t -a stdout --name my-openresty -v $PWD/config/:/usr/local/openresty/nginx/conf/:ro openresty/openresty:alpine-fat openresty -c /usr/local/openresty/nginx/conf/nginx.conf -t -``` +### Improving This Guide -_Interact w/ running container:_ +Here are some improvements this guide would benefit from, and that we would be +more than happy to accept Pull Requests for: -`docker exec -it CONTAINER_NAME bash` +- Add Docker caching for faster builds -_List running containers:_ -`docker ps` ### Referenced Articles @@ -246,8 +195,6 @@ For more documentation on the software we've chosen to use, you may find the following resources helpful: - [Orthanc for Docker](http://book.orthanc-server.com/users/docker.html) -- [OpenResty Guide](http://www.staticshin.com/programming/definitely-an-open-resty-guide/) -- [Lua Ngx API](https://openresty-reference.readthedocs.io/en/latest/Lua_Nginx_API/) For a different take on this setup, check out the repositories our community members put together: diff --git a/platform/docs/docs/deployment/user-account-control.md b/platform/docs/docs/deployment/user-account-control.md index 5447ce282cf..87f0235bead 100644 --- a/platform/docs/docs/deployment/user-account-control.md +++ b/platform/docs/docs/deployment/user-account-control.md @@ -3,8 +3,10 @@ sidebar_position: 11 --- # User Account Control -> DISCLAIMER! We make no claims or guarantees of this approach's security. If in -> doubt, enlist the help of an expert and conduct proper audits. + +:::danger +DISCLAIMER: We make no claims or guarantees regarding the security of this approach. If you have any doubts, please consult an expert and conduct thorough audits. +::: Making a viewer and its medical imaging data accessible on the open web can provide a lot of benefits, but requires additional security to make sure @@ -18,7 +20,7 @@ sensitive data. ## Overview This guide builds on top of our -[Nginx + Image Archive guide](/deployment/recipes/nginx--image-archive.md), +[Nginx + Image Archive guide](./nginx--image-archive.md), wherein we used a [`reverse proxy`](https://en.wikipedia.org/wiki/Reverse_proxy) to retrieve resources from our image archive (Orthanc). @@ -35,20 +37,43 @@ applications and services with little to no code. We improve upon our This setup allows us to create a setup similar to the one pictured below: -![userControlFlow](../assets/img/user-access-control-request-flow.png) +![userControlFlow](../assets/img/ohif-pacs-keycloak.png) + + + +**Nginx:** + +- Acts as a reverse proxy server that handles incoming requests to the domain (mydomain.com:80) and forwards them to the appropriate backend services. +- It also ensures that all requests go through the OAuth2 Proxy for authentication. + + +**OAuth2 Proxy:** + +- Serves as an intermediary that authenticates users via OAuth2. +- Works in conjunction with Keycloak to manage user sessions and authentication tokens. +- Once the user is authenticated, it allows access to specific routes (/ohif-viewer, /pacs, /pacs-admin). + +**Keycloak:** + +- An open-source identity and access management solution. +- Manages user identities, including authentication and authorization. +- Communicates with the OAuth2 Proxy to validate user credentials and provide tokens for authenticated sessions. + +**OHIF Viewer:** + +- Hosted under the route /ohif-viewer, which serves the static assets of the OHIF Viewer. +**Orthanc/DCM4chee:** +- PACS (Picture Archiving and Communication System) for managing medical imaging data. +Exposes two routes: +- /pacs: Accesses the DICOM web services. +- /pacs-admin: Provides administrative and explorer interfaces. -- All web requests are routed through `nginx` on our `OpenResty` image -- `/pacs` is a reverse proxy for `orthanc`'s `DICOM Web` endpoints - - Requires valid `Authorization: Bearer ` header -- `/pacs-admin` is a reverse proxy for `orthanc`'s Web Admin -- `/auth` is a reverse proxy for `keycloak` -- All static resources for OHIF Viewer are unprotected and accessible. We have - application logic that will redirect unauthenticated users to the appropriate - `keycloak` login screen. -## Getting Started + +## Getting Started - Orthanc + ### Requirements @@ -59,46 +84,336 @@ This setup allows us to create a setup similar to the one pictured below: _Not sure if you have `docker` installed already? Try running `docker --version` in command prompt or terminal_ -### Setup +### Setup 1 - Trying Locally + +Navigate to the Orthanc Keycloak configuration directory: + +`cd platform\app\.recipes\Nginx-Orthanc-Keycloak` + +Due to the increased complexity of this setup, we've introduced a magic word `YOUR_DOMAIN`. Replace this word with your project IP address to follow along more easily. + +Since we are running this locally, we will use `127.0.0.1` as our IP address. + +In the `docker-compose.yml` file, replace `YOUR_DOMAIN` with `127.0.0.1`. + +In the Keycloak service: + + +Before: + +``` +KC_HOSTNAME_ADMIN_URL: http://YOUR_DOMAIN/keycloak/ +KC_HOSTNAME_URL: http://YOUR_DOMAIN/keycloak/ +``` + + +After + +``` +KC_HOSTNAME_ADMIN_URL: http://127.0.0.1/keycloak/ +KC_HOSTNAME_URL: http://127.0.0.1/keycloak/ +``` + +In the Keycloak healthcheck, replace `YOUR_DOMAIN` with `localhost`. + +In the Nginx config, change: + +``` +server_name YOUR_DOMAIN; +``` + +to: + +``` +server_name 127.0.0.1; +``` + +Since we're not using SSL, remove the following lines from the Nginx config file and create one server instead of two: + +Before (two servers one for http and one for https): + +``` +server { + listen 80; + server_name YOUR_DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name YOUR_DOMAIN; + + ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem; + + root /var/www/html; +``` + +After (merging both servers into one only http server): + +``` +server { + listen 80; + server_name 127.0.0.1; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + root /var/www/html; +``` + +In OAuth2-proxy configuration at `oauth2-proxy.cfg` + +Before: + +``` +redirect_url="http://YOUR_DOMAIN/oauth2/callback" +oidc_issuer_url="http://YOUR_DOMAIN/keycloak/realms/ohif" +``` + +After: + +``` +redirect_url="http://127.0.0.1/oauth2/callback" +oidc_issuer_url="http://127.0.0.1/keycloak/realms/ohif" +``` + +Finally, in the docker-nginx-orthanc-keycloak config file that lives in `platform/app/public/config/docker-nginx-orthanc-keycloak.js`, replace `YOUR_DOMAIN` with + +Before: + +``` +wadoUriRoot: 'http://YOUR_DOMAIN/pacs', +qidoRoot: 'http://YOUR_DOMAIN/pacs', +wadoRoot: 'http://YOUR_DOMAIN/pacs', +``` + +After: + +``` +wadoUriRoot: 'http://127.0.0.1/pacs', +qidoRoot: 'http://127.0.0.1/pacs', +wadoRoot: 'http://127.0.0.1/pacs', +``` + +:::note +This is the config that is used inside the dockerfile to build the viewer, look at dockerfile + +`ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js` +::: + +Run the following command to start the services: + +``` +docker-compose up --build +``` + + +You can watch the following video, which will guide you through the process of setting up Orthanc with keycloak and OHIF locally. + +We have set up two predefined users in Keycloak: + +- `user: admin password: admin` - Has access to keycloak portal for managing users and clients +- `user: viewer password: viewer` - Has access to the OHIF Viewer but not the pacs-admin +- `user: pacsadmin password: pacsadmin` - Has access to both the pacs-admin for uploading and the OHIF Viewer + +You can navigate to: + +- `http://127.0.0.1` - This will redirect you to `http://127.0.0.1/ohif-viewer`, prompting you to log in with Keycloak using either user +- `http://127.0.0.1/pacs-admin` - Only the `pacsadmin` user can access this route, while the `viewer` user cannot +- + +
+ +
+ + +### Step 2 - Trying via a Server + +Now that you have successfully set up Orthanc with Keycloak and OHIF locally, you can deploy it to a server. While you can rent a server from any provider, this tutorial will demonstrate the process using Linode as an example. -_Spin Things Up_ +You can watch the following video, which will guide you through the process. -- Navigate to `platform\app\.recipes\OpenResty-Orthanc-Keycloak` in your shell -- Run `docker-compose up` +Some notes: -_Create Your First User_ +- Since this is a remote machine we need to clone the repo +- Typically a Linux machine, you need to download and install Docker on it +- Use the Visual Studio Code Remote SSH extension to connect to the server +- Use docker extension in Visual Studio Code to manage the containers +- The public IP address of the server now becomes the YOUR_DOMAIN and is used in the configuration files. -- Navigate to: `http://127.0.0.1/auth/admin` -- Sign in with: `admin`/`password` -- From the top left dropdown, select the `Ohif` realm -- From the left sidebar, under `Manage`, select `Users` -- Click `Add User` - - Username: `test` - - Email Verified: `ON` - - Click `Save` -- Click the `Credentials` Tab - - New Password: `test` - - Password Confirmation: `test` - - Temporary: `OFF` - - Click: `Reset Password` -- From the top right dropdown, select `Admin`, then `Sign Out` +Still we have not set up SSL, so we will use HTTP instead of HTTPS. -_Sign In_ +We should use the same one server configuration as we did locally for Nginx (but with the new server IP address) -- Navigate to `http://127.0.0.1/` -- Username: `test`, Password: `test` -- Click `Log In` +:::info +Don't forget to change the `docker-ngix-orthanc-keycloak.js` file to use the new server IP address. +::: -_Upload Your First Study_ +After you run `docker compose up --build` you can navigate to the server IP address and see the viewer will not work... -- Navigate to `http://127.0.0.1/pacs-admin` -- If you're not already logged in, use `test`/`test` -- From the top right, select "Upload" -- Click "Select files to upload..." (DICOM) -- Click "Start the upload" -- Navigate back to `http://127.0.0.1/` to view your studies in the Study List +We have encountered some strange issues with the Keycloak service not allowing non-HTTPS connections (around 10:00). To resolve this, we need to modify the Keycloak configuration to permit HTTPS. This requires accessing the container and making the necessary changes. -### Troubleshooting +After accessing the container shell + +``` +cd /opt/keycloak/bin + +./kcadm.sh config credentials --server http://localhost:8080 --realm master --user admin +./kcadm.sh update realms/master -s sslRequired=NONE +``` + +After we need to change some configurations in the Keycloak UI to enable the connection in the server + +Navigate to + +``` +http://IP_ADDRESS/keycloak +``` + +which will redirect you to the Keycloak login page + +0. login with the admin user `admin` and password `admin` +1. From the top left drop down menu, select `ohif` realm +2. Go to `Clients` and select `ohif_viewer` +3. In the `Access Settings` change all instances of `http://127.0.0.1` to `http://IP_ADDRESS` + 1. Root URL: `http://IP_ADDRESS` + 2. Home URL: `http://IP_ADDRESS` + 3. Valid Redirect URIs: `http://IP_ADDRESS/oauth2/callback` + 4. Valid post logout URIs: `*` + 5. Web Origins: `http://IP_ADDRESS` + 6. Admin URL: `http://IP_ADDRESS` + +Now if you navigate to the IP address it should work !! + + +
+ +
+ +### Step 3 - Adding SSL and Deploying to Production + +Now we'll add an SSL certificate to our server to enable HTTPS. We'll use Let's Encrypt to generate the SSL certificate. + +Let's Encrypt requires a domain name, so we'll use a free domain name service like DuckDNS (duckdns.org). Follow these steps: + +1. Visit https://www.duckdns.org/ and create an account. +2. Create a free domain name and point it to your server's IP address. + +You can watch a video guide for this process if needed. + +Replace `YOUR_DOMAIN` with your new domain name in the `docker-compose.yml` file and all other config files, as we did previously. + +Next, we'll add HTTPS support. Add the following lines to the Nginx config file: + +(Note: We'll have both HTTP and HTTPS servers, and the server IP will use HTTPS) +``` +server { + listen 80; + server_name https://IP_ADDRESS; + + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + server_name https://IP_ADDRESS; + + ssl_certificate /etc/letsencrypt/live/ohifviewer.duckdns.org/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ohifviewer.duckdns.org/privkey.pem; + + root /var/www/html; +``` + +Don't forget to replace `YOUR_DOMAIN` with the new domain name in the `docker-nginx-orthanc-keycloak.js` file. + +:::info +Remember to include `https://` when adding the domain name to the configurations. +::: + +Now, we need to add a certificate. Let's assume we have the domain name `hospital.duckdns.org` and the email we registered with DuckDNS is `your_email@example.com`. + +``` + docker run -it --rm --name certbot \ + -v ./config/letsencrypt:/etc/letsencrypt \ + -v ./config/certbot:/var/www/certbot \ + -p 80:80 \ + certbot/certbot certonly \ + --standalone \ + --preferred-challenges http \ + --email your_email@example.com \ + --agree-tos \ + --no-eff-email \ + -d hospital.duckdns.org +``` + +:::note +Replace "hospital.duckdns.org" with your domain name and update the email address accordingly. +::: + +:::warning +DuckDNS is suitable for testing and demonstration purposes only. For production environments, use a proper domain name and SSL certificate to ensure security. +::: + +If you follow these steps, you'll encounter the error `invalid parameter: redirect_uri` when attempting to log in to Keycloak. This occurs because the redirect URL isn't set up correctly in the Keycloak client configuration. To resolve this, we need to log in and adjust these settings. + +Navigate to: + +``` +http://IP_ADDRESS/keycloak +``` + +Log in using the admin credentials: +- Username: `admin` +- Password: `admin` + +Replace all IP addresses with the new domain name, using HTTPS. + +
+ +
+ + + + + + +## Getting Started - DCM4CHEE + + + + +You can follow the same steps as above to set up DCM4CHEE. The only difference is that you need to navigate to the correct directory. `platform\app\.recipes\Nginx-Dcm4chee-Keycloak` + +You can watch the following video, which will guide you through the process of setting up DCM4CHEE. + + +
+ +
+ + + +## Troubleshooting + + +_invalid parameter: redirect_uri_ + +This means the redirect URL isn't set up correctly in the Keycloak client configuration. To resolve this, log in to Keycloak and adjust the settings in the correct client (ohif_viewer) and correct realm (ohif). _Exit code 137_ @@ -116,14 +431,6 @@ Stop running all containers: - Win: `docker ps -a -q | ForEach { docker stop $_ }` - Linux: `docker stop $(docker ps -a -q)` -### Configuration - -After verifying that everything runs with default configuration values, you will -likely want to update: - -- The domain: `http://127.0.0.1` -- Set secure, non-default passwords -- Regenerate Keycloak Client Secrets #### OHIF Viewer @@ -132,10 +439,10 @@ configuration we use is set to a specific file when we build the viewer, and determined by the env variable: `APP_CONFIG`. You can see where we set its value in the `dockerfile` for this solution: -`ENV APP_CONFIG=config/docker_openresty-orthanc-keycloak.js` +`ENV APP_CONFIG=config/docker-nginx-orthanc-keycloak.js` You can find the configuration we're using here: -`/public/config/docker_openresty-orthanc-keycloak.js` +`/public/config/docker-nginx-orthanc-keycloak.js` To rebuild the `webapp` image created by our `dockerfile` after updating the Viewer's configuration, you can run: @@ -143,25 +450,15 @@ Viewer's configuration, you can run: - `docker-compose build` OR - `docker-compose up --build` -#### Other - -All other files are found in: `/docker/OpenResty-Orthanc-Keycloak/` -| Service | Configuration | Docs | -| ----------------- | ------------------------------------------------ | ------------------------------------------- | -| OHIF Viewer | [dockerfile][dockerfile] / [config.js][config] | You're reading them now! | -| OpenResty (Nginx) | [`/nginx.conf`][config-nginx] | [lua-resty-openidc][lua-resty-openidc-docs] | -| Orthanc | [`/orthanc.json`][config-orthanc] | [Here][orthanc-docs] | -| Keycloak | [`/ohif-keycloak-realm.json`][config-keycloak]\* | | -\* These are the seed values for Keycloak. They can be manually updated at -`http://127.0.0.1/auth/admin` +## Next Steps -#### Keycloak Themeing +### Keycloak Theming The `Login` screen for the `ohif-viewer` client is using a Custom Keycloak theme. You can find the source files for it in -`/docker/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes/`. You can see how +`platform/app/.recipes/deprecated-recipes/OpenResty-Orthanc-Keycloak/volumes/keycloak-themes`. You can see how we add it to Keycloak in the `docker-compose` file, and you can read up on how to leverage custom themes in [Keycloak's own docs](https://www.keycloak.org/docs/latest/server_development/index.html#_themes). @@ -170,77 +467,12 @@ to leverage custom themes in | ---------------------------------------------------------------------- | ---------------------------------------------------------------- | | ![Keycloak Default Theme](../assets/img/keycloak-default-theme.png) | ![Keycloak OHIF Theme](../assets/img/keycloak-ohif-theme.png) | -## Next Steps - -### Deploying to Production - -While these configuration and docker-compose files model an environment suitable -for production, they are not easy to deploy "as is". You can either: - -- Manually recreate this environment and deploy built application files **OR** -- Deploy to a cloud kubernetes provider like - [Digital Ocean](https://www.digitalocean.com/products/kubernetes/) **OR** - - [See a full list of cloud providers here](https://landscape.cncf.io/category=cloud&format=card-mode&grouping=category) -- Find and follow your preferred provider's guide on setting up - [swarms and stacks](https://docs.docker.com/get-started/) - -### Adding SSL - -Adding SSL registration and renewal for your domain with Let's Encrypt that -terminates at Nginx is an incredibly important step toward securing your data. -Here are some resources, specific to this setup, that may be helpful: - -- [lua-resty-auto-ssl](https://github.com/GUI/lua-resty-auto-ssl) -- [Let's Encrypt + Nginx](https://www.nginx.com/blog/using-free-ssltls-certificates-from-lets-encrypt-with-nginx/) - -While we terminate SSL at Nginx, it may be worth using self signed certificates -for communication between services. - -- [SSL Termination for TCP Upstream Servers](https://docs.nginx.com/nginx/admin-guide/security-controls/terminating-ssl-tcp/) - -### Use PostgresSQL w/ Orthanc -Orthanc can handle a large amount of data and requests, but if you find that -requests start to slow as you add more and more studies, you may want to -configure your Orthanc instance to use PostgresSQL. Instructions on how to do -that can be found in the -[`Orthanc Server Book`](http://book.orthanc-server.com/users/docker.html), under -"PostgreSQL and Orthanc inside Docker" -### Improving This Guide -Here are some improvements this guide would benefit from, and that we would be -more than happy to accept Pull Requests for: - -- SSL Support -- Complete configuration with `.env` file (or something similar) -- Keycloak Theme improvements -- Any security issues -- One-click deploy to a cloud provider ## Resources -### Misc. Helpful Commands - -_Check if `nginx.conf` is valid:_ - -```bash -docker run --rm -t -a stdout --name my-openresty -v $PWD/config/:/usr/local/openresty/nginx/conf/:ro openresty/openresty:alpine-fat openresty -c /usr/local/openresty/nginx/conf/nginx.conf -t -``` - -_Interact w/ running container:_ - -`docker exec -it CONTAINER_NAME bash` - -_List running containers:_ - -`docker ps` - -_Clear Keycloak DB so you can re-seed values:_ - -- `docker volume prune` OR -- `docker volume ls` and `docker volume rm VOLUME_NAME VOLUME_NAME` - ### Referenced Articles The inspiration for our setup was driven largely by these articles: diff --git a/platform/docs/docs/faq/technical.md b/platform/docs/docs/faq/technical.md index adc33d95a54..33a50df51f0 100644 --- a/platform/docs/docs/faq/technical.md +++ b/platform/docs/docs/faq/technical.md @@ -148,7 +148,7 @@ This is a flag that you can set in your [configuration file](../configuration/co For instance for a large pt/ct study -![](../assets/img/large-pt-ct.png) +![](../assets/img/large-pt-ct.jpeg) Before (without the flag) the app shows 399 MB of memory usage diff --git a/platform/docs/docs/platform/services/ui/customization-service.md b/platform/docs/docs/platform/services/ui/customization-service.md index e42d201aa7d..a6baf90f447 100644 --- a/platform/docs/docs/platform/services/ui/customization-service.md +++ b/platform/docs/docs/platform/services/ui/customization-service.md @@ -465,7 +465,7 @@ window.config = { ... ``` - + ## Context Menus diff --git a/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md index 548c43b34a5..6de5fa157b2 100644 --- a/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md +++ b/platform/docs/versioned_docs/version-2.0-deprecated/configuring/data-source.md @@ -93,13 +93,13 @@ is running the `dev:orthanc` script in our project's `package.json`. That script is: ```js -cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker_nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w +cross-env NODE_ENV=development PROXY_TARGET=/dicom-web PROXY_DOMAIN=http://localhost:8042 APP_CONFIG=config/docker-nginx-orthanc.js webpack-dev-server --config .webpack/webpack.pwa.js -w ``` - `cross-env` sets three environment variables - PROXY_TARGET: `/dicom-web` - PROXY_DOMAIN: `http://localhost:8042` - - APP_CONFIG: `config/docker_nginx-orthanc.js` + - APP_CONFIG: `config/docker-nginx-orthanc.js` - `webpack-dev-server` runs using the `.webpack/webpack.pwa.js` configuration file. It will watch for changes and update as we develop. diff --git a/yarn.lock b/yarn.lock index d61057d72b3..2202de09d9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13734,6 +13734,11 @@ jsprim@^2.0.2: object.assign "^4.1.4" object.values "^1.1.6" +jwt-decode@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" + integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA== + keyv@^4.5.3: version "4.5.4" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" @@ -15924,6 +15929,13 @@ ohash@^1.1.3: resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07" integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw== +oidc-client-ts@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/oidc-client-ts/-/oidc-client-ts-3.0.1.tgz#be264fb87c89f74f73863646431c32cd06f5ceb7" + integrity sha512-xX8unZNtmtw3sOz4FPSqDhkLFnxCDsdo2qhFEH2opgWnF/iXMFoYdBQzkwCxAZVgt3FT3DnuBY3k80EZHT0RYg== + dependencies: + jwt-decode "^4.0.0" + oidc-client@1.11.5: version "1.11.5" resolved "https://registry.yarnpkg.com/oidc-client/-/oidc-client-1.11.5.tgz#020aa193d68a3e1f87a24fcbf50073b738de92bb"