Files

52 lines
9.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> Historical provider-based implementation: superseded by email-authentication.md, local-mail.md and the email login tests. Google/OIDC is no longer part of the application.
# Account access — implementation and validation
The application now contains admission services for hashed seven-day invitations, atomic invitation consumption, issuer/subject identity binding, and one-time administrator bootstrap. Administrative list operations expose account/invitation metadata only. Account disable revokes device credentials; reactivation does not restore them.
Device credentials are random 256-bit tokens stored only as SHA-256 hashes, with a 30-day inactivity deadline. Passive resolution does not renew them. A request filter replaces stale security contexts and rejects absent, ambiguous, expired or revoked cookies. Cookie issuance uses Secure, HttpOnly, SameSite=Lax, Path=/ and no Domain attribute.
Authenticated mode has a conditional Vaadin security chain, an OIDC client registration, a login form, and a success handler connecting provider validation to admission and device login. The existing guest setting remains enabled. Local callback and selected Flow browser paths are verified below; the remaining acceptance items are listed explicitly.
HTTP security-chain tests cover the public login form, protected reports, CSRF rejection and device-specific logout. Admission intent is bound to the saved authorization request and tested against mismatched state and replay.
The application security chain has also passed callback integration against the shared loopback OIDC issuer: invalid signature, issuer, audience, expiry and nonce are rejected; an unknown identity is denied; bootstrap creates exactly one administrator; subsequent ordinary login resolves that account. A service-issued invitation admits a distinct non-administrator account through the actual callback, reuse by another identity is denied, and the invited account can subsequently log in without an invitation. The admission handler is installed as Vaadins shared authentication success handler, because Vaadin overwrites a handler configured only through Spring OAuth2 login during initialization.
A CSRF-protected POST endpoint `/account/logout-all` revokes every device credential of the authenticated account, clears the current session/cookie and redirects to login. HTTP tests verify CSRF rejection and that a supplied foreign account parameter cannot affect another user. Settings now exposes both logout actions only outside guest mode and submits CSRF-protected forms. The browser removes owner-scoped work drafts and sends a storage event to stop other open work forms from persisting them again. JavaScript syntax checks pass; the normal-storage two-tab browser path is verified below; unavailable-storage behavior remains outstanding.
Explicit activity renewal is exposed through a CSRF-protected POST endpoint. Its HTTP test proves passive requests and missing-CSRF requests preserve the deadline, while valid activity renews both database timestamp and cookie lifetime. MainLayout installs visible-page trusted pointer/keyboard event reporting, throttled to one successful call per minute; heartbeat and timers do not invoke it. Real browser renewal is verified below.
An initial ADMIN-only Vaadin page lists account and invitation metadata, supports account disable/enable and invitation creation/revocation, and is linked from settings for administrators. Role-filter tests cover promotion, demotion and disabled accounts without trusting stale session authorities. The invitation dialog exposes a shareable link based on `werkjournal.public-url` (default `https://werkjournal.javagil.de`). Its token travels in the URL fragment, is copied into the login form and removed from browser history before login; it is not sent in the initial HTTP URL. Node checks cover valid/invalid fragment handling and URL cleanup. Invitation creation/revocation and account disable are browser-verified below. Invitation-link redemption by a second browser identity and rejection of its admin navigation are verified below.
The login page now uses the journal logo, a responsive petrol card and system dark mode. The optional credential field is hidden for existing-account login and shown for invitations/setup, including fragment imports. Login and HTTP security tests pass after this change; the mobile login card was visually inspected below.
OAuth2 login uses an explicit non-retaining authorized-client repository. Provider access/refresh credentials are not kept for later API calls; identity admission and the application device cookie remain unchanged. The signed callback integration test passes with this repository.
Session revocation across full page reloads is implemented: authenticated layouts remember the owner locally; a signed-out login page clears that owners drafts and notifies other tabs. Script regression covers owner isolation and preserves drafts on an authenticated login page; HTTP login tests pass. Activity renewal and fragment changes have now been browser-verified. Unavailable storage is covered by a script fixture; normal-storage logout and the production guest regression are browser-verified below. The production guest JAR startup smoke has passed. Real Google/proxy acceptance remains part of the authentication prototype. Recovery and account deletion remain separate pending implementation work.
## Local browser acceptance, 2026-09-09
Using the opt-in authenticated browser harness and its loopback signing issuer: initial setup reached Today, settings exposed account actions without a guest badge, Administration opened, a shareable invitation was created and then visibly revoked, and Sign out everywhere returned to the login form. Multiple-account isolation, draft cleanup across tabs, activity renewal observations, mobile visual inspection and guest regression still require browser acceptance.
A work draft containing a distinctive description was left open in one tab while Sign out everywhere was invoked from settings in another. The work tab switched to login. After a new ordinary login, opening a work form showed an empty description and no draft recovery offer. This verifies the normal-storage two-tab logout cleanup path; unavailable-storage behavior is not yet browser-accepted. The Today shortcut exposed a navigation assertion (fixed with QueryParameters), then a duplicate attach render closing its dialog; both fixes were subsequently verified against a restarted test server: the Today shortcut navigated to Work and kept the dated work form open.
At 390×844, settings showed a single-column layout and icon-only save action without horizontal overflow. Ordinary Sign out returned to a visually inspected responsive login card with all controls fitting the viewport. Viewport was reset afterward.
Browser account-disable acceptance: disabling the signed-in local administrator redirected to login. An ordinary OIDC login for the same provider identity was then denied with the generic login error. No protected dashboard was rendered. This fixture was stopped after the destructive test.
Second-identity browser acceptance: an administrator created an invitation, signed out, and the link fragment was opened in a new local tab. Invitation mode was selected automatically; choosing the second test identity admitted it. Direct navigation to admin was then refused without exposing account metadata. Opening a fragment on an already loaded login page revealed missing hashchange handling; this was added, with syntax validation, but still needs the refreshed browser fixture for regression.
Repeatable JavaScript regression: `node --test tests/frontend/login-invitation.test.cjs` passes. It executes the production script with a minimal DOM fixture and covers initial invitation import, later hashchange import, URL cleanup and malformed tokens. The CI command now runs these checks via `tools/test-frontend` after building the production frontend and before smoke/release/deployment. The helper reuses Vaadins downloaded Node runtime (or an installed Node fallback) and fails the build if no runtime exists.
Production-JAR guest browser regression: settings and Today remain accessible without login, guest badge is shown, account actions/admin link are absent, Reading increments to 1, and the Today shortcut opens a work form which successfully saves two hours with the default customer/project. The UI then lists the entry and a 2 h day total. Tested against an isolated in-memory guest database.
`node --test tests/frontend/*.test.cjs` also passes activity/reporting checks: no request at installation, no request for untrusted or hidden-page events, one successful request per minute, retry on subsequent interaction after network failure, and CSRF-protected logout submission even when local storage throws. These are script-level fixtures, not browser-network observations.
Revoked-device script regression now covers the actual 401 branch: only the current owners draft keys are removed, another accounts drafts and theme preference remain, the cross-tab logout event is emitted, and navigation preserves an application context path. `node --test tests/frontend/*.test.cjs` passes.
Real browser activity acceptance: after local OIDC login, the harness observed last_activity 1788939107. Clicking Reading +1 through the browser changed it to 1788939131. This verifies the actual MainLayout event listener, CSRF request and database renewal together. The observer writes only the maximum timestamp, no credentials or account data.
Refreshed-browser fragment regression: navigating from the already loaded `/login` page to a syntactically valid dummy invitation fragment selected Invitation and exposed the credential field without a full-page reload. This verifies hashchange handling, not admission of the dummy token.
Final local release validation: `tools/ci-jvm` completed successfully with operations tests, JVM tests, six frontend checks and production smoke. Public Google/proxy acceptance remains in auth-spike; guest mode is still the deployment default.