Skip to content

feat: restyle login page to match Bluesky OAuth UI#10

Open
Kzoeps wants to merge 4 commits intomainfrom
bsky-signin-page
Open

feat: restyle login page to match Bluesky OAuth UI#10
Kzoeps wants to merge 4 commits intomainfrom
bsky-signin-page

Conversation

@Kzoeps
Copy link
Contributor

@Kzoeps Kzoeps commented Mar 4, 2026

Summary

  • Restyle the auth-service login page (GET /oauth/authorize) from a basic centered-card layout to the Bluesky @atproto/oauth-provider-ui split-panel design
  • CSS custom property token system with 15-stop contrast scale and automatic dark mode support
  • atproto-style input components (InputContainer pattern with icons and focus rings), admonition error/success messages

Changes

Single file changed: packages/auth-service/src/routes/login-page.ts (only renderLoginPage() — route handler untouched)

Layout

  • Split-panel: left title panel with "Sign in with Certified" branding, right form panel
  • Responsive: stacks vertically on mobile (<768px), side-by-side on desktop
  • Title panel content right-aligned on desktop, text left-aligned within

Branding

  • "Sign in with Certified" as plain <h1> text (no static SVG/PNG assets)
  • Logo conditional on b.logo_uri from OAuth client metadata (no hardcoded /static/ references)
  • Subtitle: "Sign in for the interop using Certified"

Bug fixes included

  • Recovery link now passes actual PAR request_uri instead of pdsPublicUrl
  • Dark mode placeholder uses CSS token (var(--color-text-light)) instead of hardcoded gray
  • min-width: 100% instead of 100vw (fixes horizontal scrollbar on Windows/Linux)
  • Conditional autofocus — email input on email step, OTP input on OTP step

What's preserved

  • All existing functionality: email OTP flow, social login, recovery link, login_hint auto-send
  • Route handler logic unchanged
  • renderLoginPage signature unchanged (only requestUri added for recovery link fix)
login page recovery page

Summary by CodeRabbit

  • New Features

    • Two-panel login and recovery pages with left branding panel
    • Preserved request URI through the client flow
    • Client-side OTP step handling, show/hide social section, and inline success/error messaging
  • Style

    • Overhauled layout, spacing and centralized CSS token theming (dark/light, responsive)
    • Updated button text/capitalization and improved ARIA attributes for icons/images
  • Bug Fixes

    • Unified, consistent error banners across flows

@vercel
Copy link

vercel bot commented Mar 4, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
epds-demo Ready Ready Preview, Comment Mar 4, 2026 4:44pm

Request Review

@coderabbitai
Copy link

coderabbitai bot commented Mar 4, 2026

Warning

Rate limit exceeded

@Kzoeps has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 21 minutes and 11 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6e72a91f-cc6e-491b-99c0-c0477a810c67

📥 Commits

Reviewing files that changed from the base of the PR and between ec53fa2 and 35e1074.

📒 Files selected for processing (3)
  • .beads/issues.jsonl
  • packages/auth-service/src/routes/login-page.ts
  • packages/auth-service/src/routes/recovery.ts
📝 Walkthrough

Walkthrough

Login and recovery page renderers were migrated to a token-based two-panel layout; requestUri is now passed into the login renderer and carried to the client; CSS was centralized via getBaseCSS(); client-side OTP/error state and accessibility attributes were added.

Changes

