Skip to content

Directus Auth: Adapt Copied Files to the Map Tool

Goal

The login/logout building blocks copied from another app (auth.server.ts, directus.server.ts, session.server.ts, login.tsx, logout.tsx, env.server.ts) compile cleanly and describe only concepts that exist in this project: a single-tier signed-in Directus user with tokens, and nothing else. No route enforcement yet, no per-project roles, no admin token.

Prerequisites

Already in place, no changes needed:

  • @directus/sdk dependency (package.json)
  • login/logout routes registered in app/routes.ts
  • Startup env validation wired via the side-effect import in app/entry.server.tsx — env.server.ts's own comment about this is already correct and needs no edit.

Decisions

Decision Choice Why
Plan file location docs/features/versions/v0.0.3/directus-auth-adaptation.md Follows the per-feature file precedent in this folder (e.g. map-lines-and-areas.md)
Enforcement Not in this task Confirmed with user — get signIn/signOut/session working and typechecking first; gating routes behind login is a follow-up task
Per-project roles/contexts Deleted entirely Confirmed with user — docs/features/current/projects.md says projects have no ownership model; the copied gamemaster/player context resolution doesn't map to anything here and its implementation doesn't even compile (see T4)
Directus schema Auth-only Confirmed with user — the pbpdemo_* collections (campaigns/characters/threads/posts) are the other app's domain; this app's own Directus collections don't exist yet (see the TODO in remote-data-store.md)
Admin token / directusAsAdmin Removed entirely Confirmed with user — "keep the auth as clean and lean as possible"; nothing in the trimmed sign-in flow needs to bypass Directus permissions, so DIRECTUS_ADMIN_TOKEN is dropped from required env, .env.example, and the SDK client factory
Telegram env vars Removed Confirmed with user — TELEGRAM_BOT_TOKEN/TELEGRAM_CHANNEL_ID supported a "new post" notification feature that doesn't exist in this app

Scope

In:

Out:

  • Gating any route behind login (no loader calls getActiveSession yet)
  • Any per-project ownership or role model
  • Directus-backed map/project collections (Maps/Layers/Features)
  • Any nav/UI entry point to /login or a sign-out control
  • app/routes/logout.tsx logic — expected to need no changes, verified in T6

Data model

Target shapes after this task (types only, bodies are implementation):

// app/lib/session.server.ts
export interface SessionValues {
  userId: string
  displayName: string
  accessToken: string
  refreshToken: string
  expiresAt: number
}
// app/lib/directus.server.ts
// No app-specific Schema yet — collections don't exist until the real
// Directus migration. Clients are created without a Schema generic.
export function directusAnonymous(): DirectusClient
export function directusAsUser(accessToken: string): DirectusClient
// app/lib/auth.server.ts
export type SignInResult =
  | { ok: true; values: SessionValues }
  | { ok: false; message: string }

export async function signIn(email: string, password: string): Promise<SignInResult>
export async function signOut(refreshToken: string): Promise<void>

Tasks

Ordered so each leaves the repo in a state where the remaining breakage is only what the next task fixes; pnpm typecheck is clean only once all tasks are done (T6).

T1 — directus.server.ts: strip to an auth-only client factory

Goal: The Directus SDK wrapper describes no domain this app doesn't have.

Acceptance criteria:

  • Schema, Campaign, Character, Thread, Post, ThreadCharacter, PublishStatus, PostType, ThreadType, UserSummary, DefaultFields are all deleted.
  • directusAnonymous and directusAsUser no longer reference a Schema generic (or use an empty/minimal one) — no pbpdemo_* collection name appears anywhere in the file.
  • directusAsAdmin and its doc comment are deleted.
  • DirectusFailure, SAFE_MESSAGES, toDirectusFailure, describeError are unchanged — none of them are domain-specific.
  • pnpm typecheck will now fail on auth.server.ts (still importing directusAsAdmin) — expected, fixed in T4.

T2 — env.server.ts and .env.example: drop unused env vars

Goal: Required/optional env vars match what the trimmed auth flow actually uses.

Acceptance criteria:

  • env.server.ts's REQUIRED list drops DIRECTUS_ADMIN_TOKEN, keeping DIRECTUS_BASE_URL and SESSION_SECRET.
  • The exported env object drops DIRECTUS_ADMIN_TOKEN, TELEGRAM_BOT_TOKEN, TELEGRAM_CHANNEL_ID, and the comment describing the Telegram feature.
  • .env.example drops the DIRECTUS_ADMIN_TOKEN line (it never had Telegram vars, so no change needed there).
  • pnpm typecheck will now fail on directus.server.ts (still referencing env.DIRECTUS_ADMIN_TOKEN if T1 wasn't already applied) — both T1 and T2 must land together before typecheck is expected to pass on either file.

T3 — session.server.ts: drop the context/role model

Goal: SessionValues only carries identity and Directus tokens.

Acceptance criteria:

  • ContextRole and SessionContext are deleted.
  • SessionValues matches the Data model above (userId, displayName, accessToken, refreshToken, expiresAt — no contexts, no activeContextKey).
  • sessionValues() no longer reads contexts/activeContextKey off the cookie session.
  • commitSessionValues, getActiveSession, refreshActiveSession, clearSessionCookie need no logic change beyond following the narrower type — refreshing/clearing a session isn't context-aware today either.
  • pnpm typecheck will still fail on auth.server.ts — fixed in T4.

T4 — auth.server.ts: delete resolveContexts, simplify signIn

Goal: Sign-in verifies credentials against Directus and returns identity + tokens only — no campaign/character resolution, no admin token.

Acceptance criteria:

  • resolveContexts is deleted in full (it currently doesn't compile: an unreferenced characters and a dangling empty element in its Promise.all array).
  • The directusAsAdmin import is removed.
  • signIn builds SessionValues directly from the directusLogin result and readMe({ fields: ["id", "first_name"] }) — no contexts field, no activeContextKey field.
  • SETUP_FAILED_MESSAGE no longer mentions "campaigns" — reword for a generic "your session could not be set up" failure.
  • The comment above resolveContexts's former call site and the "ONLY sanctioned use of the admin token" doc comment are removed along with it.
  • signOut is unchanged (already domain-agnostic).
  • pnpm typecheck passes for app/lib/**.

T5 — login.tsx: copy and title

Goal: The sign-in page reads as this app, not "Play by Post".

Acceptance criteria:

  • meta()'s title no longer says "Play by Post" (e.g. "Sign in · Map Tool").
  • The CardDescription copy no longer says "join your campaigns" (e.g. something generic like "Sign in with your account.").
  • No change to form fields, loader, or action logic — only copy.

T6 — Verify logout.tsx and full repo typecheck

Goal: Confirm logout.tsx needs no changes against the trimmed auth.server.ts/session.server.ts, and the whole repo is clean.

Acceptance criteria:

  • logout.tsx still compiles unchanged (signOut(values.refreshToken), clearSessionCookie, sessionValues — none of these signatures changed shape in a way that affects this call site).
  • pnpm typecheck passes with no errors anywhere in the repo.
  • pnpm format produces no diff.
  • Manual check: pnpm dev, visit /login directly, sign in with a valid Directus account, confirm redirect to / with a session cookie set; then POST /logout (e.g. via the existing form) and confirm the cookie is cleared and Directus's refresh token is revoked (no error in server logs).