Skip to content

Releases: hypercerts-org/ePDS

ePDS@0.7.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 20 Jul 15:04
7dc00a7

Who should read this release

Minor Changes

  • #197 ce25beb Thanks @aspiers! - Email delivery logs use a consistent recipient field.

    Affects: Operators

    Operators: All four email delivery paths now log the recipient as email: client-branded OTP, standard sign-in OTP, welcome OTP, and backup-email verification. Update structured-log queries for Sent client-branded OTP email to read email instead of to.

  • #203 2193515 Thanks @aspiers! - OTP send logs now record how long the email handoff took, the provider's message ID, and the SMTP server response.

    Affects: Operators

    Operators: every OTP email completion line (all four paths — client-branded, sign-in, welcome, backup-email verification) now carries elapsedMs, messageId, and smtpResponse alongside email. messageId lets these logs be joined to Resend delivery events; elapsedMs isolates a slow SMTP handoff from other causes of late-arriving codes. Failed sends log elapsedMs too, on the existing better-auth: failed to send OTP email error line.

  • #198 2bf9af4 Thanks @aspiers! - Optional email-event logging for operators who choose the third-party Resend service to send email; ePDS continues to support other email providers.

    Affects: Operators

    Operators: ePDS remains compatible with any SMTP provider and does not require Resend. Operators who already use Resend can opt in by registering https://<AUTH_HOSTNAME>/webhooks/resend for the documented email events and setting RESEND_WEBHOOK_SECRET; logs include delivery, open, complaint, suppression, and scheduling events. The route verifies each webhook, logs only events whose sender exactly matches SMTP_FROM, acknowledges unsupported click and inbound events without logging their payloads, and does not persist webhook data.

ePDS@0.6.5

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 14 Jul 13:09
fa5105a

Who should read this release

Patch Changes

  • #187 dcc9590 Thanks @aspiers! - Signing in through the demo no longer silently fails if you take a while to enter your emailed code.

    Affects: End users, Operators

    End users: If you requested a sign-in code, then took several minutes to fetch it from your email before entering it, the demo could fail at the last step with a generic "Authentication failed" message — even though your code was correct. That happened because the demo started its 10-minute sign-in timer before it sent the code, so it could forget the sign-in while a later-issued code was still valid. The demo now remembers your sign-in for up to an hour, and if it genuinely does time out you now see "Your sign-in took too long to finish. Please sign in again." instead of a message that made it look like you typed the code wrong.

    Operators: The demo client's oauth_state cookie maxAge is raised from 600 (10 min) to 60 * 60 (1 hour), matching the auth service's auth_flow row TTL. Previously, the cookie's timer started when the OAuth flow began, while the OTP's 600-second validity started only when the code was issued, so the cookie could expire first. The OAuth callback now maps a missing/expired state cookie to /?error=session_expired rather than /?error=auth_failed; session_expired renders as "Your sign-in took too long to finish. Please sign in again."

  • #187 e1c9d06 Thanks @aspiers! - Sign-in now stays active while you retrieve your emailed code and offers a clear way to restart if it times out.

    Affects: End users

    End users: Previously, taking more than about five minutes to enter a code could leave the surrounding sign-in expired even though the code was still valid. The code-entry page now keeps the sign-in active while it remains open. If browser suspension or a longer interruption still makes the sign-in unrecoverable, the page disables code entry and Use different email, hides Resend code, and shows Start over; clicking it returns to the app you came from so you can begin again.

ePDS@0.6.4

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 12 Jul 17:44
4fd3230

Who should read this release

Patch Changes

  • #189 008b143 Thanks @aspiers! - Failed one-time code attempts now appear in the server logs, split by reason and tagged with the account's email.

    Affects: Operators

    Operators: failed OTP verifications are now logged under the auth:better-auth logger.

    • Each line carries an email, statusCode, and path field, and names the reason: code expired, invalid or unrecognized code, or too many attempts, code invalidated.
    • Routine failures (expired / invalid) log at info; too-many-attempts logs at warn.
    • All are visible at the default info level — no LOG_LEVEL change needed.
  • #176 a00cb25 Thanks @aspiers! - The /preview/validate page now checks epds_handle_login_url on your client metadata.

    Affects: Client app developers

    Client app developers: a new handle-login-url row joins the existing field checks.

    • Missing or empty value warns you that the "Or sign in with ATProto/Bluesky" button won't render.
    • An http(s):// value is ok, matching the isSafeHttpUrl gate that renders the button at runtime (http:// accepted so localhost dev clients still pass).
    • Any other value (javascript:, file:, unparseable) errors, because the runtime gate would silently drop the button on real flows.