Cohort / File(s) Summary
Login page renderer
packages/auth-service/src/routes/login-page.ts
Added requestUri parameter to renderLoginPage and its call sites; render output now embeds requestUri for client use; rewrote HTML/CSS to a two-panel layout with theme tokens; added ARIA attributes, updated button text/capitalization, and client-side email/OTP state & messaging.
Recovery, OTP, and error pages
packages/auth-service/src/routes/recovery.ts
Reworked recovery/OTP/error templates into unified two-panel layout (layout/title-panel/form-panel); introduced shared error/admonition rendering; replaced inline CSS with token-based getBaseCSS(); updated form markup, icons, and accessibility attributes.
Styling tokens / shared CSS
packages/auth-service/src/routes/* (CSS changes across login & recovery templates)
Centralized styling into getBaseCSS() token system; removed previous inline CSS constants and applied consistent theming, responsive behavior, and dark/light adjustments across pages.

Sequence Diagram(s)

sequenceDiagram
    participant Browser as Browser
    participant Auth as Auth Service
    participant DB as Database
    participant Email as Email/OTP Service

    Browser->>Auth: GET /login (optional request_uri)
    Auth-->>Browser: 200 HTML (renderLoginPage includes requestUri in client payload)
    Browser->>Auth: POST /login/start (email, csrf, requestUri)
    Auth->>DB: create OTP/session record
    Auth->>Email: request OTP delivery
    Email-->>User: deliver OTP (out-of-band)
    Browser->>Auth: POST /login/verify (otp, csrf, requestUri)
    Auth->>DB: verify OTP, create auth session
    Auth-->>Browser: 302 Redirect (to requestUri or client redirect)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped from form of one to two,

Tokens, ARIA, and a clearer view.
OTPs flutter, errors hum,
RequestUri carried where it must run.
A rabbit cheers — hop, code, and yum!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main change: restyling the login page to match Bluesky OAuth UI design, which aligns with the substantial layout, CSS, and UI component updates shown in the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bsky-signin-page

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/auth-service/src/routes/login-page.ts`:
- Around line 261-703: The file contains formatting issues flagged by Prettier
(CI failure); run your project's Prettier formatter against the login-page.ts
file (or run the repo-wide formatter) to reformat the CSS block including
selectors like :root, .input-container, .btn-primary, .title-panel, and
.otp-input, then stage and commit the formatted file so the CI Prettier check
passes; ensure you use the repo Prettier config (or npm/yarn script like
formatting or prettier --write) and push the commit.
- Around line 719-725: The markup and runtime code use inline style.display
toggles for visibility (e.g., the <div id="error-msg" class="admonition error"
style="display:none;"> and the sibling <span class="admonition-icon
admonition-icon-success" style="display:none;">, plus the runtime mutations that
set element.style.display around the login page logic), which violates the
project rule to use CSS classes; replace the static inline display attributes
with a default visibility class (e.g., add/remove a "hidden" or "inactive" class
on the `#error-msg` container and the .admonition-icon-success element) and change
any JS that does element.style.display = ... (the code that currently toggles
display at the login handling functions) to use element.classList.add/remove or
toggle with the chosen class so visibility is driven by CSS classes rather than
inline styles.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2bf36d5b-5f73-4de5-8680-acf2c50244a4

📥 Commits

Reviewing files that changed from the base of the PR and between 9351243 and f191466.

📒 Files selected for processing (2)
  • .beads/issues.jsonl
  • packages/auth-service/src/routes/login-page.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/auth-service/src/routes/recovery.ts (1)

362-730: Consider extracting getBaseCSS() to a shared auth-page style module.

The token/layout/admonition CSS here duplicates the same style block already present in packages/auth-service/src/routes/login-page.ts (see context snippet lines 260-700). Centralizing this will prevent style drift between login and recovery pages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth-service/src/routes/recovery.ts` around lines 362 - 730, The
getBaseCSS() block in recovery.ts duplicates the same CSS used in login-page.ts;
extract it into a single shared style module (e.g., export a constant or
function like BASE_AUTH_CSS or getBaseCSS from a new auth-page-styles module)
and replace the local getBaseCSS() in both recovery.ts and login-page.ts with
imports from that shared module (update any references to getBaseCSS to use the
imported symbol and remove the duplicate definitions).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/auth-service/src/routes/recovery.ts`:
- Line 315: Replace the inline style on the form element in recovery.ts (the
<form method="POST" action="/auth/recover" style="display:inline;">) with a CSS
class (e.g., class="inline-form") and add the corresponding rule in the
component/site stylesheet (e.g., .inline-form { display: inline; }) or use an
existing utility class; update the form element to use that class and remove the
style attribute so styling is centralized in CSS.
- Around line 298-307: The OTP input (input id="code", name="code", class
"otp-input") lacks an explicit accessible label; add a visible or
visually-hidden <label for="code">One-time code</label> or an equivalent
aria-labelledby referencing a label element, ensuring the label text describes
the purpose (e.g., "One-time code" or "Verification code") and keep the input's
id "code" to maintain association; update the markup near the input-inner block
so screen readers receive a proper accessible name.

---

Nitpick comments:
In `@packages/auth-service/src/routes/recovery.ts`:
- Around line 362-730: The getBaseCSS() block in recovery.ts duplicates the same
CSS used in login-page.ts; extract it into a single shared style module (e.g.,
export a constant or function like BASE_AUTH_CSS or getBaseCSS from a new
auth-page-styles module) and replace the local getBaseCSS() in both recovery.ts
and login-page.ts with imports from that shared module (update any references to
getBaseCSS to use the imported symbol and remove the duplicate definitions).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 50b85954-654b-4a8e-a7c7-5981ac36da1f

📥 Commits

Reviewing files that changed from the base of the PR and between f191466 and 90239a4.

📒 Files selected for processing (2)
  • .beads/issues.jsonl
  • packages/auth-service/src/routes/recovery.ts

- Split-panel layout: left title panel with 'Sign in with Certified', right form panel
- CSS custom property token system with 15-stop contrast scale and dark mode
- atproto-style input components (InputContainer pattern with icons and focus rings)
- Admonition error/success messages with proper contrast (WCAG AA)
- Logo conditional on client metadata logo_uri (no hardcoded static assets)
- Recovery link passes actual PAR request_uri
- Conditional autofocus (email step vs OTP step)
- Resend button disabled during auto-send to prevent race condition
- Responsive: stacks vertically on mobile (<768px)
- Split-panel layout matching login page design
- Same CSS token system, input components, and admonition errors
- Form-actions row: 'Back to sign in' left, submit button right (auto-width)
- OTP label, autocomplete='email', proper field labels
- Dark mode support with inverted contrast scale
- Class-based visibility (.inline-form) instead of inline styles
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/auth-service/src/routes/login-page.ts (1)

211-213: Harden logo_uri handling with protocol allowlisting.

escapeHtml() protects HTML context, but logo_uri is still metadata-controlled. Consider validating URL protocol (https:; optionally http: in dev) before rendering the <img> and dropping invalid values.

🔒 Suggested hardening patch
-  const logoHtml = b.logo_uri
-    ? `<img src="${escapeHtml(b.logo_uri)}" alt="${escapeHtml(appName)}" class="client-logo" aria-hidden="true">`
-    : ''
+  const logoUri = (() => {
+    if (!b.logo_uri) return null
+    try {
+      const u = new URL(b.logo_uri)
+      if (u.protocol === 'https:') return u.toString()
+      if (process.env.NODE_ENV === 'development' && u.protocol === 'http:') return u.toString()
+      return null
+    } catch {
+      return null
+    }
+  })()
+
+  const logoHtml = logoUri
+    ? `<img src="${escapeHtml(logoUri)}" alt="${escapeHtml(appName)}" class="client-logo" aria-hidden="true">`
+    : ''
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth-service/src/routes/login-page.ts` around lines 211 - 213, The
generated logoHtml currently uses b.logo_uri directly (escaped) which can still
allow unsafe protocols; before constructing logoHtml, validate b.logo_uri using
the URL constructor (or a safe URL-parse helper) and only allow protocols like
"https:" (optionally "http:" behind a dev flag), otherwise treat it as
missing/invalid and set logoHtml to ''. Update the code around
logoHtml/escapeHtml to perform this protocol check (e.g., validate b.logo_uri,
ensure it starts with an allowed protocol, then call escapeHtml(appName) and
build the <img> string) so invalid or non-allowed-scheme URIs are dropped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/auth-service/src/routes/recovery.ts`:
- Around line 317-323: The resend-code form is nested inside the verify form
which produces invalid HTML; update the template in recovery.ts (where the
verify form is rendered) so the Resend Code form (the form with hidden csrf,
request_uri, email and the "Resend Code" submit button) is moved entirely
outside the verify/<verify form> block or converted to a separate button/link
that performs POST to /auth/recover (e.g., a standalone form sibling to the
verify form or an anchor that triggers a POST), ensuring no <form> tags are
nested and that the inputs (csrf, request_uri, email) remain included for the
resend action.

---

Nitpick comments:
In `@packages/auth-service/src/routes/login-page.ts`:
- Around line 211-213: The generated logoHtml currently uses b.logo_uri directly
(escaped) which can still allow unsafe protocols; before constructing logoHtml,
validate b.logo_uri using the URL constructor (or a safe URL-parse helper) and
only allow protocols like "https:" (optionally "http:" behind a dev flag),
otherwise treat it as missing/invalid and set logoHtml to ''. Update the code
around logoHtml/escapeHtml to perform this protocol check (e.g., validate
b.logo_uri, ensure it starts with an allowed protocol, then call
escapeHtml(appName) and build the <img> string) so invalid or non-allowed-scheme
URIs are dropped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 939a3e51-ef71-42ac-83ac-54e5dadce691

📥 Commits

Reviewing files that changed from the base of the PR and between 90239a4 and f602900.

📒 Files selected for processing (3)
  • .beads/issues.jsonl
  • packages/auth-service/src/routes/login-page.ts
  • packages/auth-service/src/routes/recovery.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/auth-service/src/routes/recovery.ts (1)

361-731: Consider extracting shared CSS to a common utility.

The getBaseCSS() function contains ~370 lines of CSS that appears to be duplicated in login-page.ts. Both files define identical token systems, layout classes (.title-panel, .form-panel, .input-container), and component styles (.admonition, .btn-primary).

Consider extracting this to a shared module (e.g., src/ui/base-css.ts) that both files can import. This would:

  • Ensure visual consistency between login and recovery pages
  • Reduce maintenance burden when updating styles
  • Make it easier to catch accidental divergence
♻️ Example extraction
// src/ui/base-css.ts
export function getBaseCSS(): string {
  return `/* ... shared CSS ... */`
}
// recovery.ts
+import { getBaseCSS } from '../ui/base-css.js'
-function getBaseCSS(): string { ... }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/auth-service/src/routes/recovery.ts` around lines 361 - 731, The
getBaseCSS() function in recovery.ts duplicates ~370 lines of CSS already
present in login-page.ts; extract the shared CSS into a new module (e.g.,
src/ui/base-css.ts) that exports function getBaseCSS(): string returning the
common CSS, then replace the inline getBaseCSS implementations in both
recovery.ts and login-page.ts with imports from that module (update both files
to import { getBaseCSS } from 'src/ui/base-css' and remove the duplicated CSS
blocks) so both pages share the same canonical stylesheet and avoid divergence.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/auth-service/src/routes/recovery.ts`:
- Around line 361-731: The getBaseCSS() function in recovery.ts duplicates ~370
lines of CSS already present in login-page.ts; extract the shared CSS into a new
module (e.g., src/ui/base-css.ts) that exports function getBaseCSS(): string
returning the common CSS, then replace the inline getBaseCSS implementations in
both recovery.ts and login-page.ts with imports from that module (update both
files to import { getBaseCSS } from 'src/ui/base-css' and remove the duplicated
CSS blocks) so both pages share the same canonical stylesheet and avoid
divergence.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e8680d0-7cc3-41be-903c-062c1984dc8c

📥 Commits

Reviewing files that changed from the base of the PR and between f602900 and ec53fa2.

📒 Files selected for processing (2)
  • .beads/issues.jsonl
  • packages/auth-service/src/routes/recovery.ts

Kzoeps added 2 commits March 4, 2026 22:43
- Add .hidden { display: none !important } utility class
- Replace all style="display:none" attributes with class="hidden"
- Replace all element.style.display JS mutations with classList.add/remove
- Per AGENTS.md: use CSS classes for visibility, not inline styles
@Kzoeps
Copy link
Contributor Author

Kzoeps commented Mar 4, 2026

@aspiers done. realized also that right now its mostly hardcoded. but with the css injection trusted clients should be able to change it. but it would also be nice to have some of it be configurable through the env vars like how bsky pds does it. but that would requires a lot more code and maybe will do it as another pr ( will create an issue for it as well ). and also another issue for the authorization page.

I guess theres also css repetition will fix that i the morning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants