Skip to content

Commit e29cd77

Browse files
committed
feat(auth-ui): login app hardening + Issue #755 fixes + auth-flow findings
Squash of the rebuild branch's enterprise hardening (W1-W10: security, structure, UX), the datum-cloud/enhancements#755 staging-feedback fixes, and the auth-flow-sessions campaign findings (F1-F4). Verified on the real local Zitadel harness and diffed against the old apps/login. - Sign-in/session: account-switch destination preserved (M10); userAgent payload byte-matched to old so cloud-portal shows Device/OS again (M1/S1095); clickable account rows (M9); not-found returns 200 inline, no console 404 (F1). - SSO/IdP: fresh-IdP link falls back to the session user (J2); distinct already-linked vs expired-link errors (J1); linked-IdP badge + dedupe (M6); IdP buttons rendered only for configured providers (F2). - Device/CLI: legacy /ui/v2/login/device query passthrough + post-login auto-complete so datumctl finishes without a second click (M8); consent layout (M7). - MFA: skippable nudge suppressed on account-switch while preserving forced MFA (M10); decorative authenticator icon (F4); deduped loginName query param (F3). - Design: dark-mode toggle (M4); SVG signup assets (M3); colors use datum-ui original tokens with the cypress-axe color-contrast rule deferred to datum-ui. Out of scope (infra): M2 logout post_logout_redirect_uri (Pulumi + Envoy).
1 parent 3227d2e commit e29cd77

318 files changed

Lines changed: 14675 additions & 4290 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dependency-cruiser.cjs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// .dependency-cruiser.cjs
2+
module.exports = {
3+
forbidden: [
4+
{
5+
// The composition seam: only server/composition.ts may reach across into a provider
6+
// adapter. A provider package composing its OWN cohesive sibling modules
7+
// (the zitadel monolith was decomposed into capability modules under
8+
// providers/zitadel/ — index.ts delegates to user/settings/session/… which share
9+
// mappers/transport) is intra-package composition, not a cross-boundary import, so it
10+
// is exempted via the `from` pathNot. The guard still flags every import that reaches
11+
// into providers/ from OUTSIDE the providers tree (e.g. select.server.ts).
12+
// Exemptions:
13+
// - Test files (`__tests__/` and `*.{test,spec}.{ts,tsx}`) legitimately import the
14+
// fake provider directly to drive service/route units against a deterministic
15+
// in-process double. This is test wiring, not production cross-boundary coupling.
16+
// - `app/modules/auth/select.server.ts` is the provider registry seam: the
17+
// single concrete-provider selection site (zitadel|fake), a legitimate second
18+
// composition seam alongside server/composition.ts. It is the one place outside the
19+
// providers tree allowed to name a concrete provider.
20+
name: 'only-composition-imports-providers',
21+
severity: 'error',
22+
from: {
23+
pathNot:
24+
'app/server/composition\\.ts$|^app/modules/auth/providers/|__tests__/|\\.(test|spec)\\.(ts|tsx)$|^app/modules/auth/select\\.server\\.ts$',
25+
},
26+
to: { path: 'app/modules/auth/providers/' },
27+
},
28+
{
29+
name: 'resources-not-server-edge',
30+
severity: 'error',
31+
from: { path: 'app/resources/' },
32+
to: { path: 'app/server/edge/' },
33+
},
34+
{
35+
name: 'shared-is-leaf',
36+
severity: 'error',
37+
from: { path: 'app/shared/' },
38+
to: { path: 'app/(routes|resources|modules|server|components)/' },
39+
},
40+
{
41+
// `app/routes/paths.ts` is a pure typed-path constant module (URL string builders,
42+
// no route/loader/action behavior). Resources importing it for redirect targets is a
43+
// constant dependency, not a layering violation, so it is exempted from this guard.
44+
name: 'no-routes-from-resources',
45+
severity: 'error',
46+
from: { path: 'app/resources/' },
47+
to: { path: 'app/routes/', pathNot: 'app/routes/paths\\.ts$' },
48+
},
49+
{
50+
name: 'no-circular',
51+
severity: 'error',
52+
from: {},
53+
to: { circular: true },
54+
},
55+
],
56+
options: {
57+
tsConfig: { fileName: 'tsconfig.json' },
58+
doNotFollow: { path: 'node_modules' },
59+
},
60+
};