ePDS@0.6.3

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 13 May 12:53
7f3bc8e

Who should read this release

Patch Changes

  • #169 cc65707 Thanks @Kzoeps! - The final sign-in screen now lines up its message card with the page title.

    Affects: End users

    End users: After approving an app sign-in, the "Login complete" screen now keeps the "You are being redirected..." card visually aligned with the title instead of sitting slightly lower on the page.

ePDS@0.6.2

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 05 May 16:54
08f7d24

Who should read this release

Patch Changes

  • #154 2e4d327 Thanks @aspiers! - Sign-in pages no longer strand users on a "session expired" dead end, and Resend no longer offers codes that won't work.

    Affects: End users

    End users: if your sign-in times out (you closed the tab and came back, or your wait was longer than the page can keep alive in the background), you are now taken back to the app you were signing in to so it can offer you a retry. The page also no longer offers Resend in the rare case where the new code wouldn't work — instead it tells you the sign-in has timed out and gives you a Start over button. No more typing a fresh code that fails. If for some reason the automatic return is not possible, the page shows a "Return to sign in" button so you can get back to the app yourself in one click.

  • #154 b1fc940 Thanks @aspiers! - Slow sign-ins are less likely to time out before you finish entering your code.

    Affects: End users

    End users: if you take a few minutes to find your sign-in code in your inbox before entering it, you will no longer be bounced to a "session expired" page when you submit it. Closing the tab or walking away for a long stretch can still expire the flow, in which case the existing error pages still apply — but reading email at human speed should not.

ePDS@0.6.1

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 01 May 20:53
e678b86

Who should read this release

Patch Changes

  • #130 6a8671d Thanks @s-adamantine! - "Powered by Certified" footer now appears on every auth-service page.

    Affects: End users

    End users: every page rendered by the auth service now displays the same "Powered by Certified" footer that the main sign-in already shows, so the branding is consistent end-to-end. New surfaces covered: the Account Settings sign-in flow at /account/login (email-entry and code-entry steps), the "Choose your handle" page shown to new users after email verification, both Account Recovery steps (backup-email entry and recovery-code entry), the /account settings dashboard, the backup-email verification confirmation, the post-deletion confirmation, and the generic error pages used by 404 / 500 / session-expired flows.

  • #141 899346c Thanks @aspiers! - "Use a different account" on the chooser now reliably takes you to the email form, not the code step for the previous account.

    Affects: End users

    End users: when you click "Another account" on the account chooser to sign in as someone else, you now always land on a fresh email entry form. Previously, if the app that started the sign-in had pre-filled an account hint, the page jumped straight to the verification-code step for the previous account — leaving you stuck typing a code for an account you were trying to leave.

  • #122 dacf1d2 Thanks @aspiers! - Sign-in no longer fails with "Authentication session expired" when an OTP code is resent after the original code times out.

    Affects: End users

    End users: Previously, if you took longer than 10 minutes to enter the one-time code emailed to you and then clicked Resend code, the new code would verify, but the next page would say "Authentication session expired. Please try again." and you would have to start the whole sign-in over. The OAuth session that was tracking your sign-in had the same 10-minute lifetime as the OTP code itself, so it had already gone away by the time the new code arrived.

    The OAuth session now lives long enough to outlast a typical resend cycle, so a slow first attempt followed by Resend completes normally. The OTP code's own 10-minute lifetime is unchanged.

  • #127 8bf888b Thanks @s-adamantine! - Terms of Use and Privacy Policy links on the sign-in page now open in a new tab.

    Affects: End users

    End users: clicking Terms of Use or Privacy Policy on the sign-in page no longer navigates away from the in-progress sign-in. The links open in a new tab instead, so you can read the legal page and come back to finish signing in without restarting.

  • #136 143ff35 Thanks @Kzoeps! - OAuth consent buttons stack cleanly on small screens.

    Affects: End users

    End users: On phones and narrow browser windows, the consent screen now places the approve and deny buttons on separate lines so they are easier to read and tap. Larger screens keep the existing button layout.

  • #134 bce65b5 Thanks @s-adamantine! - A smoother sign-in code experience: no false error flash on a successful sign-in, no rapid-fire failures when correcting a wrong code, and tidier-looking banners.

    Affects: End users, Client app developers

    End users:

    • A successful sign-in no longer briefly shows a red "Invalid OTP" message on its way to signing you in.
    • After entering a wrong code, the boxes clear and focus jumps back to the first one, so retyping doesn't immediately resubmit the still-wrong code on every keystroke (which previously could lock you out for spamming the server).
    • The red "Invalid OTP" and green "Code resent" banners are centred inside their coloured container instead of sitting in the corner of an empty wide box.

    Client app developers: the sign-in page's flash-message container now uses a stable flash-msg base class with error / success modifier classes, so custom client CSS can restyle either variant cleanly via .flash-msg, .flash-msg.error, and .flash-msg.success.

  • #128 0e62bd6 Thanks @aspiers! - Sign-in no longer fails with a raw JSON error page when a user takes too long on the OTP step.

    Affects: End users

    End users: Previously, if you took more than five minutes between requesting your one-time code and submitting it (a slow inbox, switching tabs, fishing the code out of spam, multiple Resend cycles), sign-in could fail with a blank page showing only {"error": "Authentication failed"} on the PDS host — even though your OTP code itself was still valid. You now either land back inside the app you were signing into (which can offer a one-click retry), or see a styled error page on the PDS host explaining that sign-in timed out — depending on how far through the flow the timeout is detected. Either way, no more raw JSON.

  • #129 14e5033 Thanks @aspiers! - Sign-in no longer hits a dead-end on the password form

    Affects: End users

    End users: if you saw a "handle and password" form during sign-in with no way to enter a code, that path is gone. The email-code form will be shown instead, and after entering the code you'll be signed in normally.

