feat(radius): Android WLAN profile with a per-device app password #1566

Merged
k.renner merged 5 commits from feat/dot1x-app-passwords into main 2026-11-05 12:19:12 +00:00
Member

Android one-click setup on wlan.renner.uno without a password prompt.

  • authentik: new OAuth2 application wlan-setup (confidential, authorization code, scope goauthentik.io/api, redirect https://wlan.renner.uno/android/callback). The dot1x-password stage also accepts app passwords (TokenBackend).
  • Secret: ESO Password generator wlan-setup-oauth (CreatedOnce); the worker reads it for the blueprint, wlan-setup for the code exchange (client_secret_basic).
  • wlan-setup: /android/install starts the authorization; /android/callback exchanges the code, creates the app password wlan-android-<user>-<timestamp>-<random> with the user's own access token and returns the Passpoint profile in the response to that GET. Nothing is stored on the page. Deleting the app password in authentik removes the device.
  • Expiry when unused: new app passwords start with 29 days (below the 30-day API cap). The expression policy dot1x-app-password-renew on the dot1x-login binding renews the app password used for an 802.1X login to 60 days. authentik rejects expired app passwords and deletes them in clean_expired_models.
  • authentik calls go through an internal nginx location: headers from $arg_* (subrequests share the parent's variable cache), proxy_pass_request_headers off, subrequest_output_buffer_size 256k.
  • Reloader annotations moved to the Deployment metadata.
  • docs/radius/dot1x.md describes the flow and the expiry.

🤖 Generated with Claude Code

Android one-click setup on wlan.renner.uno without a password prompt. - **authentik**: new OAuth2 application `wlan-setup` (confidential, authorization code, scope `goauthentik.io/api`, redirect `https://wlan.renner.uno/android/callback`). The `dot1x-password` stage also accepts app passwords (`TokenBackend`). - **Secret**: ESO Password generator `wlan-setup-oauth` (CreatedOnce); the worker reads it for the blueprint, wlan-setup for the code exchange (client_secret_basic). - **wlan-setup**: `/android/install` starts the authorization; `/android/callback` exchanges the code, creates the app password `wlan-android-<user>-<timestamp>-<random>` with the user's own access token and returns the Passpoint profile in the response to that GET. Nothing is stored on the page. Deleting the app password in authentik removes the device. - **Expiry when unused**: new app passwords start with 29 days (below the 30-day API cap). The expression policy `dot1x-app-password-renew` on the `dot1x-login` binding renews the app password used for an 802.1X login to 60 days. authentik rejects expired app passwords and deletes them in `clean_expired_models`. - authentik calls go through an internal nginx location: headers from `$arg_*` (subrequests share the parent's variable cache), `proxy_pass_request_headers off`, `subrequest_output_buffer_size 256k`. - Reloader annotations moved to the Deployment metadata. - `docs/radius/dot1x.md` describes the flow and the expiry. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-11-05 11:52:21 +00:00
feat(radius): Android WLAN profile with a per-device app password
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
b1be3181ef
wlan.renner.uno signs the user in through the new authentik OAuth2
application wlan-setup (confidential, scope goauthentik.io/api). The
callback exchanges the code (client_secret_basic), creates an app password
with the user's own access token (10 years) and returns the Passpoint
profile carrying it in the response to the user's GET. The page asks for
no password and stores nothing; deleting the app password in authentik
removes the device.

- dot1x-password stage also accepts app passwords (TokenBackend).
- Client secret: ESO Password generator wlan-setup-oauth, read by the
  authentik worker (blueprint) and by wlan-setup.
- authentik calls go through an internal nginx location; headers come
  from $arg_* because subrequests share the parent's variable cache.
- Reloader annotations on the wlan-setup Deployment metadata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
k.renner requested review from review-bot 2026-11-05 11:56:45 +00:00
review-bot approved these changes 2026-11-05 12:00:41 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit b1be3181efb3fa8a084774653eddbbe911384f1c.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

This PR replaces the old flow, where the user typed a password and got a one-time token, with an authorization-code OAuth flow. On the callback, a new authentik app password is created for each Android device and returned straight away in a Passpoint profile. TokenBackend is added to the dot1x-password stage, and a CreatedOnce ESO client secret is added. The design holds up: the state cookie guards against CSRF, the callback checks the username against X-authentik-username, the access token lasts 5 minutes and stays on the server, and the client secret is alphanumeric, so it fits the $arg_cred path. I found no verified defect. The main risk is in how nginx subrequests handle authentik's responses (see Unverified). mergeable is true, no issue is linked, and no other open PR covers this.

⚪ Unverified

  • Subrequest responses may be too big for the buffer. wlan-setup-config.yaml:175-183 has no subrequest_output_buffer_size. njs subrequests buffer the reply in memory, and nginx's documented default buffer is 4k or 8k (one memory page). A longer reply gets cut off, so the callback throws and returns 502. GET /api/v3/core/users/me/ (passpoint.js, ak(r, "GET", "/api/v3/core/users/me/", …)) returns the session user's groups, avatar (often an initials SVG data URI) and system_permissions. For a superuser or a member of many groups, I suspect that is well over 4 KB. I could not check authentik's current response size. Setting subrequest_output_buffer_size 64k; in the /_ak location would remove the risk.
  • Client headers are forwarded to authentik, including Accept-Encoding. proxy_pass_request_headers is on by default, so the subrequest sends the browser's headers to authentik-server, apart from the five it overrides. If authentik's server compresses responses when the client sends Accept-Encoding: gzip, JSON.parse(reply.responseText) will fail on the compressed body. I did not check whether current authentik compresses API responses. Adding proxy_set_header Accept-Encoding ""; would be cheap insurance. It would also be cleaner not to forward the gate's X-authentik-* headers and the browser User-Agent.
  • Normal users may not be allowed to create tokens. POST /api/v3/core/tokens/ with intent: app_password is called with the user's own token. I did not check whether current authentik lets a user without extra permissions create their own tokens this way. If not, setup works for admins only. The docs only cover the group's maximum lifetime.

🟡 Suggestions

  • Two installs in the same second collide. In wlan-setup-config.yaml (passpoint.js callback), identifier is built from the username plus a timestamp with one-second resolution. Two installs by the same user in the same second, such as a double tap, use the same identifier, so authentik rejects the second create and the user gets a 502. Adding a few random characters (e.g. random().substring(0, 6)) to the identifier would avoid this.
  • Abandoned installs leave valid app passwords behind. In wlan-setup-config.yaml (passpoint.js callback), each successful callback creates a token that lasts 10 years. If the user cancels the Android installer or opens the link again, the earlier token stays valid and nobody can see it. The page text could tell users to delete unused wlan-android-* entries in authentik, or the callback could delete older tokens with the same prefix that have never been used.

🟢 Nits

  • wlan-setup-config.yaml:58 says "resolved once at start". That is true for upstream blocks, but if the authentik-server Service is ever recreated with a new ClusterIP, wlan-setup has to be restarted. It's fine to leave as is.

Tests

The repo has no test harness for these manifests or the njs code. The flow should be tried by hand on Android with a normal (non-admin) user and with an admin user. That would settle the response-size and token-permission questions above.

Recommendation

Looks reasonable — no blocking issues; comments above are optional.

🤖 **Automated review** — model `claude-opus-5-5`, commit `b1be3181efb3fa8a084774653eddbbe911384f1c`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary This PR replaces the old flow, where the user typed a password and got a one-time token, with an authorization-code OAuth flow. On the callback, a new authentik app password is created for each Android device and returned straight away in a Passpoint profile. `TokenBackend` is added to the `dot1x-password` stage, and a CreatedOnce ESO client secret is added. The design holds up: the state cookie guards against CSRF, the callback checks the username against `X-authentik-username`, the access token lasts 5 minutes and stays on the server, and the client secret is alphanumeric, so it fits the `$arg_cred` path. I found no verified defect. The main risk is in how nginx subrequests handle authentik's responses (see Unverified). `mergeable` is true, no issue is linked, and no other open PR covers this. ## ⚪ Unverified - **Subrequest responses may be too big for the buffer.** `wlan-setup-config.yaml:175-183` has no `subrequest_output_buffer_size`. njs subrequests buffer the reply in memory, and nginx's documented default buffer is 4k or 8k (one memory page). A longer reply gets cut off, so the callback throws and returns 502. `GET /api/v3/core/users/me/` (`passpoint.js`, `ak(r, "GET", "/api/v3/core/users/me/", …)`) returns the session user's groups, avatar (often an initials SVG data URI) and `system_permissions`. For a superuser or a member of many groups, I suspect that is well over 4 KB. I could not check authentik's current response size. Setting `subrequest_output_buffer_size 64k;` in the `/_ak` location would remove the risk. - **Client headers are forwarded to authentik, including `Accept-Encoding`.** `proxy_pass_request_headers` is on by default, so the subrequest sends the browser's headers to authentik-server, apart from the five it overrides. If authentik's server compresses responses when the client sends `Accept-Encoding: gzip`, `JSON.parse(reply.responseText)` will fail on the compressed body. I did not check whether current authentik compresses API responses. Adding `proxy_set_header Accept-Encoding "";` would be cheap insurance. It would also be cleaner not to forward the gate's `X-authentik-*` headers and the browser `User-Agent`. - **Normal users may not be allowed to create tokens.** `POST /api/v3/core/tokens/` with `intent: app_password` is called with the user's own token. I did not check whether current authentik lets a user without extra permissions create their own tokens this way. If not, setup works for admins only. The docs only cover the group's maximum lifetime. ## 🟡 Suggestions - **Two installs in the same second collide.** In `wlan-setup-config.yaml` (`passpoint.js` `callback`), `identifier` is built from the username plus a timestamp with one-second resolution. Two installs by the same user in the same second, such as a double tap, use the same identifier, so authentik rejects the second create and the user gets a 502. Adding a few random characters (e.g. `random().substring(0, 6)`) to the identifier would avoid this. - **Abandoned installs leave valid app passwords behind.** In `wlan-setup-config.yaml` (`passpoint.js` `callback`), each successful callback creates a token that lasts 10 years. If the user cancels the Android installer or opens the link again, the earlier token stays valid and nobody can see it. The page text could tell users to delete unused `wlan-android-*` entries in authentik, or the callback could delete older tokens with the same prefix that have never been used. ## 🟢 Nits - `wlan-setup-config.yaml:58` says "resolved once at start". That is true for upstream blocks, but if the authentik-server Service is ever recreated with a new ClusterIP, wlan-setup has to be restarted. It's fine to leave as is. ## Tests The repo has no test harness for these manifests or the njs code. The flow should be tried by hand on Android with a normal (non-admin) user and with an admin user. That would settle the response-size and token-permission questions above. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. <!-- pr-review:sha=b1be3181efb3fa8a084774653eddbbe911384f1c -->
claude-bot added 1 commit 2026-11-05 12:02:13 +00:00
fix(radius): wlan-setup authentik subrequests take large replies
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
c781e263fc
- subrequest_output_buffer_size 256k: users/me with groups, avatar and
  permissions exceeds the one-page default and was cut off.
- proxy_pass_request_headers off: browser headers (Accept-Encoding,
  User-Agent, X-authentik-*) no longer reach authentik.
- Random suffix on the app password identifier, so two installs in the
  same second do not collide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot dismissed review-bot's review 2026-11-05 12:02:13 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

claude-bot requested review from review-bot 2026-11-05 12:02:25 +00:00
review-bot approved these changes 2026-11-05 12:04:58 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit c781e263fc81036384b96a6b1a8bca23fe4c8d9c.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

Re-review against b1be318. One commit has landed since then, c781e26. It changes only the /_ak location and the app-password identifier in wlan-setup-config.yaml. My last review had no 🔴 points. The state of the earlier points:

  • Subrequest buffer size: fixed. subrequest_output_buffer_size 256k is now set in the /_ak location.
  • Client headers forwarded to authentik: fixed. proxy_pass_request_headers off means the browser's Accept-Encoding, User-Agent and X-authentik-* headers no longer reach authentik. Only the explicitly set Host, Cookie, Authorization, Content-Type and Accept headers are sent. nginx's proxy module still adds Content-Length for the POST bodies itself, so that setting does not drop it.
  • 🟡 Same-second collision: fixed. The identifier now ends in random().substring(0, 6), with anything outside [A-Za-z0-9] replaced by x. Every character fits a slug-style identifier and is safe in the /view_key/ path.
  • 🟡 Abandoned installs leave valid app passwords: not addressed. It was optional, so it stays closed.
  • ⚪ Normal users may not be allowed to create tokens: still unverified (see below). The new commit does not touch it.

The new changes introduce no defects. mergeable is true.

⚪ Unverified

  • Token creation by non-admin users: still open from the last review. The callback calls POST /api/v3/core/tokens/ with intent: app_password, using a normal user's own access token. I did not check whether current authentik allows that without extra permissions. A manual Android test with a non-admin account would settle it.

Tests

The repo has no test harness for this nginx/njs code. Running the Android flow by hand with a non-admin user would confirm both the buffer fix and the open token-permission question.

Recommendation

Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commit adds none.

🤖 **Automated review** — model `claude-opus-5-5`, commit `c781e263fc81036384b96a6b1a8bca23fe4c8d9c`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary Re-review against b1be318. One commit has landed since then, c781e26. It changes only the `/_ak` location and the app-password identifier in `wlan-setup-config.yaml`. My last review had no 🔴 points. The state of the earlier points: - **Subrequest buffer size:** fixed. `subrequest_output_buffer_size 256k` is now set in the `/_ak` location. - **Client headers forwarded to authentik:** fixed. `proxy_pass_request_headers off` means the browser's `Accept-Encoding`, `User-Agent` and `X-authentik-*` headers no longer reach authentik. Only the explicitly set Host, Cookie, Authorization, Content-Type and Accept headers are sent. nginx's proxy module still adds Content-Length for the POST bodies itself, so that setting does not drop it. - 🟡 **Same-second collision:** fixed. The identifier now ends in `random().substring(0, 6)`, with anything outside `[A-Za-z0-9]` replaced by `x`. Every character fits a slug-style identifier and is safe in the `/view_key/` path. - 🟡 **Abandoned installs leave valid app passwords:** not addressed. It was optional, so it stays closed. - ⚪ **Normal users may not be allowed to create tokens:** still unverified (see below). The new commit does not touch it. The new changes introduce no defects. `mergeable` is true. ## ⚪ Unverified - **Token creation by non-admin users:** still open from the last review. The callback calls `POST /api/v3/core/tokens/` with `intent: app_password`, using a normal user's own access token. I did not check whether current authentik allows that without extra permissions. A manual Android test with a non-admin account would settle it. ## Tests The repo has no test harness for this nginx/njs code. Running the Android flow by hand with a non-admin user would confirm both the buffer fix and the open token-permission question. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commit adds none. <!-- pr-review:sha=c781e263fc81036384b96a6b1a8bca23fe4c8d9c -->
claude-bot requested review from review-bot 2026-11-05 12:05:29 +00:00
claude-bot added 1 commit 2026-11-05 12:05:30 +00:00
docs(radius): app password lifetime cap is 30 days without the attribute
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline is pending
29da21c28f
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot dismissed review-bot's review 2026-11-05 12:05:30 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

review-bot approved these changes 2026-11-05 12:09:57 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 29da21c28f3ead2f8ee17dc37e82c067ed9c3b20.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

This is a re-review against c781e26. One commit has landed since then, 29da21c, and it changes only docs/radius/dot1x.md. It changes the stated default cap on app password lifetime from "30 minutes" to "30 days". It names the error that appears when a token's lifetime is over the cap (Token expires exceeds maximum lifetime). It also says outright that creating the app password needs no admin rights. No code or manifest changed. My earlier reviews had no 🔴 points. The state of the earlier points:

  • Subrequest buffer size: fixed in c781e26 and unchanged since.
  • Browser headers forwarded to authentik: fixed in c781e26 and unchanged since.
  • 🟡 Two installs in the same second collide: fixed in c781e26 and unchanged since.
  • 🟡 Abandoned installs leave valid app passwords behind: it was optional and wasn't addressed, so it stays closed.
  • ⚪ Normal users may not be allowed to create tokens: still unverified. The docs now say no admin rights are needed (docs/radius/dot1x.md:168-169), but the PR gives no evidence for it (see below).

mergeable is true. There were no author replies or discussion comments to answer.

⚪ Unverified

  • Can a normal user create a token? docs/radius/dot1x.md:168-169 now says "Creating the app password needs no admin rights." Nothing in this PR backs that up, and I can't check from the repository how current authentik behaves when a normal user calls POST /api/v3/core/tokens/. A manual Android run with a non-admin account would settle it. If the claim is wrong, Android setup fails with a 502 for normal users.
  • Default cap of 30 days. docs/radius/dot1x.md:165 says that without the group attribute the cap is 30 days. That is a statement about authentik's current defaults, and I couldn't check it here. It affects only the docs: the setup still needs the days=3650 attribute either way.
  • Recent commits on main. I couldn't list them because no commit-listing tool was available. Since mergeable is true and the new commit is docs-only, there is no sign of a conflict.

Tests

The repo has no test harness for this nginx/njs code. The flow still needs a manual Android test with a non-admin user to confirm the new docs claim about token permissions.

Recommendation

Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commit, which changes only docs, adds none.

🤖 **Automated review** — model `claude-opus-5-5`, commit `29da21c28f3ead2f8ee17dc37e82c067ed9c3b20`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary This is a re-review against c781e26. One commit has landed since then, 29da21c, and it changes only `docs/radius/dot1x.md`. It changes the stated default cap on app password lifetime from "30 minutes" to "30 days". It names the error that appears when a token's lifetime is over the cap (`Token expires exceeds maximum lifetime`). It also says outright that creating the app password needs no admin rights. No code or manifest changed. My earlier reviews had no 🔴 points. The state of the earlier points: - Subrequest buffer size: fixed in c781e26 and unchanged since. - Browser headers forwarded to authentik: fixed in c781e26 and unchanged since. - 🟡 Two installs in the same second collide: fixed in c781e26 and unchanged since. - 🟡 Abandoned installs leave valid app passwords behind: it was optional and wasn't addressed, so it stays closed. - ⚪ Normal users may not be allowed to create tokens: still unverified. The docs now say no admin rights are needed (`docs/radius/dot1x.md:168-169`), but the PR gives no evidence for it (see below). `mergeable` is true. There were no author replies or discussion comments to answer. ## ⚪ Unverified - **Can a normal user create a token?** `docs/radius/dot1x.md:168-169` now says "Creating the app password needs no admin rights." Nothing in this PR backs that up, and I can't check from the repository how current authentik behaves when a normal user calls `POST /api/v3/core/tokens/`. A manual Android run with a non-admin account would settle it. If the claim is wrong, Android setup fails with a 502 for normal users. - **Default cap of 30 days.** `docs/radius/dot1x.md:165` says that without the group attribute the cap is 30 days. That is a statement about authentik's current defaults, and I couldn't check it here. It affects only the docs: the setup still needs the `days=3650` attribute either way. - **Recent commits on `main`.** I couldn't list them because no commit-listing tool was available. Since `mergeable` is true and the new commit is docs-only, there is no sign of a conflict. ## Tests The repo has no test harness for this nginx/njs code. The flow still needs a manual Android test with a non-admin user to confirm the new docs claim about token permissions. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points, and the new commit, which changes only docs, adds none. <!-- pr-review:sha=29da21c28f3ead2f8ee17dc37e82c067ed9c3b20 -->
claude-bot added 1 commit 2026-11-05 12:11:15 +00:00
feat(radius): WLAN app passwords expire 60 days after last use
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline failed
21625685b4
New app passwords start with 29 days (below the 30-day API cap, so no
group attribute is needed). The expression policy dot1x-app-password-renew,
re-evaluated on the dot1x-login binding, renews the app password that
TokenBackend matched to 60 days on each 802.1X login (at most one write
per day; always passes, failure_result true). authentik rejects expired
app passwords and deletes them in clean_expired_models (every 5 min).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot dismissed review-bot's review 2026-11-05 12:11:16 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

claude-bot requested review from review-bot 2026-11-05 12:11:29 +00:00
review-bot approved these changes 2026-11-05 12:15:23 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 21625685b4b12079ceb9d14d60b1d4d033bad5e7.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Request a review from review-bot again for a fresh one.


Summary

This re-review covers one new commit since 29da21c, 2162568. It cuts the lifetime of new app passwords from 10 years to 29 days (LIFETIME_DAYS = 29 in passpoint.js). It adds the expression policy dot1x-app-password-renew, bound to the dot1x-login stage binding with re_evaluate_policies: true, which is meant to extend a used app password to 60 days. It also updates the docs.

My earlier reviews had no 🔴 points. State of the earlier points:

  • Subrequest buffer size: fixed in c781e26, unchanged since.
  • Browser headers forwarded to authentik: fixed in c781e26, unchanged since.
  • 🟡 Two installs in the same second collide: fixed in c781e26, unchanged since.
  • 🟡 Abandoned installs leave valid app passwords behind: closed earlier. The 29-day starting lifetime now resolves it in practice, because unused tokens expire.
  • ⚪ Normal users may not be allowed to create tokens: still unverified, and the new commit does not touch it.

The new commit adds no verified defect. Whether the renewal actually fires is uncertain (see Unverified). If it doesn't, every Android device drops off the WLAN after 29 days, and nothing in the logs would show it. mergeable is true. There are no author replies to answer.

⚪ Unverified

  • The renewal filter may never match, because request.user may be the wrong user. blueprints-configmap.yaml:1542 filters on user=request.user. The policy runs when the dot1x-login binding is re-evaluated, which is before the user-login stage has signed anyone in. In a re-evaluated stage-binding policy, the policy request's user is probably the HTTP request's user, i.e. anonymous at this point. The identified user would sit in the plan context as pending_user. If so, the update() touches zero rows and the token is never renewed. failure_result: true and the unconditional return True (:1547, :1558) also hide any error.
    • What I checked: only the blueprint. I could not check from the repo how current authentik fills request.user for re-evaluated binding policies.
    • Contrast: dot1x-has-vlan (:1567) also uses request.user, but it runs as an application policy after authentication, so it does not show what happens here.
    • Suggested fix: use request.context.get("pending_user"), or drop the user filter. The token pk comes from TokenBackend itself, so the user filter adds little.
  • The token reference may not reach the policy in the expected shape. :1537-1538 assumes TokenBackend stores auth_method == "token" and a dict with pk under auth_method_args["token"], and that the re-evaluated policy request includes the plan context. All of this depends on authentik internals (set_method, sanitize_dict and cleanse_dict on a key named token) that I could not read here. If the key is masked or the value is not a dict, the policy silently does nothing.
  • Normal users creating tokens: still open from the earlier reviews (docs/radius/dot1x.md:162).
  • Recent commits on main touching these files: I could not list them because no commit-listing tool was available. mergeable is true.

🟡 Suggestions

  • Log whether the renewal ran. blueprints-configmap.yaml:1540-1546: because failure_result: true hides errors and a zero-row update is silent, a broken renewal only shows up as devices disconnecting after 29 days. Logging the result of update() with ak_logger.info(...) would make the first real 802.1X login with an app password show whether renewal works.

Tests

The repo has no test harness for blueprints or njs. Before merging, someone should:

  1. Install the Android profile with a non-admin user (this also settles the open token-permission question).
  2. Connect once.
  3. Check in authentik that the token's expires moved from about 29 to about 60 days.

Recommendation

Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points and the new commit adds none, but the renewal path should be confirmed live before merging.

🤖 **Automated review** — model `claude-opus-5-5`, commit `21625685b4b12079ceb9d14d60b1d4d033bad5e7`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Request a review from `review-bot` again for a fresh one. --- ## Summary This re-review covers one new commit since 29da21c, 2162568. It cuts the lifetime of new app passwords from 10 years to 29 days (`LIFETIME_DAYS = 29` in `passpoint.js`). It adds the expression policy `dot1x-app-password-renew`, bound to the `dot1x-login` stage binding with `re_evaluate_policies: true`, which is meant to extend a used app password to 60 days. It also updates the docs. My earlier reviews had no 🔴 points. State of the earlier points: - **Subrequest buffer size:** fixed in c781e26, unchanged since. - **Browser headers forwarded to authentik:** fixed in c781e26, unchanged since. - 🟡 **Two installs in the same second collide:** fixed in c781e26, unchanged since. - 🟡 **Abandoned installs leave valid app passwords behind:** closed earlier. The 29-day starting lifetime now resolves it in practice, because unused tokens expire. - ⚪ **Normal users may not be allowed to create tokens:** still unverified, and the new commit does not touch it. The new commit adds no verified defect. Whether the renewal actually fires is uncertain (see Unverified). If it doesn't, every Android device drops off the WLAN after 29 days, and nothing in the logs would show it. `mergeable` is true. There are no author replies to answer. ## ⚪ Unverified - **The renewal filter may never match, because `request.user` may be the wrong user.** `blueprints-configmap.yaml:1542` filters on `user=request.user`. The policy runs when the `dot1x-login` binding is re-evaluated, which is before the user-login stage has signed anyone in. In a re-evaluated stage-binding policy, the policy request's user is probably the HTTP request's user, i.e. anonymous at this point. The identified user would sit in the plan context as `pending_user`. If so, the `update()` touches zero rows and the token is never renewed. `failure_result: true` and the unconditional `return True` (`:1547`, `:1558`) also hide any error. - **What I checked:** only the blueprint. I could not check from the repo how current authentik fills `request.user` for re-evaluated binding policies. - **Contrast:** `dot1x-has-vlan` (`:1567`) also uses `request.user`, but it runs as an application policy after authentication, so it does not show what happens here. - **Suggested fix:** use `request.context.get("pending_user")`, or drop the user filter. The token pk comes from `TokenBackend` itself, so the user filter adds little. - **The token reference may not reach the policy in the expected shape.** `:1537-1538` assumes `TokenBackend` stores `auth_method == "token"` and a dict with `pk` under `auth_method_args["token"]`, and that the re-evaluated policy request includes the plan context. All of this depends on authentik internals (`set_method`, `sanitize_dict` and `cleanse_dict` on a key named `token`) that I could not read here. If the key is masked or the value is not a dict, the policy silently does nothing. - **Normal users creating tokens:** still open from the earlier reviews (`docs/radius/dot1x.md:162`). - **Recent commits on `main` touching these files:** I could not list them because no commit-listing tool was available. `mergeable` is true. ## 🟡 Suggestions - **Log whether the renewal ran.** `blueprints-configmap.yaml:1540-1546`: because `failure_result: true` hides errors and a zero-row update is silent, a broken renewal only shows up as devices disconnecting after 29 days. Logging the result of `update()` with `ak_logger.info(...)` would make the first real 802.1X login with an app password show whether renewal works. ## Tests The repo has no test harness for blueprints or njs. Before merging, someone should: 1. Install the Android profile with a non-admin user (this also settles the open token-permission question). 2. Connect once. 3. Check in authentik that the token's `expires` moved from about 29 to about 60 days. ## Recommendation Looks reasonable — no blocking issues; comments above are optional. There were no earlier 🔴 points and the new commit adds none, but the renewal path should be confirmed live before merging. <!-- pr-review:sha=21625685b4b12079ceb9d14d60b1d4d033bad5e7 -->
claude-bot added 1 commit 2026-11-05 12:16:29 +00:00
feat(radius): log WLAN app password renewals
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
6c2c278f65
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
claude-bot dismissed review-bot's review 2026-11-05 12:16:29 +00:00
Reason:

New commits pushed, approval review dismissed automatically according to repository settings

Author
Member

Answers to the unverified points of review 1929, from authentik version/2026.8.5:

  • request.user in the renewal policy: authentik/flows/markers.py (ReevaluateMarker.process) builds the engine with PolicyEngine(self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user)) and then engine.request.context.update(plan.context). The policy sees the pending user, and auth_method / auth_method_args sit at the top level of request.context.
  • Shape of auth_method_args.token: TokenBackend.authenticate calls set_method("token", request, token=token), which stores cleanse_dict(sanitize_dict(kwargs)) (authentik/core/auth.py). sanitize_item turns a model into model_to_dict = {app, model_name, pk, name} with the UUID pk as hex (authentik/events/utils.py). cleanse_item recurses into dict values before the key check, so only the inner keys are tested and none of them is masked.
  • Non-admin token creation: tested against auth.renner.uno as claude-bot (is_superuser: false): POST /api/v3/core/tokens/ with intent: app_password returned 201, view_key 200. A 10-year expiry returned 400 Token expires exceeds maximum lifetime (cap now + 30 days), hence the 29-day start.
  • Logging: added in 6c2c278 (ak_logger.info("dot1x app password", token=..., renewed=count)).