.github/CODEOWNERS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Code review ownership for datum-cloud/auth-ui.
2+
# Default owner for everything in the repo.
3+
* @datum-cloud/portals
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
name: Bug report
3+
about: Report a defect in auth-ui (a reproducible, non-security problem)
4+
title: ""
5+
labels: bug
6+
assignees: ""
7+
---
8+
9+
## Summary
10+
11+
A clear, one- or two-sentence description of the bug.
12+
13+
## Steps to reproduce
14+
15+
1. Go to '...'
16+
2. Sign in with provider '...'
17+
3. Click '...'
18+
4. See the error
19+
20+
## Expected vs actual
21+
22+
**Expected:** what you expected to happen.
23+
24+
**Actual:** what actually happened.
25+
26+
## Environment
27+
28+
- auth-ui image / commit sha: <!-- e.g. ghcr.io/datum-cloud/auth-ui:<sha>, or the sha shown in the footer -->
29+
- Browser & version: <!-- e.g. Chrome 126, Safari 17.5, Firefox 127 -->
30+
- Auth provider: <!-- `fake` (local dev) or `zitadel` -->
31+
- Deployment: <!-- local dev / staging / production -->
32+
33+
## Logs
34+
35+
Paste any relevant console output, network responses, or server logs.
36+
37+
> **Redact secrets and personal data** before pasting — strip tokens, cookies,
38+
> `Authorization` headers, email addresses, and login names.
39+
40+
```text
41+
(paste redacted logs here)
42+
```
43+
44+
## Security note
45+
46+
> If this is a **security vulnerability**, do **not** file it here. Report it
47+
> privately via [SECURITY.md](../../SECURITY.md)
48+
> [Report a vulnerability](https://github.com/datum-cloud/auth-ui/security/advisories/new).

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
blank_issues_enabled: false
2+
contact_links:
3+
- name: Report a security vulnerability
4+
url: https://github.com/datum-cloud/auth-ui/security/advisories/new
5+
about: Please report security issues privately, not as public issues.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
name: Feature request
3+
about: Propose a new capability or improvement for auth-ui
4+
title: ""
5+
labels: enhancement
6+
assignees: ""
7+
---
8+
9+
## Problem / Motivation
10+
11+
What problem does this solve, and for whom? Describe the user or operator pain
12+
point — not the solution. Link to any related issues or discussions.
13+
14+
## Proposed solution
15+
16+
The change you'd like to see. Be concrete: which routes, flows, or components
17+
would change, and what the resulting behavior should be.
18+
19+
## Alternatives considered
20+
21+
Other approaches you weighed, and why they were set aside (including doing
22+
nothing).
23+
24+
## Scope / impact
25+
26+
Check anything this proposal would touch — these areas carry extra review:
27+
28+
- [ ] **URLs / redirects** — adds or changes a route, redirect, or callback URL
29+
(URLs are byte-frozen; changes require the URL-resolution gate)
30+
- [ ] **Auth flows** — sign-in, MFA, IdP linking, session, or logout behavior
31+
- [ ] **Boundary layers**`routes``resources``modules``providers`,
32+
or the `server/` layer (subject to the architecture-fitness gate)
33+
- [ ] **Env / configuration** — adds or changes a validated env variable
34+
- [ ] None of the above (self-contained UI or internal change)
35+
36+
## Additional context
37+
38+
Mockups, references, or links that help evaluate the request.

.github/pull_request_template.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<!--
2+
Thanks for contributing to datum-cloud/auth-ui.
3+
Fill in each section. Keep the PR focused — one logical change per PR.
4+
-->
5+
6+
## Summary
7+
8+
What does this PR change, and why? Describe the behavior before and after.
9+
10+
## Linked issue
11+
12+
<!-- e.g. Closes #123 / Refs #456 -->
13+
14+
## Type
15+
16+
- [ ] feat — new user-facing capability
17+
- [ ] fix — bug fix
18+
- [ ] refactor — behavior-preserving restructure
19+
- [ ] chore — tooling, deps, or housekeeping
20+
- [ ] docs — documentation only
21+
- [ ] test — tests only
22+
- [ ] perf — performance improvement
23+
24+
## Checklist
25+
26+
- [ ] `bun run typecheck` passes
27+
- [ ] `bun run lint:ci` passes (no new `console.*` — guarded by the `no-console` rule)
28+
- [ ] `bun run test:unit` passes
29+
- [ ] **URLs are byte-frozen** — no route, redirect, or callback URL changed
30+
without going through the URL-resolution gate
31+
- [ ] **No raw PII in logs** — login names / emails are hashed (`hashActor`) or
32+
redacted; no secrets, tokens, or cookies logged
33+
- [ ] `CHANGELOG.md` `[Unreleased]` updated (if this change is user-facing)
34+
- [ ] Boundary layers respected (`routes``resources``modules`
35+
`providers`); `bun run lint:boundaries` passes if layers were touched
36+
37+
## Screenshots
38+
39+
<!-- For UI changes, include before/after screenshots. Delete this section otherwise. -->

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,6 @@ e2e-artifacts/
2323
# Internal working docs (planning/specs/issues — not part of the shipped repo)
2424
docs/issues/
2525
docs/superpowers/
26+
# Playwright run artifacts
27+
test-results/
28+
playwright-report/

CHANGELOG.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here.
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
## [0.1.0] — 2026-06-23
10+
11+
First public-release candidate. Covers the enterprise audit-remediation run
12+
(waves W1–W10): the safety-net + governance foundation (W1–W3), the
13+
component/route refactor and god-route split (W4–W7), type & PnP hardening (W8),
14+
error-handling completion (W9), and the perf/UX/a11y finalize gate (W10). All
15+
ten wave gates closed green; auth URLs are byte-frozen across the run.
16+
17+
### Added
18+
- MIT `LICENSE`, `SECURITY.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md`
19+
(Contributor Covenant v2.1), `CODEOWNERS`, and GitHub issue/PR templates
20+
(enterprise OSS governance).
21+
- Architecture-fitness gate (`dependency-cruiser`/`madge`), v8 coverage with a ratchet
22+
floor, byte-frozen URL-resolution e2e suite, and a visual-regression baseline.
23+
- `no-console` lint guard; client-bundle size budget (first-load gzip).
24+
- `<BackLink/>` predecessor navigation across the setup and password ceremony
25+
routes (authenticator, passkey, security-key, email, sms, mfa, password new/change).
26+
- TOTP enrollment QR code (`qrcode.react`) on the authenticator setup screen,
27+
with the manual secret key retained as a fallback.
28+
- Branding on the login-method screen, consistent with the other branded ceremony
29+
screens.
30+
- Pluggable rate-limit store interface with an env-gated `ioredis` sliding-window
31+
adapter (`RATE_LIMIT_REDIS_URL`); the in-memory store remains the default.
32+
- Accessibility regression guards: `axe-core` structural/aria unit tests,
33+
keyboard focus-order tests, and a `prefers-reduced-motion` test across the
34+
ceremony components, plus a one-time live `cypress-axe` contrast audit.
35+
- `webauthn-enroll` factory consolidating passkey and security-key enrollment.
36+
37+
### Changed
38+
- CSP `style-src` hardened to a per-request nonce (dropped `'unsafe-inline'`).
39+
- `ALLOW_IDP_UNLINK` now resolved through the validated env schema.
40+
- Error surface consolidated to the single inline `<AuthCeremony error>` banner;
41+
inline recovery affordances added for expired-session and unsupported-method states.
42+
- Architecture-fitness gate flipped from `warn` to `error` severity (legitimate
43+
composition/registry/path-constant seams exempted with justification).
44+
- `getLoginSettings`, `/accounts` auth-method lookups, the SSO callback/link RPCs,
45+
and session create/update were deduped/batched/memoized — fewer calls, identical
46+
observable output.
47+
48+
### Fixed
49+
- Rate-limit audit logs redact `loginName` via `hashActor` (no raw PII).
50+
- `last-used-login` cookie scoped to `/id`.
51+
- Contextless device-authorization requests (missing `user_code`) now redirect to
52+
`/device` instead of rendering an error page; stale/expired codes keep the
53+
tailored recovery screen.
54+
- `sso/link` validates `returnTo` against a same-origin allowlist and uses a
55+
`Link` to login in place of a GET form.
56+
57+
### Removed
58+
- Unused `@tanstack/react-query` and `@tanstack/react-virtual` dependencies.

CODE_OF_CONDUCT.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
# Contributor Covenant Code of Conduct
3+
4+
## Our Pledge
5+
6+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
7+
8+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
9+
10+
## Our Standards
11+
12+
Examples of behavior that contributes to a positive environment for our community include:
13+
14+
* Demonstrating empathy and kindness toward other people
15+
* Being respectful of differing opinions, viewpoints, and experiences
16+
* Giving and gracefully accepting constructive feedback
17+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
18+
* Focusing on what is best not just for us as individuals, but for the overall community
19+
20+
Examples of unacceptable behavior include:
21+
22+
* The use of sexualized language or imagery, and sexual attention or advances of any kind
23+
* Trolling, insulting or derogatory comments, and personal or political attacks
24+
* Public or private harassment
25+
* Publishing others' private information, such as a physical or email address, without their explicit permission
26+
* Other conduct which could reasonably be considered inappropriate in a professional setting
27+
28+
## Enforcement Responsibilities
29+
30+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
31+
32+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
33+
34+
## Scope
35+
36+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
37+
38+
## Enforcement
39+
40+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement via GitHub private vulnerability reporting (repository Security tab) or by contacting @datum-cloud/portals. All complaints will be reviewed and investigated promptly and fairly.
41+
42+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
43+
44+
## Enforcement Guidelines
45+
46+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
47+
48+
### 1. Correction
49+
50+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
51+
52+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
53+
54+
### 2. Warning
55+
56+
**Community Impact**: A violation through a single incident or series of actions.
57+
58+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
59+
60+
### 3. Temporary Ban
61+
62+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
63+
64+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
65+
66+
### 4. Permanent Ban
67+
68+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
69+
70+
**Consequence**: A permanent ban from any sort of public interaction within the community.
71+
72+
## Attribution
73+
74+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
75+
76+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
77+
78+
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
79+
80+
[homepage]: https://www.contributor-covenant.org
81+
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
82+
[Mozilla CoC]: https://github.com/mozilla/diversity
83+
[FAQ]: https://www.contributor-covenant.org/faq
84+
[translations]: https://www.contributor-covenant.org/translations
85+

0 commit comments

Comments
 (0)