ePDS@0.6.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 29 Apr 23:12
b8a5296

Who should read this release

Minor Changes

  • #115 7f265b7 Thanks @aspiers! - Auth-service login page can now offer ATProto/Bluesky handle sign-in alongside email OTP.

    image

    Affects: End users, Client app developers, Operators

    End users:

    • When the app you came from supports it, the sign-in page now shows an "Or sign in with ATProto/Bluesky" button under the email form.
    • Clicking the button switches the form into handle-entry mode (e.g. you.bsky.social). Submitting a handle takes you back to your own PDS to finish signing in there.
    • Clicking the button again returns you to the email form.

    Client app developers: opt in by adding epds_handle_login_url to your OAuth client metadata.

    • The value must be an absolute https:// URL on your client's own origin. ePDS auth-service redirects the browser to that URL with ?handle=<value> appended when the user submits a handle.
    • Your route is responsible for resolving the handle to its PDS and starting a fresh OAuth flow against that PDS — auth-service is bound to one PDS and cannot start a PAR on your client's behalf, so off-PDS handles only work via this hand-off.
    • The reference demo client opts in by exposing ${baseUrl}/api/oauth/login?handle=..., which already accepts a handle query parameter and resolves it dynamically.
    • If you do not declare epds_handle_login_url, the button is not rendered. Existing clients see no behaviour change.

    Operators: no new required configuration. The button only renders for OAuth clients that explicitly opt in via their metadata.

  • #103 226781b / #93 d363b3d Thanks @aspiers! - Preview ePDS's auth-service screens and emails directly in your browser, without walking through the OAuth flow.

    Screenshot 2026-04-30 at 00 14 00

    Affects: Client app developers, Operators

    Client app developers:

    A new preview route on pds-core renders the account chooser with fixture sessions and your branding CSS, alongside the existing /preview/consent route. Open /preview/chooser (linked from the /preview index) to see how a returning user with one or more bound accounts will see your client. Inline controls on the index let you tweak the preview without editing the URL: a number field for ?numAccounts=N (clamped to 1–10) grows or shrinks the fixture account list, and a dropdown for ?epds_handle_mode= overrides the handle-picker mode the same way a real OAuth request can. The dropdown defaults to "Auto", which omits the param so client metadata (or the operator's env default) wins — exactly the production resolver order. The same ?client_id=<URL-of-your-client-metadata.json> param the other preview routes accept also injects your branding CSS, subject to the standard trusted-clients gate. The existing /preview/choose-handle link on the auth-service index gains the same ?epds_handle_mode= and ?error= dropdowns and collapses the four enumerated handle-mode entries into a single link with bound controls.

    Three new preview routes on the auth service render the exact email HTML real users receive, inside a sandboxed iframe:

    • /preview/emails/new-user — welcome / email-verification code sent during signup.
    • /preview/emails/returning-user — sign-in OTP sent when an existing user logs in to your app.
    • /preview/emails/recovery — backup-email verification link sent when a user adds a recovery address.

    Each route accepts the same ?client_id=<URL-of-your-client-metadata.json> query param as the other preview pages, so you can see how your branded template will look without walking through a real OAuth flow. Optional extras: ?otp=<code> to override the fixture OTP, ?app=<name> to override the fixture app name on the returning-user template, ?verify_url=<url> to override the bac...

Read more

ePDS@0.5.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 19 Apr 23:07
362cdd4

Who should read this release

Minor Changes

  • #84 fe3ec90 Thanks @aspiers! - Add preview routes on auth-service and pds-core for iterating on client branding CSS.

    Affects: Client app developers, Operators

    Client app developers:

    • Visit /preview on either auth-service or pds-core for an index of every preview page. Each page renders against fixture data, so you can iterate on your branding.css without walking through a real OAuth flow.
    • Paste your client-metadata.json URL into the input field on the index page. The value is persisted in your browser and wires up every preview link, subject to the same PDS_OAUTH_TRUSTED_CLIENTS check as a real flow. Leave it blank to see the unbranded baseline.
    • The workflow becomes: edit branding.css, refresh any preview page. No OTP emails, no full flow.
    • The demo app links directly to the auth-service preview index with its own client_id pre-selected.

    Operators:

    • Two new env vars gate the preview routes, one per service: AUTH_PREVIEW_ROUTES=1 on auth-service, PDS_PREVIEW_ROUTES=1 on pds-core. Both are independent.
    • Safe to enable on preview deployments (Railway PR previews, pr-base, dev) and on local development instances. Preview routes don't affect real auth flows — they short-circuit real state — so they can technically run in production too, but they are a developer-only surface and are best left off outside preview/dev envs.
    • Privacy: enabling previews exposes /preview/cache-status, which returns the list of client_id URLs currently in the shared client-metadata cache — i.e. apps that have recently started an OAuth flow against this PDS. That partially leaks which third-party clients are using the instance, so keep previews disabled in production unless you're comfortable with that.
    • See packages/auth-service/.env.example and packages/pds-core/.env.example for the full notes.

Patch Changes

  • #83 cc722c4 Thanks @aspiers! - Demo amber/ocean themes now colour the OAuth consent page correctly.

    Affects: End users of the trusted demo

    End users: The consent screen shown after signing in via the trusted demo now uses the demo's own warm indigo / amber palette throughout — the Authorize and Deny-access buttons, the "Authorize" header strip, and the surrounding surface all match the theme instead of falling back to the default @atproto/oauth-provider dark-mode look.

    The previous CSS targeted auth-service's hand-rolled login markup (.btn-primary, .container, .field), which does not exist on the consent page — that page is built from @atproto/oauth-provider-ui, which is a Tailwind-utility bundle whose colours are driven by CSS custom properties (--branding-color-primary and friends). The demo theme now overrides those variables at :root, so a single declaration recolours every bg-primary / text-primary / border-primary utility on the consent page at once, and additionally paints the card surface and body background to match.

  • #89 1942ebb Thanks @aspiers! - Fix two preview-route cache bugs and remove long-stale debug endpoints.

    Affects: Client app developers, Operators

    Client app developers:

    • Preview-route fetch failures no longer poison the shared client-metadata cache. Previously, a failed preview fetch for a client_id with a valid 10-minute entry would overwrite that entry with a 60-second branding-less fallback, silently dropping branding.css on real OAuth flows for up to a minute. The in-memory cache is now only written by real-flow resolution.
    • The auth-service HTML preview pages (/preview/login, /preview/login-otp, /preview/choose-handle, /preview/choose-handle-picker, /preview/recovery, /preview/recovery-otp, and the /preview index) now send Cache-Control: no-store. Without it, a browser refresh could serve a cached page and never ask the server for fresh branding.css, breaking the advertised "edit branding.css, refresh the preview page" workflow.
    • /preview/validate now flags branding.css whose escaped size exceeds the 32 KB injection limit as an error, instead of reporting ok and letting the developer discover later that their CSS was silently dropped on real OAuth flows. Byte counts now match getClientCss()'s measurement (escaped UTF-8).

    Operators:

    • Removed /_internal/debug-grants and /_internal/debug-recent-accounts. These were added as temporary HYPER-270 debugging endpoints with a code comment marking them for removal before PR #21 shipped (v0.2.2); they survived through v0.2.2, v0.3.0, v0.4.0, and the pending v0.5.0. The matching env var EPDS_DEBUG_GRANTS is no longer read.

ePDS@0.4.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 14 Apr 16:26
f94f611

Who should read this release

Minor Changes

  • #48 0c275e4 Thanks @Kzoeps & @aspiers! - Trusted apps can now style the sign-in and consent pages to match their own brand.

    Affects: End users, Client app developers, Operators

    End users: When signing in through an app that your ePDS operator has approved for branding, the login page, code entry page, handle picker, account recovery page, and consent page will display that app's colour scheme instead of the default look. The pages still work exactly the same way — only the visual appearance changes.

    Client app developers: Add a branding.css field inside a branding object in your client-metadata.json. The CSS is injected as a <style> tag into every auth-service page and the PDS stock consent page (/oauth/authorize) when your client_id is listed in the operator's PDS_OAUTH_TRUSTED_CLIENTS. The CSS is size-capped at 32 KB (measured in escaped UTF-8 bytes) and sanitised to prevent </style> tag closure. The CSP style-src directive is updated with a SHA-256 hash of the injected CSS. Example metadata:

    {
      "client_id": "https://app.example/client-metadata.json",
      "client_name": "My App",
      "branding": {
        "css": "body { background: #0f1b2d; color: #e2e8f0; } .btn-primary { background: #3b82f6; }"
      }
    }

    Untrusted clients (not in PDS_OAUTH_TRUSTED_CLIENTS) never get CSS injection, regardless of what their metadata contains.

    Operators: CSS branding injection is controlled by the existing PDS_OAUTH_TRUSTED_CLIENTS env var on pds-core. No new env vars are required on pds-core or auth-service. The auth-service reads the same PDS_OAUTH_TRUSTED_CLIENTS list to decide whether to inject CSS on its pages (login, OTP, choose-handle, recovery). See docs/configuration.md for the full reference.

    For the demo app, a new optional EPDS_CLIENT_THEME env var selects a named theme preset (e.g. ocean) that applies consistent styling to both the demo's own pages and the CSS served in its client metadata. When unset, the demo uses the default light theme with no branding CSS. See packages/demo/.env.example for details.

Patch Changes

  • #77 b3c779a Thanks @aspiers! - Generate ES256 keypairs with pnpm jwk:generate instead of re-running full setup.

    Affects: Client app developers

    Client app developers: A new pnpm jwk:generate command outputs a compact ES256 private JWK (with auto-derived kid) on stdout. Use this when you need a keypair for private_key_jwt client authentication without running the full scripts/setup.sh. The output is suitable for the EPDS_CLIENT_PRIVATE_JWK environment variable (used by the bundled demo app in packages/demo, not by third-party client apps) or for embedding the public half in any client metadata's jwks field.

  • #77 0eaded0 Thanks @aspiers! - Updated login integration docs to recommend @atproto/oauth-client-node and confidential clients.

    Affects: Client app developers

    Client app developers: The tutorial and skill reference now recommend @atproto/oauth-client-node's NodeOAuthClient for Flow 2 (no hint, handle, or DID input), which handles PAR, PKCE, DPoP, and token exchange automatically. Flow 1 (email login_hint) remains hand-rolled. The default client metadata example has been flipped from "token_endpoint_auth_method": "none" to "private_key_jwt" with jwks_uri or inline jwks for publishing the public key. A new "Confidential vs public clients" section explains the trade-offs — notably that public clients force a consent screen on every login. New sections cover JWKS key generation, publishing, and rotation.

ePDS@0.3.0

Choose a tag to compare

@hypercerts-release-bot hypercerts-release-bot released this 12 Apr 20:32
c2e770d

Who should read this release

Minor Changes

  • #74 b46273a Thanks @aspiers! - The health endpoint now reports the running ePDS version.

    Affects: Client app developers, Operators

    Client app developers: both /health endpoints (pds-core and auth-service) now include a version field in their JSON response (e.g. { "status": "ok", "service": "epds", "version": "0.2.2+f37823ee" }). You can use this to check which ePDS release your app is running against. The demo frontend also displays the version in its page footer.

    Operators: in Docker and Railway deployments the version is automatically set to <package.json version>+<8-char commit SHA> at build time. In local dev it falls back to the root package.json version (e.g. 0.2.2). To override, set the EPDS_VERSION environment variable on both pds-core and auth-service to any string. Docker Compose users should now build with pnpm docker:build instead of docker compose build directly — the wrapper stamps the version before building, and the build will fail if the version stamp is missing.

Patch Changes

  • #76 f709066 Thanks @aspiers! - The upstream PDS version now appears on the stock health endpoint.

    Affects: Client app developers, Operators

    /xrpc/_health now returns the upstream @atproto/pds version in its JSON response (e.g. { "version": "0.4.211" }). Previously this endpoint returned {}. This is independent of the ePDS version reported by /health.

    Operators: no configuration is needed — the version is read from the installed @atproto/pds package at startup. To override, set the PDS_VERSION environment variable on pds-core.