Answers to the unverified points of review 1929, from authentik `version/2026.8.5`: - **`request.user` in the renewal policy**: `authentik/flows/markers.py` (`ReevaluateMarker.process`) builds the engine with `PolicyEngine(self.binding, plan.context.get(PLAN_CONTEXT_PENDING_USER, http_request.user))` and then `engine.request.context.update(plan.context)`. The policy sees the pending user, and `auth_method` / `auth_method_args` sit at the top level of `request.context`. - **Shape of `auth_method_args.token`**: `TokenBackend.authenticate` calls `set_method("token", request, token=token)`, which stores `cleanse_dict(sanitize_dict(kwargs))` (`authentik/core/auth.py`). `sanitize_item` turns a model into `model_to_dict` = `{app, model_name, pk, name}` with the UUID pk as hex (`authentik/events/utils.py`). `cleanse_item` recurses into dict values before the key check, so only the inner keys are tested and none of them is masked. - **Non-admin token creation**: tested against auth.renner.uno as `claude-bot` (`is_superuser: false`): `POST /api/v3/core/tokens/` with `intent: app_password` returned 201, `view_key` 200. A 10-year expiry returned 400 `Token expires exceeds maximum lifetime` (cap now + 30 days), hence the 29-day start. - **Logging**: added in 6c2c278 (`ak_logger.info("dot1x app password", token=..., renewed=count)`).
claude-bot requested review from review-bot 2026-11-05 12:16:44 +00:00
k.renner merged commit 1de63af07b into main 2026-11-05 12:19:12 +00:00
Sign in to join this conversation.