Kanban board tasků — filtr dle priority, labelu nebo runu.
Nový src/middleware.ts s defineMiddleware — parse session cookie, lookup přes Session service, populate Astro.locals.user + locals.session. SessionExpired catchTag → silent Option.none (uživatel se chová jako anonymní). DbError i ConfigMissing v middleware se logují a fallthrough na null locals (nezhroutí stránku).
Nová stránka src/pages/account.astro — prerender=false, čte Astro.locals.user, redirect na /login pokud null. Per-page guard místo middleware-driven redirect (žádný hidden seznam protected rout).
pnpm tsc --noEmit a pnpm astro check oba běží clean. Jeden typecheck error po prvním write (passwordHash je nullable v schema, login musí na null branch vrátit no_password) opraven přidáním passwordHash null guardu v login.ts před verify.
Nový endpoint src/pages/api/auth/login.ts — POST form/JSON, findByEmail → verify PBKDF2 → vytvoří session a vrátí cookie. UserNotFound i wrong_password se klientovi vrací jako jeden tag invalid_credentials, aby chyba neodhalila existenci účtu. Login schema (src/auth/schemas/login.ts) vyžaduje jen přítomnost hesla, ne min 8, kvůli legacy slabým heslům.
Nová stránka src/pages/login.astro mirroring signup form — prerender=false, error banner z URL query (invalid_credentials/validation/internal), link na signup. Czech labels.
Nový endpoint src/pages/api/auth/logout.ts — idempotent (vždy 303 → /), revoke session když cookie existuje, clear-cookie header. Žádný 404 při chybějící cookie aby logout fungoval i po expiraci.
Cookie attributes (HttpOnly/Secure/SameSite) byly hardcoded a env vars (SESSION_COOKIE_NAME, SESSION_TTL_DAYS) viseli nezapojené. Vznikl nový auth/config.ts který přes Effect.gen čte env a vrátí CookieConfig (name, maxAge, secure z import.meta.env.PROD); pokud chybí cookie name, fail s ConfigMissing. Session cookie serialize/clear přijímá secure: boolean — dev běh bez HTTPS funguje.
Typecheck pnpm tsc --noEmit a pnpm astro check oba 0 errors / 0 warnings. Hlavní typing landmine: TS 5.9 Uint8Array default ArrayBufferLike nesedí na BufferSource — explicit cast.
Layer.mergeAll(Session+User+Password) přes DbLive. runAuthToResponse přidán optional onFailure handler — umožní per-endpoint content negotiation (form redirect vs JSON).
POST /api/auth/signup — handluje JSON i form. Browser: cookie + 303 redirect na /account, error 303 na /signup?error=&email=. JSON klient: 201 nebo errored JSON.
Pure Astro form bez Svelte/JS hydration. prerender=false (čte query). Error banner s czech texty mapuje tag z URL.
PBKDF2 přes WebCrypto SubtleCrypto — 600k iter SHA-256, 16B salt, format pbkdf2$<iter>$<salt-b64>$<hash-b64>. Constant-time compare ve verify, žádný WASM payload.
Effect Schema validace pro signup body (email pattern, password 8-200, name optional). decodeUnknownEffect → ValidationError s message z SchemaError.
User service nad Db: create/findByEmail/findById. UNIQUE constraint violation detekuji přes substring v error.message a remapuji DbError → UserAlreadyExists (409 místo 500). Email lowercase normalize.
Login flow potrebuje session management. Session service (zavislost na Db) expose create/findByCookie/revoke/revokeAllForUser. Session ID = 32B random bytes base64url, default TTL 30 dni, findByCookie kontroluje expiry + maze expired session + touchuje lastUsedAt. K tomu cookie helpers (serialize/clear/parse) pro Run 01-03.
Astro endpointy potrebovaly entrypoint do Effect. Runtime.ts ma module-level ManagedRuntime (cached per Worker isolate) co builds Layer z DbLive + SessionLive + provideD1(env.DB) z cloudflare:workers. Export runAuth() a runAuthToResponse() (druha mapuje AuthError tagy na 400/401/409/500 Response).
Astro middleware potrebuje typed locals. env.d.ts deklaruje App.Locals.user a App.Locals.session jako nullable. Smoke endpoint /api/smoke overuje runtime+D1 connect (vraci pocet useru), v dev serveru vratil {ok:true,users:0}, pred commitem smazan.
Projekt nemel auth deps ani D1. Pridany effect@4.0.0-beta.67, drizzle-orm, drizzle-kit; vytvorena D1 databaze oskar-db s bindingem DB v wrangler.jsonc; pridany scripty db:generate, db:migrate:local/remote.
Auth potreboval persistent storage. Drizzle schema definuje 4 tabulky (users, sessions, oauth_accounts s composite PK, password_resets) vcetne FK cascade a indexu. Tabulky pro Sprint 002 jsou pridane preventivne aby nebyla druha migrace. Migrace aplikovana lokalne.
Auth flow potreboval typove vsechny failure mody. AuthError ADT v errors.ts pres Data.TaggedError pokryva 9 tagu (UserAlreadyExists, UserNotFound, InvalidCredentials, SessionNotFound/Expired, DbError, ValidationError, ConfigMissing, TokenInvalid). Runtime adapter pres ne exhaustive switchuje na HTTP statusy.
Effect services potrebovaly D1 abstrakci. Db service (Context.Service v Effect 4) drzi drizzle handle + raw D1Database a expose run(operation, fn) helper co maps Promise na Effect s DbError. D1Binding je separatni service co se providuje z env.DB v runtime adapteru.