diff --git a/flake.lock b/flake.lock index cbafc31..8de9cee 100644 --- a/flake.lock +++ b/flake.lock @@ -335,11 +335,11 @@ ] }, "locked": { - "lastModified": 1724595780, - "narHash": "sha256-c6XxFH+qo3SbstKAFLcvGn3GHVJxbuXE2VtBnrjBk10=", + "lastModified": 1737810569, + "narHash": "sha256-b3ymxmPuMPnAG6Z8FNErmKzjmUcQkXiTs6WkAE1qBkk=", "ref": "main", - "rev": "f2a3da5f2637a859897c136e650b88046a89f9fd", - "revCount": 4, + "rev": "af99e9e38fcbdd691c12aa6044cf831c8eea28b4", + "revCount": 6, "type": "git", "url": "https://git.pub.solar/pub-solar/keycloak-event-listener" }, diff --git a/tests/keycloak.nix b/tests/keycloak.nix index f97f661..ac0d51b 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -44,8 +44,11 @@ in import re import sys - def puppeteer_run(cmd): - client.succeed(f'puppeteer-run \'{cmd}\' ') + def puppeteer_succeed(cmd): + return client.succeed(f'puppeteer-run \'{cmd}\' ') + + def puppeteer_execute(cmd): + return client.execute(f'puppeteer-run \'{cmd}\' ') start_all() @@ -69,32 +72,39 @@ in client.wait_for_unit("system.slice") client.wait_for_file("/tmp/puppeteer.sock") - puppeteer_run('page.goto("https://auth.test.pub.solar")') - puppeteer_run('page.waitForNetworkIdle()') + ####### Registration ####### + + puppeteer_succeed('page.goto("https://auth.test.pub.solar")') + puppeteer_succeed('page.waitForNetworkIdle()') client.screenshot("initial") - puppeteer_run('page.locator("::-p-text(Sign in)").click()') - puppeteer_run('page.waitForNetworkIdle()') + puppeteer_succeed('page.locator("::-p-text(Sign in)").click()') + puppeteer_succeed('page.waitForNetworkIdle()') client.screenshot("sign-in") - puppeteer_run('page.locator("::-p-text(Register)").click()') - puppeteer_run('page.waitForNetworkIdle()') + puppeteer_succeed('page.locator("::-p-text(Register)").click()') + puppeteer_succeed('page.waitForNetworkIdle()') client.screenshot("register") - puppeteer_run('page.locator("[name=username]").fill("test-user")') - puppeteer_run('page.locator("[name=email]").fill("test-user@test.pub.solar")') - puppeteer_run('page.locator("[name=password]").fill("Password1234")') - puppeteer_run('page.locator("[name=password-confirm]").fill("Password1234")') + puppeteer_succeed('page.locator("[name=username]").fill("test-user")') + puppeteer_succeed('page.locator("[name=email]").fill("test-user@test.pub.solar")') + puppeteer_succeed('page.locator("[name=password]").fill("Password1234")') + puppeteer_succeed('page.locator("[name=password-confirm]").fill("Password1234")') client.screenshot("register-filled-in") - puppeteer_run('page.locator("input[type=submit][value=Register]").click()') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("after-register") + puppeteer_succeed('page.locator("input[type=submit][value=Register]").click()') + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("before-email-confirm") + + # Sometimes offlineimap errors out + # ERROR: [Errno 2] No such file or directory: '/home/test-user/.local/share/offlineimap' + client.succeed("${su "mkdir -p ~/.local/share/offlineimap"}") client.succeed("${su "offlineimap"}") client.succeed("${su "[ $(messages -s ~/Maildir/test-user@test.pub.solar/INBOX) -eq 1 ]"}") - puppeteer_run('page.locator("a::-p-text(Click here)").click()') - puppeteer_run('page.waitForNetworkIdle()') + puppeteer_succeed('page.locator("a::-p-text(Click here)").click()') + puppeteer_succeed('page.waitForNetworkIdle()') client.succeed("${su "offlineimap"}") client.succeed("${su "[ $(messages -s ~/Maildir/test-user@test.pub.solar/INBOX) -eq 2 ]"}") + mail_text = client.execute("${su "echo p | mail -Nf ~/Maildir/test-user@test.pub.solar/INBOX"}")[1] boundary_match = re.search('boundary="(.*)"', mail_text, flags=re.M) if not boundary_match: @@ -105,11 +115,94 @@ in print(url_match) if not url_match: sys.exit(1) - puppeteer_run(f'page.goto("{url_match.group(1)}")') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("email-confirmed") + puppeteer_succeed(f'page.goto("{url_match.group(1)}")') + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("registration-complete") - sys.exit(0) - time.sleep(1) + ####### Logout ####### + + puppeteer_succeed('page.locator("[data-testid=options-toggle]").click()') + puppeteer_succeed('page.locator("::-p-text(Sign out)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("logged-out") + + ####### Login plain ####### + + puppeteer_succeed('page.locator("[name=username]").fill("test-user")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + puppeteer_succeed('page.locator("::-p-text(Restart login)").click()') + + puppeteer_succeed('page.locator("[name=username]").fill("test-user")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + puppeteer_succeed('page.locator("[name=password]").fill("Password1234")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("logged-in") + + ####### Add TOTP ####### + + puppeteer_succeed('page.locator("::-p-text(Account security)").click()') + puppeteer_succeed('page.locator("::-p-text(Signing in)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("signing-in-settings") + + puppeteer_succeed('page.locator(`[data-testid="otp/create"]`).click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("TOTP-setup-qr") + + puppeteer_succeed('page.locator("::-p-text(Unable to scan?)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("TOTP-setup-manual") + + totp_secret_key = puppeteer_execute('(async () => { const el = await page.waitForSelector("#kc-totp-secret-key"); return el.evaluate(e => e.textContent); })()')[1] + + totp = client.execute(f'oathtool --totp -b "{totp_secret_key}"')[1].replace("\n", "") + + puppeteer_succeed(f'page.locator("[name=totp]").fill("{totp}")') + puppeteer_succeed('page.locator("[name=userLabel]").fill("My TOTP")') + client.screenshot("TOTP-form-filled") + puppeteer_succeed('page.locator("input[type=submit][value=Submit]").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("TOTP-added") + + ####### Login w/ TOTP ####### + + puppeteer_succeed('page.locator("[data-testid=options-toggle]").click()') + puppeteer_succeed('page.locator("::-p-text(Sign out)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("logged-out") + + puppeteer_succeed('page.locator("[name=username]").fill("test-user")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + puppeteer_succeed('page.locator("[name=password]").fill("Password1234")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("TOTP-login-form") + + print("Sleeping 30s to make sure we roll over into the next TOTP token") + time.sleep(30) + + totp = client.execute(f'oathtool --totp -b "{totp_secret_key}"')[1].replace("\n", "") + puppeteer_succeed(f'page.locator("[name=otp]").fill("{totp}")') + puppeteer_succeed('page.locator("::-p-text(Sign In)").click()') + + puppeteer_succeed('page.waitForNetworkIdle()') + client.screenshot("TOTP-signed-in") + + ####### Delete TOTP ####### + + puppeteer_succeed('page.locator(`[data-testid="otp/credential-list"] button::-p-text(Delete)`).click()') + puppeteer_succeed('page.waitForNetworkIdle()') + + puppeteer_succeed('page.locator("main").scroll({ scrollTop: 200 })') + client.screenshot("TOTP-deleted") ''; } diff --git a/tests/support/client.nix b/tests/support/client.nix index a86f5d9..2c863d3 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -17,16 +17,13 @@ in ]; security.polkit.enable = true; - services.xserver.enable = true; - services.xserver.displayManager.gdm.enable = true; - services.xserver.desktopManager.gnome.enable = true; - services.xserver.displayManager.autoLogin.enable = true; - services.xserver.displayManager.autoLogin.user = "test-user"; environment.systemPackages = [ puppeteer-run pkgs.alacritty pkgs.mailutils + pkgs.oath-toolkit + pkgs.firefox ]; services.getty.autologinUser = "test-user"; @@ -41,6 +38,7 @@ in wayland.windowManager.sway = { enable = true; + systemd.enable = true; extraSessionCommands = '' export WLR_RENDERER=pixman ''; diff --git a/tests/support/keycloak-realm-export/realm-export.json b/tests/support/keycloak-realm-export/realm-export.json index 63fd58c..2f4f7e8 100644 --- a/tests/support/keycloak-realm-export/realm-export.json +++ b/tests/support/keycloak-realm-export/realm-export.json @@ -483,6 +483,31 @@ "webAuthnPolicyPasswordlessAcceptableAaguids": [], "webAuthnPolicyPasswordlessExtraOrigins": [], "users": [ + { + "id" : "49fcf95e-6fb3-4430-a29a-506a8b20e77c", + "createdTimestamp" : 1673444664000, + "username" : "existing-user", + "enabled" : true, + "totp" : false, + "emailVerified" : true, + "firstName" : "Existing", + "lastName" : "Tester", + "email" : "existing-user@test.pub.solar", + "credentials" : [ { + "id" : "b9fbc30d-4269-49cc-a0ea-9170dc44a30c", + "type" : "password", + "createdDate" : 1673444664000, + "secretData" : "{\"value\":\"yxEZKVTeZlKufE5q4v0Hvxlggg2EaRta5zBtIMxialgwOHrQ3h4Hmre//uk9SlrEv2eqo4aH4bFgPDoktOTyHQ==\",\"salt\":\"d3mk1F43bvQrbV1D+jC1NQ==\",\"additionalParameters\":{}}", + "credentialData" : "{\"hashIterations\":27500,\"algorithm\":\"pbkdf2-sha256\",\"additionalParameters\":{}}" + } ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "realmRoles" : [ + "default-roles-test.pub.solar" + ], + "notBefore" : 0, + "groups" : [ ] + }, { "id": "a0a10fbb-2d1d-4bf1-918d-86659f7dcef1", "username": "service-account-admin-cli", diff --git a/tests/support/puppeteer-socket/src/index.mjs b/tests/support/puppeteer-socket/src/index.mjs index b3371d3..e05f320 100644 --- a/tests/support/puppeteer-socket/src/index.mjs +++ b/tests/support/puppeteer-socket/src/index.mjs @@ -16,12 +16,18 @@ const EXECUTABLE = process.env.EXECUTABLE || 'firefox'; }); const page = await firefoxBrowser.newPage(); - page.on('request', request => { - console.log(request.url()); - }); + // page.on('request', request => { + // console.log(`[puppeteer req] ${request.url()}`); + // }); - page.on('response', response => { - console.log(response.url()); + // page.on('response', response => { + // console.log(`[puppeteer res] ${response.url()}`); + // }); + + await page.setViewport({ + width: 1200, + height: 600, + deviceScaleFactor: 1, }); const server = http.createServer({}); @@ -41,9 +47,9 @@ const EXECUTABLE = process.env.EXECUTABLE || 'firefox'; const responseText = (() => { try { - return JSON.stringify({ data: val }); + return val.toString(); } catch (err) { - return JSON.stringify({ data: val.toString() }); + return val; } })();