From 9b8856c6b0327a2622c5ddcf8eb82b216addc6f0 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 22 Apr 2025 12:12:10 +0300 Subject: [PATCH 01/13] WIP --- .github/workflows/tests.yml | 62 ++++++++++------ tests/oauth2_authorization_code.js | 112 +++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 20 deletions(-) create mode 100644 tests/oauth2_authorization_code.js diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 17ce3da..af31855 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,37 +17,59 @@ jobs: - name: Start & configure Keycloak and debugger id: configure run: | + # Install testing dependencies + npm install --prefix tests + # Start Docker containers CONFIG_FILE=./env/local.js docker compose -f docker-compose-with-keycloak.yml up -d --build sleep 30 - # Configure client credentials flow + # Configure Keycloak KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "client-credentials-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "client-credentials", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' - KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') - KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') - KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') - KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="client-credentials-scope") | .id') - KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="client-credentials-scope") | .name') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_ID/optional-client-scopes/$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_ID" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" - - # Share variables to next steps - echo "CLIENT_CREDENTIALS_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT - echo "CLIENT_CREDENTIALS_CLIENT_ID=$(echo $KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT - echo "CLIENT_CREDENTIALS_CLIENT_SECRET=$(echo $KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_SECRET)" >> $GITHUB_OUTPUT - echo "CLIENT_CREDENTIALS_SCOPE=$(echo $KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME)" >> $GITHUB_OUTPUT + + for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE + do + FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') + + KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "$FLOW_NAME-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' + case "$FLOW_VARIABLE" in + CLIENT_CREDENTIALS) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "$FLOW_NAME", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' + ;; + AUTHORIZATION_CODE) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "$FLOW_NAME", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + ;; + esac + + KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') + KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') + KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') + KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="$FLOW_NAME-scope") | .id') + KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="$FLOW_NAME-scope") | .name') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID/optional-client-scopes/$KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_ID" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" + + echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT + echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT + echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET)" >> $GITHUB_OUTPUT + echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME)" >> $GITHUB_OUTPUT + done - name: Test client credentials flow id: test_client_credentials run: | - # Install dependencies - cd tests && npm install - - # Test client credentials flow DISCOVERY_ENDPOINT=${{ steps.configure.outputs.CLIENT_CREDENTIALS_DISCOVERY_ENDPOINT }} \ CLIENT_ID=${{ steps.configure.outputs.CLIENT_CREDENTIALS_CLIENT_ID }} \ CLIENT_SECRET=${{ steps.configure.outputs.CLIENT_CREDENTIALS_CLIENT_SECRET }} \ SCOPE=${{ steps.configure.outputs.CLIENT_CREDENTIALS_SCOPE }} \ - node oauth2_client_credentials.js \ No newline at end of file + node tests/oauth2_client_credentials.js + + - name: Test authorization code flow + id: test_authorization_code + run: | + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_SCOPE }} \ + node tests/oauth2_authorization_code.js \ No newline at end of file diff --git a/tests/oauth2_authorization_code.js b/tests/oauth2_authorization_code.js new file mode 100644 index 0000000..0dc26be --- /dev/null +++ b/tests/oauth2_authorization_code.js @@ -0,0 +1,112 @@ +const { Builder, By, until } = require("selenium-webdriver"); +const { Select } = require('selenium-webdriver/lib/select'); +const chrome = require("selenium-webdriver/chrome"); +const jwt = require("jsonwebtoken"); +const assert = require("assert"); + +async function populateMetadata(driver, discovery_endpoint) { + oidc_discovery_endpoint = By.id("oidc_discovery_endpoint"); + btn_oidc_discovery_endpoint = By.className("btn_oidc_discovery_endpoint"); + btn_oidc_populate_meta_data = By.className("btn_oidc_populate_meta_data"); + + // Wait until page is loaded + await driver.wait(until.elementLocated(oidc_discovery_endpoint), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(oidc_discovery_endpoint)), 10000); + + // Enter discovery endpoint + await driver.findElement(oidc_discovery_endpoint).clear(); + await driver.findElement(oidc_discovery_endpoint).sendKeys(discovery_endpoint); + await driver.findElement(btn_oidc_discovery_endpoint).click(); + + // Populate metadata + await driver.wait(until.elementLocated(btn_oidc_populate_meta_data), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(btn_oidc_populate_meta_data)), 10000); + await driver.executeScript("arguments[0].scrollIntoView({ behavior: 'smooth', block: 'center' });", await driver.findElement(btn_oidc_populate_meta_data)); + await driver.findElement(btn_oidc_populate_meta_data).click(); +} + +async function getAccessToken(driver, client_id, client_secret, scope) { + authorization_grant_type = By.id("authorization_grant_type"); + token_client_id = By.id("token_client_id"); + token_client_secret = By.id("token_client_secret"); + token_scope = By.id("token_scope"); + btn1 = By.className("btn1"); + token_access_token = By.id("token_access_token"); + display_token_error_form_textarea1 = By.id("display_token_error_form_textarea1"); + + // Select client credential login type + await new Select(await driver.findElement(authorization_grant_type)).selectByVisibleText('OAuth2 Client Credential'); + await driver.wait(until.elementLocated(token_client_id), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(token_client_id)), 10000); + + // Submit credentials + await driver.findElement(token_client_id).clear(); + await driver.findElement(token_client_id).sendKeys(client_id); + await driver.findElement(token_client_secret).clear(); + await driver.findElement(token_client_secret).sendKeys(client_secret); + await driver.findElement(token_scope).clear(); + await driver.findElement(token_scope).sendKeys(scope); + await driver.findElement(btn1).click(); + + // Get access token result + async function waitForVisibility(element) { + await driver.wait(until.elementLocated(element), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(element)), 10000); + return element; + } + + let visibleAccessTokenElement = await Promise.any([ + waitForVisibility(token_access_token), + waitForVisibility(display_token_error_form_textarea1) + ]); + + return await driver.findElement(visibleAccessTokenElement).getAttribute("value"); +} + +async function verifyAccessToken(access_token, client_id, scope) { + async function compareScopes(scope1, scope2) { + scope1 = scope1.split(" "); + scope2 = scope2.split(" "); + + return scope2.every(element => scope1.includes(element)); + } + + let decoded_access_token = jwt.decode(access_token, { complete: true }); + let response_text = access_token.match(/responseText: (.*)/); + + assert.notStrictEqual(decoded_access_token, null, "Cannot decode access token. Request result: " + (response_text ? response_text[1] : "no response text")); + assert.strictEqual(decoded_access_token.payload.client_id, client_id, "Access token client ID does not match client ID."); + assert.strictEqual(await compareScopes(decoded_access_token.payload.scope, scope), true, "Access token scope does not match scope."); +} + +async function test() { + const options = new chrome.Options(); + options.addArguments("--headless"); + options.addArguments("--no-sandbox"); + const driver = await new Builder().forBrowser("chrome").setChromeOptions(options).build(); + + try { + const discovery_endpoint = process.env.DISCOVERY_ENDPOINT; + const client_id = process.env.CLIENT_ID; + const client_secret = process.env.CLIENT_SECRET; + const scope = process.env.SCOPE; + + assert(discovery_endpoint, "DISCOVERY_ENDPOINT environment variable is not set."); + assert(client_id, "CLIENT_ID environment variable is not set."); + assert(client_secret, "CLIENT_SECRET environment variable is not set."); + assert(scope, "SCOPE environment variable is not set."); + + await driver.get("http://localhost:3000"); + await populateMetadata(driver, discovery_endpoint); + let access_token = await getAccessToken(driver, client_id, client_secret, scope); + await verifyAccessToken(access_token, client_id, scope); + console.log("Test completed successfully.") + } catch (error) { + console.log(error.message); + process.exit(1); + } finally { + await driver.quit(); + } +} + +test(); \ No newline at end of file From dd2415a00ce88547e3d86efcf6eab36ec624d1a4 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Thu, 24 Apr 2025 15:17:20 +0300 Subject: [PATCH 02/13] WIP --- .github/workflows/tests.yml | 42 ++++++++++++++++---------------- run-tests.sh | 48 +++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af31855..af1e5db 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -30,30 +30,30 @@ jobs: for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE do - FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') + FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') - KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "$FLOW_NAME-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' - case "$FLOW_VARIABLE" in - CLIENT_CREDENTIALS) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "$FLOW_NAME", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' - ;; - AUTHORIZATION_CODE) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "$FLOW_NAME", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' - ;; - esac + KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "'$FLOW_NAME'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' + case "$FLOW_VARIABLE" in + CLIENT_CREDENTIALS) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' + ;; + AUTHORIZATION_CODE) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + ;; + esac - KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') - KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') - KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') - KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="$FLOW_NAME-scope") | .id') - KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="$FLOW_NAME-scope") | .name') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID/optional-client-scopes/$KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_ID" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" - echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT - echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT - echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET)" >> $GITHUB_OUTPUT - echo "$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME)" >> $GITHUB_OUTPUT + echo "$(echo $FLOW_VARIABLE)_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT + echo "$(echo $FLOW_VARIABLE)_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT + echo "$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET)" >> $GITHUB_OUTPUT + echo "$(echo $FLOW_VARIABLE)_SCOPE=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME)" >> $GITHUB_OUTPUT done - name: Test client credentials flow diff --git a/run-tests.sh b/run-tests.sh index 09baa05..cc8b4d5 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,27 +1,49 @@ #!/bin/bash +# Install testing dependencies +npm install --prefix tests + # Start Docker containers CONFIG_FILE=./env/local.js docker compose -f docker-compose-with-keycloak.yml up -d --build sleep 30 -# Configure client credentials flow +# Configure Keycloak KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' -curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "client-credentials-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' -curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "client-credentials", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' -KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') -KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') -KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=client-credentials" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') -KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="client-credentials-scope") | .id') -KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="client-credentials-scope") | .name') -curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_ID/optional-client-scopes/$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_ID" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" - -# Install dependencies -cd tests && npm install + +for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE +do + FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') + + KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "'$FLOW_NAME'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' + case "$FLOW_VARIABLE" in + CLIENT_CREDENTIALS) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' + ;; + AUTHORIZATION_CODE) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + ;; + esac + + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" +done # Test client credentials flow DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ CLIENT_ID=$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_CLIENTID \ CLIENT_SECRET=$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_SECRET \ SCOPE=$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME \ -node oauth2_client_credentials.js \ No newline at end of file +node tests/oauth2_client_credentials.js + +# Test authorization code flow +DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_SCOPE_NAME \ +node tests/oauth2_authorization_code.js \ No newline at end of file From e96d774535a99e7c2d34ab490fd1a087f516c90b Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Fri, 25 Apr 2025 15:07:38 +0300 Subject: [PATCH 03/13] WIP --- .github/workflows/tests.yml | 43 ++++++++++++++++--- run-tests.sh | 41 +++++++++++++++--- tests/oauth2_authorization_code.js | 68 ++++++++++++++++++++++++++++-- 3 files changed, 137 insertions(+), 15 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af1e5db..84ba79b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' - for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE + for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_PRIVATE AUTHORIZATION_CODE_PUBLIC do FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') @@ -38,9 +38,12 @@ jobs: CLIENT_CREDENTIALS) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; - AUTHORIZATION_CODE) + AUTHORIZATION_CODE_PRIVATE) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; + AUTHORIZATION_CODE_PUBLIC) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + ;; esac declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') @@ -49,6 +52,8 @@ jobs: declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"username": "'$FLOW_NAME'", "firstName": "'$FLOW_NAME'", "lastName": "'$FLOW_NAME'", "email": "'$FLOW_NAME'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_USER_ID); echo ${!TMP})/reset-password" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"type": "password", "value": "'$FLOW_NAME'", "temporary": false}' echo "$(echo $FLOW_VARIABLE)_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT echo "$(echo $FLOW_VARIABLE)_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT @@ -68,8 +73,34 @@ jobs: - name: Test authorization code flow id: test_authorization_code run: | - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_SCOPE }} \ + # Private client with PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_SCOPE }} \ + PKCE_ENABLED=true \ + node tests/oauth2_authorization_code.js + + # Private client without PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_SCOPE }} \ + PKCE_ENABLED=false \ + node tests/oauth2_authorization_code.js + + # Public client with PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ + PKCE_ENABLED=true \ + node tests/oauth2_authorization_code.js + + # Public client without PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ + PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js \ No newline at end of file diff --git a/run-tests.sh b/run-tests.sh index cc8b4d5..2bf2ca4 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -11,7 +11,7 @@ sleep 30 KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' -for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE +for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_PRIVATE AUTHORIZATION_CODE_PUBLIC do FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') @@ -21,9 +21,12 @@ do CLIENT_CREDENTIALS) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; - AUTHORIZATION_CODE) + AUTHORIZATION_CODE_PRIVATE) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; + AUTHORIZATION_CODE_PUBLIC) + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + ;; esac declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') @@ -32,6 +35,8 @@ do declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" + declare KEYCLOAK_$(echo $FLOW_VARIABLE)_USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"username": "'$FLOW_NAME'", "firstName": "'$FLOW_NAME'", "lastName": "'$FLOW_NAME'", "email": "'$FLOW_NAME'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_USER_ID); echo ${!TMP})/reset-password" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"type": "password", "value": "'$FLOW_NAME'", "temporary": false}' done # Test client credentials flow @@ -42,8 +47,34 @@ SCOPE=$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME \ node tests/oauth2_client_credentials.js # Test authorization code flow +## Private client with PKCE +DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_SCOPE_NAME \ +PKCE_ENABLED=true \ +node tests/oauth2_authorization_code.js + +## Private client without PKCE +DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_SCOPE_NAME \ +PKCE_ENABLED=false \ +node tests/oauth2_authorization_code.js + +## Public client with PKCE +DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ +PKCE_ENABLED=true \ +node tests/oauth2_authorization_code.js + +## Public client without PKCE DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_SCOPE_NAME \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ +PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js \ No newline at end of file diff --git a/tests/oauth2_authorization_code.js b/tests/oauth2_authorization_code.js index 0dc26be..8703e60 100644 --- a/tests/oauth2_authorization_code.js +++ b/tests/oauth2_authorization_code.js @@ -25,21 +25,70 @@ async function populateMetadata(driver, discovery_endpoint) { await driver.findElement(btn_oidc_populate_meta_data).click(); } -async function getAccessToken(driver, client_id, client_secret, scope) { +async function getAccessToken(driver, client_id, client_secret, scope, pkce_enabled) { authorization_grant_type = By.id("authorization_grant_type"); + usePKCE_yes = By.id("usePKCE-yes"); + usePKCE_no = By.id("usePKCE-no"); + authz_expand_button = By.id("authz_expand_button"); + client_id_ = By.id("client_id"); + scope_ = By.id("scope"); token_client_id = By.id("token_client_id"); token_client_secret = By.id("token_client_secret"); token_scope = By.id("token_scope"); + btn_authorize = By.css("input[type=\"submit\"][value=\"Authorize\"]"); + keycloak_username = By.id("username"); + keycloak_password = By.id("password"); + keycloak_kc_login = By.id("kc-login"); btn1 = By.className("btn1"); token_access_token = By.id("token_access_token"); display_token_error_form_textarea1 = By.id("display_token_error_form_textarea1"); // Select client credential login type - await new Select(await driver.findElement(authorization_grant_type)).selectByVisibleText('OAuth2 Client Credential'); + await new Select(await driver.findElement(authorization_grant_type)).selectByVisibleText('OIDC Authorization Code Flow(code)'); + await driver.wait(until.elementLocated(usePKCE_yes), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(usePKCE_yes)), 10000); + await driver.wait(until.elementLocated(usePKCE_no), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(usePKCE_no)), 10000); + + if (pkce_enabled) { + await driver.findElement(usePKCE_yes).click(); + } else { + await driver.findElement(usePKCE_no).click(); + } + + await driver.wait(until.elementLocated(authz_expand_button), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(authz_expand_button)), 10000); + await driver.findElement(authz_expand_button).click(); + await driver.wait(until.elementLocated(client_id_), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(client_id_)), 10000); + + // Submit credentials + await driver.findElement(client_id_).clear(); + await driver.findElement(client_id_).sendKeys(client_id); + await driver.findElement(scope_).clear(); + await driver.findElement(scope_).sendKeys(scope); + await driver.findElement(btn_authorize).click(); + + // Login to Keycloak + try { + await driver.wait(until.elementLocated(keycloak_username), 10000); + await driver.wait(until.elementIsVisible(driver.findElement(keycloak_username)), 10000); + } catch (error) { + authz_error_report = await driver.findElement(By.id("authz-error-report")); + authz_error_report_paragraphs = await authz_error_report.findElements(By.css("p")); + throw new Error(await authz_error_report_paragraphs[authz_error_report_paragraphs.length - 1].getText()); + } + + await driver.findElement(keycloak_username).clear(); + await driver.findElement(keycloak_username).sendKeys(client_id); + await driver.findElement(keycloak_password).clear(); + await driver.findElement(keycloak_password).sendKeys(client_id); + await driver.findElement(keycloak_kc_login).click(); + + // Submit credentials (again) await driver.wait(until.elementLocated(token_client_id), 10000); await driver.wait(until.elementIsVisible(driver.findElement(token_client_id)), 10000); - // Submit credentials await driver.findElement(token_client_id).clear(); await driver.findElement(token_client_id).sendKeys(client_id); await driver.findElement(token_client_secret).clear(); @@ -90,15 +139,26 @@ async function test() { const client_id = process.env.CLIENT_ID; const client_secret = process.env.CLIENT_SECRET; const scope = process.env.SCOPE; + let pkce_enabled = process.env.PKCE_ENABLED assert(discovery_endpoint, "DISCOVERY_ENDPOINT environment variable is not set."); assert(client_id, "CLIENT_ID environment variable is not set."); assert(client_secret, "CLIENT_SECRET environment variable is not set."); assert(scope, "SCOPE environment variable is not set."); + assert(pkce_enabled, "PKCE_ENABLED environment variable is not set."); + + if (pkce_enabled === "true") { + pkce_enabled = true; + } else if (pkce_enabled === "false") { + pkce_enabled = false; + } else { + console.log("PKCE_ENABLED must be true or false."); + process.exit(1); + } await driver.get("http://localhost:3000"); await populateMetadata(driver, discovery_endpoint); - let access_token = await getAccessToken(driver, client_id, client_secret, scope); + let access_token = await getAccessToken(driver, client_id, client_secret, scope, pkce_enabled); await verifyAccessToken(access_token, client_id, scope); console.log("Test completed successfully.") } catch (error) { From 1884c418f444ad477dc6178b94a09b3e5d712acb Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Mon, 28 Apr 2025 14:45:48 +0300 Subject: [PATCH 04/13] WIP --- .github/workflows/tests.yml | 29 +++++++++++++++++------------ run-tests.sh | 24 ++++++++++++++---------- tests/oauth2_authorization_code.js | 12 +++++++++--- 3 files changed, 40 insertions(+), 25 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 84ba79b..472b91c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -28,7 +28,7 @@ jobs: KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' - for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_PRIVATE AUTHORIZATION_CODE_PUBLIC + for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_CONFIDENTIAL AUTHORIZATION_CODE_PUBLIC do FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') @@ -38,7 +38,7 @@ jobs: CLIENT_CREDENTIALS) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; - AUTHORIZATION_CODE_PRIVATE) + AUTHORIZATION_CODE_CONFIDENTIAL) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; AUTHORIZATION_CODE_PUBLIC) @@ -59,6 +59,7 @@ jobs: echo "$(echo $FLOW_VARIABLE)_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT echo "$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET)" >> $GITHUB_OUTPUT echo "$(echo $FLOW_VARIABLE)_SCOPE=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME)" >> $GITHUB_OUTPUT + echo "$(echo $FLOW_VARIABLE)_USER=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_USER_ID)" >> $GITHUB_OUTPUT done - name: Test client credentials flow @@ -73,19 +74,21 @@ jobs: - name: Test authorization code flow id: test_authorization_code run: | - # Private client with PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_SCOPE }} \ + # Confidential client with PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_USER }} \ PKCE_ENABLED=true \ node tests/oauth2_authorization_code.js - # Private client without PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PRIVATE_SCOPE }} \ + # Confidential client without PKCE + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_USER }} \ PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js @@ -94,6 +97,7 @@ jobs: CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_USER }} \ PKCE_ENABLED=true \ node tests/oauth2_authorization_code.js @@ -102,5 +106,6 @@ jobs: CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_USER }} \ PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js \ No newline at end of file diff --git a/run-tests.sh b/run-tests.sh index 2bf2ca4..274957f 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -11,7 +11,7 @@ sleep 30 KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' -for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_PRIVATE AUTHORIZATION_CODE_PUBLIC +for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_CONFIDENTIAL AUTHORIZATION_CODE_PUBLIC do FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') @@ -21,7 +21,7 @@ do CLIENT_CREDENTIALS) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; - AUTHORIZATION_CODE_PRIVATE) + AUTHORIZATION_CODE_CONFIDENTIAL) curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; AUTHORIZATION_CODE_PUBLIC) @@ -47,19 +47,21 @@ SCOPE=$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME \ node tests/oauth2_client_credentials.js # Test authorization code flow -## Private client with PKCE +## Confidential client with PKCE DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_SCOPE_NAME \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE_NAME \ +USER=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_USER_ID \ PKCE_ENABLED=true \ node tests/oauth2_authorization_code.js -## Private client without PKCE +## Confidential client without PKCE DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PRIVATE_SCOPE_NAME \ +CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_CLIENTID \ +CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET \ +SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE_NAME \ +USER=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_USER_ID \ PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js @@ -68,6 +70,7 @@ DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/op CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ +USER=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_USER_ID \ PKCE_ENABLED=true \ node tests/oauth2_authorization_code.js @@ -76,5 +79,6 @@ DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/op CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ +USER=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_USER_ID \ PKCE_ENABLED=false \ node tests/oauth2_authorization_code.js \ No newline at end of file diff --git a/tests/oauth2_authorization_code.js b/tests/oauth2_authorization_code.js index 8703e60..896625c 100644 --- a/tests/oauth2_authorization_code.js +++ b/tests/oauth2_authorization_code.js @@ -112,7 +112,7 @@ async function getAccessToken(driver, client_id, client_secret, scope, pkce_enab return await driver.findElement(visibleAccessTokenElement).getAttribute("value"); } -async function verifyAccessToken(access_token, client_id, scope) { +async function verifyAccessToken(access_token, client_id, scope, user) { async function compareScopes(scope1, scope2) { scope1 = scope1.split(" "); scope2 = scope2.split(" "); @@ -124,8 +124,12 @@ async function verifyAccessToken(access_token, client_id, scope) { let response_text = access_token.match(/responseText: (.*)/); assert.notStrictEqual(decoded_access_token, null, "Cannot decode access token. Request result: " + (response_text ? response_text[1] : "no response text")); - assert.strictEqual(decoded_access_token.payload.client_id, client_id, "Access token client ID does not match client ID."); + assert.strictEqual(decoded_access_token.payload.azp, client_id, "Access token AZP does not match client ID."); assert.strictEqual(await compareScopes(decoded_access_token.payload.scope, scope), true, "Access token scope does not match scope."); + assert.strictEqual(decoded_access_token.payload.sub, user, "Access token SUB does not match user ID."); + assert.strictEqual(decoded_access_token.payload.given_name, client_id, "Access token given_name does not match."); + assert.strictEqual(decoded_access_token.payload.family_name, client_id, "Access token family_name does not match."); + assert.strictEqual(decoded_access_token.payload.email, `${client_id}@iyasec.io`, "Access token email does not match."); } async function test() { @@ -139,12 +143,14 @@ async function test() { const client_id = process.env.CLIENT_ID; const client_secret = process.env.CLIENT_SECRET; const scope = process.env.SCOPE; + const user = process.env.USER; let pkce_enabled = process.env.PKCE_ENABLED assert(discovery_endpoint, "DISCOVERY_ENDPOINT environment variable is not set."); assert(client_id, "CLIENT_ID environment variable is not set."); assert(client_secret, "CLIENT_SECRET environment variable is not set."); assert(scope, "SCOPE environment variable is not set."); + assert(user, "USER environment variable is not set."); assert(pkce_enabled, "PKCE_ENABLED environment variable is not set."); if (pkce_enabled === "true") { @@ -159,7 +165,7 @@ async function test() { await driver.get("http://localhost:3000"); await populateMetadata(driver, discovery_endpoint); let access_token = await getAccessToken(driver, client_id, client_secret, scope, pkce_enabled); - await verifyAccessToken(access_token, client_id, scope); + await verifyAccessToken(access_token, client_id, scope, user); console.log("Test completed successfully.") } catch (error) { console.log(error.message); From 681897e74b06eacd9bb9fc565319df5f52f3ffa1 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 12:36:11 +0300 Subject: [PATCH 05/13] WIP --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8ba5a5a..72ac23a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ api/node_modules client/node_modules -node_modules \ No newline at end of file +node_modules +.idea \ No newline at end of file From 97e6e3cb974ad4f3002406225cfe306fad6b3fcc Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 12:41:53 +0300 Subject: [PATCH 06/13] WIP --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 472b91c..b345d53 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,6 +16,7 @@ jobs: - name: Start & configure Keycloak and debugger id: configure + shell: bash run: | # Install testing dependencies npm install --prefix tests @@ -64,6 +65,7 @@ jobs: - name: Test client credentials flow id: test_client_credentials + shell: bash run: | DISCOVERY_ENDPOINT=${{ steps.configure.outputs.CLIENT_CREDENTIALS_DISCOVERY_ENDPOINT }} \ CLIENT_ID=${{ steps.configure.outputs.CLIENT_CREDENTIALS_CLIENT_ID }} \ @@ -73,6 +75,7 @@ jobs: - name: Test authorization code flow id: test_authorization_code + shell: bash run: | # Confidential client with PKCE DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ From 58fe74eb855b67e295b620101d3b495bf1cb16e7 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 12:47:46 +0300 Subject: [PATCH 07/13] WIP --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b345d53..f106986 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -63,6 +63,9 @@ jobs: echo "$(echo $FLOW_VARIABLE)_USER=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_USER_ID)" >> $GITHUB_OUTPUT done + echo $GITHUB_OUTPUT + cat $GITHUB_OUTPUT + - name: Test client credentials flow id: test_client_credentials shell: bash From 25330ea9356495ed6eeb25d626eefc992439d740 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 12:51:58 +0300 Subject: [PATCH 08/13] WIP --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f106986..1f0d148 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,6 +18,8 @@ jobs: id: configure shell: bash run: | + #!/bin/bash + # Install testing dependencies npm install --prefix tests @@ -63,7 +65,6 @@ jobs: echo "$(echo $FLOW_VARIABLE)_USER=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_USER_ID)" >> $GITHUB_OUTPUT done - echo $GITHUB_OUTPUT cat $GITHUB_OUTPUT - name: Test client credentials flow From fda9730729e4caf5edb64e5461ac86ce2ec06f7b Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 13:08:44 +0300 Subject: [PATCH 09/13] WIP --- .github/workflows/tests.yml | 45 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1f0d148..d02a8ad 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,10 +16,7 @@ jobs: - name: Start & configure Keycloak and debugger id: configure - shell: bash run: | - #!/bin/bash - # Install testing dependencies npm install --prefix tests @@ -29,47 +26,46 @@ jobs: # Configure Keycloak KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') - curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' + curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_CONFIDENTIAL AUTHORIZATION_CODE_PUBLIC do - FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') + FLOW_NAME=$(echo ${FLOW_VARIABLE} | tr '[:upper:]' '[:lower:]' | tr '_' '-') KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "'$FLOW_NAME'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' - case "$FLOW_VARIABLE" in + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"name": "'${FLOW_NAME}'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' + case "${FLOW_VARIABLE}" in CLIENT_CREDENTIALS) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; AUTHORIZATION_CODE_CONFIDENTIAL) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; AUTHORIZATION_CODE_PUBLIC) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; esac - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"username": "'$FLOW_NAME'", "firstName": "'$FLOW_NAME'", "lastName": "'$FLOW_NAME'", "email": "'$FLOW_NAME'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_USER_ID); echo ${!TMP})/reset-password" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"type": "password", "value": "'$FLOW_NAME'", "temporary": false}' + CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].id') + CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].clientId') + CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].secret') + SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[] | select(.name=="'${FLOW_NAME}'-scope") | .id') + SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[] | select(.name=="'${FLOW_NAME}'-scope") | .name') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/${CLIENT_ID}/optional-client-scopes/${SCOPE_ID}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" + USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"username": "'${FLOW_NAME}'", "firstName": "'${FLOW_NAME}'", "lastName": "'${FLOW_NAME}'", "email": "'${FLOW_NAME}'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/${USER_ID}/reset-password" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"type": "password", "value": "'${FLOW_NAME}'", "temporary": false}' - echo "$(echo $FLOW_VARIABLE)_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT - echo "$(echo $FLOW_VARIABLE)_CLIENT_ID=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_CLIENTID)" >> $GITHUB_OUTPUT - echo "$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_CLIENT_SECRET)" >> $GITHUB_OUTPUT - echo "$(echo $FLOW_VARIABLE)_SCOPE=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_SCOPE_NAME)" >> $GITHUB_OUTPUT - echo "$(echo $FLOW_VARIABLE)_USER=$(echo $KEYCLOAK_$(TMP=$(echo $FLOW_VARIABLE);echo ${!TMP})_USER_ID)" >> $GITHUB_OUTPUT + echo "${FLOW_VARIABLE}_DISCOVERY_ENDPOINT=http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" >> $GITHUB_OUTPUT + echo "${FLOW_VARIABLE}_CLIENT_ID=${CLIENT_CLIENTID}" >> $GITHUB_OUTPUT + echo "${FLOW_VARIABLE}_CLIENT_SECRET=${CLIENT_SECRET}" >> $GITHUB_OUTPUT + echo "${FLOW_VARIABLE}_SCOPE=${SCOPE_NAME}" >> $GITHUB_OUTPUT + echo "${FLOW_VARIABLE}_USER=${USER_ID}" >> $GITHUB_OUTPUT done cat $GITHUB_OUTPUT - name: Test client credentials flow id: test_client_credentials - shell: bash run: | DISCOVERY_ENDPOINT=${{ steps.configure.outputs.CLIENT_CREDENTIALS_DISCOVERY_ENDPOINT }} \ CLIENT_ID=${{ steps.configure.outputs.CLIENT_CREDENTIALS_CLIENT_ID }} \ @@ -79,7 +75,6 @@ jobs: - name: Test authorization code flow id: test_authorization_code - shell: bash run: | # Confidential client with PKCE DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ From 1cde5214ba2d0859ee52049c12f1086ecefe8636 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Tue, 29 Apr 2025 13:33:44 +0300 Subject: [PATCH 10/13] WIP --- .github/workflows/tests.yml | 55 ++++++++------------- run-tests.sh | 97 +++++++++++++++++-------------------- 2 files changed, 63 insertions(+), 89 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d02a8ad..6304f8a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,8 +62,6 @@ jobs: echo "${FLOW_VARIABLE}_USER=${USER_ID}" >> $GITHUB_OUTPUT done - cat $GITHUB_OUTPUT - - name: Test client credentials flow id: test_client_credentials run: | @@ -76,38 +74,23 @@ jobs: - name: Test authorization code flow id: test_authorization_code run: | - # Confidential client with PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE }} \ - USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_USER }} \ - PKCE_ENABLED=true \ - node tests/oauth2_authorization_code.js - - # Confidential client without PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE }} \ - USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_USER }} \ - PKCE_ENABLED=false \ - node tests/oauth2_authorization_code.js - - # Public client with PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ - USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_USER }} \ - PKCE_ENABLED=true \ - node tests/oauth2_authorization_code.js + for PKCE_ENABLED in true false + do + # Confidential client + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_CONFIDENTIAL_USER }} \ + PKCE_ENABLED=${PKCE_ENABLED} \ + node tests/oauth2_authorization_code.js - # Public client without PKCE - DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT }} \ - CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ - CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ - SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ - USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_USER }} \ - PKCE_ENABLED=false \ - node tests/oauth2_authorization_code.js \ No newline at end of file + # Public client + DISCOVERY_ENDPOINT=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT }} \ + CLIENT_ID=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_ID }} \ + CLIENT_SECRET=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET }} \ + SCOPE=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_SCOPE }} \ + USER=${{ steps.configure.outputs.AUTHORIZATION_CODE_PUBLIC_USER }} \ + PKCE_ENABLED=${PKCE_ENABLED} \ + node tests/oauth2_authorization_code.js + done \ No newline at end of file diff --git a/run-tests.sh b/run-tests.sh index 274957f..f021ccb 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -9,76 +9,67 @@ sleep 30 # Configure Keycloak KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') -curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' +curl -X POST "http://localhost:8080/admin/realms" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"realm": "debugger-testing", "enabled": true}' for FLOW_VARIABLE in CLIENT_CREDENTIALS AUTHORIZATION_CODE_CONFIDENTIAL AUTHORIZATION_CODE_PUBLIC do - FLOW_NAME=$(echo $FLOW_VARIABLE | tr '[:upper:]' '[:lower:]' | tr '_' '-') + FLOW_NAME=$(echo ${FLOW_VARIABLE} | tr '[:upper:]' '[:lower:]' | tr '_' '-') KEYCLOAK_ACCESS_TOKEN=$(curl -X POST "http://localhost:8080/realms/master/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "client_id=admin-cli" -d "username=keycloak" -d "password=keycloak" -d "grant_type=password" | jq -r '.access_token') - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"name": "'$FLOW_NAME'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' - case "$FLOW_VARIABLE" in + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"name": "'${FLOW_NAME}'-scope", "protocol": "openid-connect", "attributes": {"display.on.consent.screen": "false", "include.in.token.scope": "true"}}' + case "${FLOW_VARIABLE}" in CLIENT_CREDENTIALS) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": true, "authorizationServicesEnabled": false, "standardFlowEnabled": false, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret"}' ;; AUTHORIZATION_CODE_CONFIDENTIAL) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": false, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": "client-secret", "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; AUTHORIZATION_CODE_PUBLIC) - curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"clientId": "'$FLOW_NAME'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' + curl -X POST "http://localhost:8080/admin/realms/debugger-testing/clients" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"clientId": "'${FLOW_NAME}'", "protocol": "openid-connect", "publicClient": true, "serviceAccountsEnabled": false, "authorizationServicesEnabled": false, "standardFlowEnabled": true, "directAccessGrantsEnabled": false, "clientAuthenticatorType": null, "frontchannelLogout": true, "redirectUris": ["http://localhost:3000/callback"], "webOrigins": ["/*", "http://localhost:3000/*"], "attributes": {"frontchannel.logout.url": "http://localhost:3000/logout"}}' ;; esac - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].id') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].clientId') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=$FLOW_NAME" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[0].secret') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .id') - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" | jq -r '.[] | select(.name=="'$FLOW_NAME'-scope") | .name') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_CLIENT_ID); echo ${!TMP})/optional-client-scopes/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_SCOPE_ID); echo ${!TMP})" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" - declare KEYCLOAK_$(echo $FLOW_VARIABLE)_USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"username": "'$FLOW_NAME'", "firstName": "'$FLOW_NAME'", "lastName": "'$FLOW_NAME'", "email": "'$FLOW_NAME'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') - curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/$(TMP=$(echo KEYCLOAK_${FLOW_VARIABLE}_USER_ID); echo ${!TMP})/reset-password" -H "Authorization: Bearer $KEYCLOAK_ACCESS_TOKEN" -H "Content-Type: application/json" -d '{"type": "password", "value": "'$FLOW_NAME'", "temporary": false}' + CLIENT_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].id') + CLIENT_CLIENTID=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].clientId') + CLIENT_SECRET=$(curl "http://localhost:8080/admin/realms/debugger-testing/clients?clientId=${FLOW_NAME}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[0].secret') + SCOPE_ID=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[] | select(.name=="'${FLOW_NAME}'-scope") | .id') + SCOPE_NAME=$(curl "http://localhost:8080/admin/realms/debugger-testing/client-scopes" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" | jq -r '.[] | select(.name=="'${FLOW_NAME}'-scope") | .name') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/clients/${CLIENT_ID}/optional-client-scopes/${SCOPE_ID}" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" + USER_ID=$(curl -X POST "http://localhost:8080/admin/realms/debugger-testing/users" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"username": "'${FLOW_NAME}'", "firstName": "'${FLOW_NAME}'", "lastName": "'${FLOW_NAME}'", "email": "'${FLOW_NAME}'@iyasec.io", "enabled": true, "emailVerified": true}' -i | grep Location | rev | cut -d '/' -f 1 | rev | tr -d ' \n\r') + curl -X PUT "http://localhost:8080/admin/realms/debugger-testing/users/${USER_ID}/reset-password" -H "Authorization: Bearer ${KEYCLOAK_ACCESS_TOKEN}" -H "Content-Type: application/json" -d '{"type": "password", "value": "'${FLOW_NAME}'", "temporary": false}' + + declare ${FLOW_VARIABLE}_DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" + declare ${FLOW_VARIABLE}_CLIENT_ID="${CLIENT_CLIENTID}" + declare ${FLOW_VARIABLE}_CLIENT_SECRET="${CLIENT_SECRET}" + declare ${FLOW_VARIABLE}_SCOPE="${SCOPE_NAME}" + declare ${FLOW_VARIABLE}_USER="${USER_ID}" done # Test client credentials flow -DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_CLIENT_CREDENTIALS_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_CLIENT_CREDENTIALS_SCOPE_NAME \ +DISCOVERY_ENDPOINT=${CLIENT_CREDENTIALS_DISCOVERY_ENDPOINT} \ +CLIENT_ID=${CLIENT_CREDENTIALS_CLIENT_ID} \ +CLIENT_SECRET=${CLIENT_CREDENTIALS_CLIENT_SECRET} \ +SCOPE=${CLIENT_CREDENTIALS_SCOPE} \ node tests/oauth2_client_credentials.js # Test authorization code flow -## Confidential client with PKCE -DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE_NAME \ -USER=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_USER_ID \ -PKCE_ENABLED=true \ -node tests/oauth2_authorization_code.js - -## Confidential client without PKCE -DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE_NAME \ -USER=$KEYCLOAK_AUTHORIZATION_CODE_CONFIDENTIAL_USER_ID \ -PKCE_ENABLED=false \ -node tests/oauth2_authorization_code.js - -## Public client with PKCE -DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ -USER=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_USER_ID \ -PKCE_ENABLED=true \ -node tests/oauth2_authorization_code.js +for PKCE_ENABLED in true false +do + # Confidential client + DISCOVERY_ENDPOINT=${AUTHORIZATION_CODE_CONFIDENTIAL_DISCOVERY_ENDPOINT} \ + CLIENT_ID=${AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_ID} \ + CLIENT_SECRET=${AUTHORIZATION_CODE_CONFIDENTIAL_CLIENT_SECRET} \ + SCOPE=${AUTHORIZATION_CODE_CONFIDENTIAL_SCOPE} \ + USER=${AUTHORIZATION_CODE_CONFIDENTIAL_USER} \ + PKCE_ENABLED=${PKCE_ENABLED} \ + node tests/oauth2_authorization_code.js -## Public client without PKCE -DISCOVERY_ENDPOINT="http://localhost:8080/realms/debugger-testing/.well-known/openid-configuration" \ -CLIENT_ID=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_CLIENTID \ -CLIENT_SECRET=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET \ -SCOPE=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_SCOPE_NAME \ -USER=$KEYCLOAK_AUTHORIZATION_CODE_PUBLIC_USER_ID \ -PKCE_ENABLED=false \ -node tests/oauth2_authorization_code.js \ No newline at end of file + # Public client + DISCOVERY_ENDPOINT=${AUTHORIZATION_CODE_PUBLIC_DISCOVERY_ENDPOINT} \ + CLIENT_ID=${AUTHORIZATION_CODE_PUBLIC_CLIENT_ID} \ + CLIENT_SECRET=${AUTHORIZATION_CODE_PUBLIC_CLIENT_SECRET} \ + SCOPE=${AUTHORIZATION_CODE_PUBLIC_SCOPE} \ + USER=${AUTHORIZATION_CODE_PUBLIC_USER} \ + PKCE_ENABLED=${PKCE_ENABLED} \ + node tests/oauth2_authorization_code.js +done \ No newline at end of file From 94d9bc2de3377c7b67a05af95191b0d29e372ba3 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Wed, 30 Apr 2025 09:39:05 +0300 Subject: [PATCH 11/13] Fix server-side request forgery --- api/server.js | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/server.js b/api/server.js index ec6e820..a31fa4c 100644 --- a/api/server.js +++ b/api/server.js @@ -11,6 +11,11 @@ const bunyan = require("bunyan"); const axios = require('axios'); const bodyParser = require('body-parser'); const cors = require('cors'); +const appconfig = require(process.env.CONFIG_FILE); + +if (!appconfig) { + log.debug('Failed to load appconfig.'); +} // Constants const PORT = process.env.PORT || 4000; @@ -197,6 +202,13 @@ app.post('/token', (req, res) => { parameterObject[key] + "&"; }); + + if (tokenEndpoint != appconfig.apiUrl) { + log.error('Invalid token endpoint: ' + tokenEndpoint); + res.status(400) + res.json({ error: 'Invalid token endpoint' }); + } + var headers = { 'content-type' : 'application/x-www-form-urlencoded' }; @@ -277,5 +289,4 @@ let options = { expressSwagger(options) app.listen(PORT, HOST); -log.info(`Running on http://${HOST}:${PORT}`); - +log.info(`Running on http://${HOST}:${PORT}`); \ No newline at end of file From 2818dc02100d6a11b15b04779fba40e1a8d107b0 Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Wed, 30 Apr 2025 10:00:24 +0300 Subject: [PATCH 12/13] SSRF fix --- api/server.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/api/server.js b/api/server.js index a31fa4c..a9c2e46 100644 --- a/api/server.js +++ b/api/server.js @@ -11,11 +11,6 @@ const bunyan = require("bunyan"); const axios = require('axios'); const bodyParser = require('body-parser'); const cors = require('cors'); -const appconfig = require(process.env.CONFIG_FILE); - -if (!appconfig) { - log.debug('Failed to load appconfig.'); -} // Constants const PORT = process.env.PORT || 4000; @@ -203,7 +198,7 @@ app.post('/token', (req, res) => { "&"; }); - if (tokenEndpoint != appconfig.apiUrl) { + if (!tokenEndpoint.includes(`${HOST}:${PORT}`)) { log.error('Invalid token endpoint: ' + tokenEndpoint); res.status(400) res.json({ error: 'Invalid token endpoint' }); From fbde18ab86f27812a219353d95a9268c152f06dd Mon Sep 17 00:00:00 2001 From: Aleksandar Belchev Date: Wed, 30 Apr 2025 10:20:02 +0300 Subject: [PATCH 13/13] WIP --- api/server.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/server.js b/api/server.js index a9c2e46..d13dd5e 100644 --- a/api/server.js +++ b/api/server.js @@ -198,12 +198,6 @@ app.post('/token', (req, res) => { "&"; }); - if (!tokenEndpoint.includes(`${HOST}:${PORT}`)) { - log.error('Invalid token endpoint: ' + tokenEndpoint); - res.status(400) - res.json({ error: 'Invalid token endpoint' }); - } - var headers = { 'content-type' : 'application/x-www-form-urlencoded' };