Account And Downloads
Use this guide when adding customer account pages.
The ⓐ copied account Astro template files cover:
- magic-link access;
- account overview;
- orders and protected invoice links;
- subscriptions;
- licenses;
- downloads;
- account export and delete request examples.
Account pages are host-owned Astro pages that call createMika(Astro, { api }). They should not project raw provider invoice URLs or raw storage paths to the browser. Use Mika-issued hrefs and token-bound routes.
Before exposing account pages in production, the host needs a session/customer policy, magic-link or stronger auth delivery, account rate limits, and storage for tokens, entitlements, licenses, downloads, export requests, and delete requests. Mika supplies the operation contracts and copied page examples; it does not decide who is allowed to see an account.
Account Page
Section titled “Account Page”Treat AUTH_REQUIRED as “not signed in”, not as an error, and redirect first when the billing portal action returns a URL:
---import { createMika } from "@bnomei/emdash-mika/astro";import { actions } from "astro:actions";import { api } from "../lib/mika-api";export const prerender = false;
const Mika = createMika(Astro, { api });const portal = Astro.getActionResult(actions.mika.account.portal);if (portal?.data?.ok && portal.data.data.redirectUrl) { return Astro.redirect(portal.data.data.redirectUrl);}const portalError = portal?.error?.message ?? (portal?.data?.ok === false ? portal.data.error.message : undefined);
const result = await Mika.account.get();const account = result.ok ? result.data : null;const signInRequired = !result.ok && result.error.code === "AUTH_REQUIRED";---{portalError && <p role="alert">{portalError}</p>}The unpublished 0.2.0 target preserves the Mika envelope inside ordinary Astro Actions. Check Astro validation/guard failures at result.error, commerce failures at result.data.ok === false (with result.data.error and result.data.status), and successful DTOs at result.data.data after result.data.ok === true. Direct Mika.account.get() and Mika.download.resolve() server helpers retain their single result.ok / result.data layer.
account.get() returns orders, subscriptions, entitlements, and downloads in one AccountDTO. Export and delete are forms posting account.export and account.delete — use bracket access for the reserved words: actions.mika.account["export"] and actions.mika.account["delete"].
Magic-Link Sign In
Section titled “Magic-Link Sign In”Mika supplies passwordless magic-link request and verify actions. The host still owns session policy, link delivery, account gating, and any stronger authentication requirements. Request a link with magicLink.request (field email):
<form action={actions.mika.magicLink.request.queryString} method="post"> <input name="email" type="email" required /> <button type="submit">Email me a link</button></form>The emailed link lands on a host page (e.g. /account/magic-link?token=...) that posts magicLink.verify (field token) and redirects to the account on success.
Only show “Email sent” after requestResult?.data?.ok && requestResult.data.data.sent; only treat verification as successful after verifyResult?.data?.ok. Export status lives at exportResult.data.data.status, and delete confirmation at deleteResult.data.data.requested, each guarded by its Action’s data.ok. Render both Astro and commerce errors rather than treating any truthy Action data as success.
Downloads
Section titled “Downloads”Downloads are tokenized. The package template provides a host-owned .astro interstitial page at templates/astro/pages/download/[token].astro that renders a GET confirmation page and consumes with download.confirm on POST. The seeded template repo uses the same .astro interstitial shape for its fixture download route.
For the confirmation Action, redirect only when confirmResult?.data?.ok && confirmResult.data.data.redirectUrl; surface confirmResult.error or the failed confirmResult.data.error otherwise. This nested Action envelope does not change the direct server helper example below.
A host can instead write a custom API route that resolves the token to a redirect. Never expose raw storage paths:
import { createMika } from "@bnomei/emdash-mika/astro";import type { APIRoute } from "astro";import { api } from "../../lib/mika-api";export const prerender = false;
export const GET: APIRoute = async ({ params, request, url, redirect }) => { const Mika = createMika({ request, url }, { api }); // a minimal { request, url } context, not full Astro const result = await Mika.download.resolve(params.token!); if (!result.ok) return new Response(result.error.message, { status: result.status }); if (result.data.redirectUrl) return redirect(result.data.redirectUrl); return new Response("Download unavailable.", { status: 404 });};The built-in backend may return only DownloadResolutionDTO.downloadRef. That value is opaque fulfillment evidence, not a URL or storage path. Production host code must map it to an authorized, short-lived HTTPS asset URL and return that as redirectUrl; the copied interstitial fails closed when no URL has been resolved. Keep the signed asset expiry at or before the Mika token expiry, and resolve the asset before consuming the one-time token so a signing failure does not burn the buyer’s link.
Account screens link download.href (tokenized) and order.invoiceHref (behind Mika’s protected order.invoice route) — never raw provider invoice URLs. Licenses show only a key suffix, never the full key.
Raw license-key generation and delivery are also host-owned. Mika can persist a license id and display suffix, but the full secret should travel through the host’s secure issuance and notification boundary rather than Mika DTOs or account pages.
Account Export Downloads
Section titled “Account Export Downloads”account.export returns an AccountExportDTO with a tokenized downloadHref when ready. Built-in JSON exports require the direct Astro HTTP integration with EmDash 0.22: its plugin dispatcher JSON-wraps returned Response objects and cannot deliver the attachment itself.
Add this integration alongside EmDash in the host’s existing configuration:
// astro.config.mjs — add to the existing integrations array.import { fileURLToPath } from "node:url";import { mikaAccountExportIntegration } from "@bnomei/emdash-mika/astro";
const accountExports = mikaAccountExportIntegration({ entrypoint: fileURLToPath( new URL("./src/lib/mika-api.ts", import.meta.url), ),});// integrations: [accountExports, emdash(/* existing host options */)]The absolute server entrypoint must export the same live api used by the plugin and Actions. It may also export operationPolicy and onError; share the plugin’s policy rather than leaving this route with different authorization. The package endpoint calls validateMikaAccountExportHttpWiring before dispatch, rejecting missing or invalid wiring instead of silently using a stub API.
The integration injects the exact route /_emdash/api/plugins/mika/account/export/download, ahead of EmDash’s catch-all. Do not copy/customize that endpoint or install a competing route at the same path. Host middleware must allow the token route and initialize the same session as account Actions. Production needs an SSR adapter. This narrow package-owned HTTP route does not make the copied storefront pages package-owned.
GET validates the export ID and token without consuming it and renders confirmation. Same-origin browser POST consumes the one-time token and delivers the built-in JSON artifact as an attachment. Used or revoked tokens return errors such as TOKEN_USED or DOWNLOAD_REVOKED. Confirmation is token-delivery policy, not proof of humanity.
deliverAccountExport is a server-only request capability used to pass artifact bytes to the HTTP response boundary. DTOs contain metadata, never inline artifact bytes; the capability must never enter browser or agent tool output. Delivery does not depend on retaining the DTO object’s identity, so wrapping or JSON-cloning a DTO does not transfer or remove that server capability. Old data-URL export links must be requested anew. Custom external artifact delivery remains host-owned.
Template Fixture Route
Section titled “Template Fixture Route”The ⓣ seeded GitHub template repo documents /download/download_panel_pack_mira as a smoke-test path only. It is available when the sibling emdash-mika-template app is running with its fixture seed data.
That route is browser-reachable because src/pages/download/[token].astro is a seeded-repo host page. In the seeded repo fixture, download_panel_pack_mira is a literal seeded token for “Mira Panel Pack Download”; after POST confirmation it redirects to a local placeholder file under /template-downloads/.
Do not model production tokens after that readable fixture name. Production download resolution should use Mika-issued opaque tokens, expiry, token consumption, paid-order checks, and entitlement or license revocation checks before redirecting to a signed asset URL. The mira suffix is just the demo product/customer naming used by the seeded repo fixtures.
Verify
Section titled “Verify”- Account pages treat
AUTH_REQUIREDas a sign-in state. - Download links use Mika tokenized hrefs, not raw storage paths.
- Opaque
downloadRefvalues are resolved server-side to short-lived HTTPS URLs before token consumption. - Invoice links stay behind protected order/invoice resolution.
- Raw license keys are generated and delivered by a host-owned secure channel; Mika stores only safe evidence and suffixes.
- Production download tokens are opaque, expiring, and checked against paid-order or entitlement state.
- Built-in export GET renders confirmation without consuming the token; same-origin POST returns a JSON attachment, not a JSON-wrapped
Responseor inline DTO artifact. Reused tokens fail. - Export integration and plugin share the live API, session, and operation policy; agent output contains neither artifact bytes nor token URLs.
Next: Backend And Provider covers the trusted API behind these account flows. Security And Idempotency covers the host policy boundary.
Source Anchors
Section titled “Source Anchors”- ⓐ
../emdash-mika/src/templates/astro/pages/account.astro - ⓐ
../emdash-mika/src/templates/astro/pages/account/orders.astro - ⓐ
../emdash-mika/src/templates/astro/pages/account/magic-link.astro - ⓐ
../emdash-mika/src/templates/astro/components/AccountSignInPanel.astro - ⓐ
../emdash-mika/src/templates/astro/components/MagicLinkForm.astro - ⓐ
../emdash-mika/src/templates/astro/pages/download/[token].astro - ⓟ
../emdash-mika/src/api/operations.ts - ⓟ
../emdash-mika/src/api/types.ts - ⓟ
../emdash-mika/src/api/backend.ts - ⓟ
../emdash-mika/src/astro.ts - ⓟ
../emdash-mika/src/api/backend/account.ts - ⓟ
../emdash-mika/src/api/route-handlers.ts - ⓐ
../emdash-mika/src/templates/astro/account-export-http.ts - ⓐ
../emdash-mika/src/templates/astro/examples/astro-storefront.md - ⓟ
../emdash-mika/test/fixtures/digital-delivery.ts - ⓣ
../emdash-mika-template/src/pages/download/[token].astro - ⓣ
../emdash-mika-template/src/lib/mika-fixture-storefront.ts - ⓣ
../emdash-mika-template/seed/mika-actions.seed.json