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/sdkdependency (package.json)login/logoutroutes 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:
- app/lib/directus.server.ts — drop the
pbpdemo_*Schema/collection types anddirectusAsAdmin - app/lib/env.server.ts — drop
DIRECTUS_ADMIN_TOKEN,TELEGRAM_BOT_TOKEN,TELEGRAM_CHANNEL_ID - .env.example — drop
DIRECTUS_ADMIN_TOKEN - app/lib/session.server.ts — drop
SessionContext/ContextRole/activeContextKey, shrinkSessionValues - app/lib/auth.server.ts — delete
resolveContexts(currently non-compiling), simplifysignIn, rewriteSETUP_FAILED_MESSAGE - app/routes/login.tsx — copy/title only
Out:
- Gating any route behind login (no loader calls
getActiveSessionyet) - Any per-project ownership or role model
- Directus-backed map/project collections (
Maps/Layers/Features) - Any nav/UI entry point to
/loginor 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,DefaultFieldsare all deleted.directusAnonymousanddirectusAsUserno longer reference aSchemageneric (or use an empty/minimal one) — nopbpdemo_*collection name appears anywhere in the file.directusAsAdminand its doc comment are deleted.DirectusFailure,SAFE_MESSAGES,toDirectusFailure,describeErrorare unchanged — none of them are domain-specific.pnpm typecheckwill now fail onauth.server.ts(still importingdirectusAsAdmin) — 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'sREQUIREDlist dropsDIRECTUS_ADMIN_TOKEN, keepingDIRECTUS_BASE_URLandSESSION_SECRET.- The exported
envobject dropsDIRECTUS_ADMIN_TOKEN,TELEGRAM_BOT_TOKEN,TELEGRAM_CHANNEL_ID, and the comment describing the Telegram feature. - .env.example drops the
DIRECTUS_ADMIN_TOKENline (it never had Telegram vars, so no change needed there). pnpm typecheckwill now fail ondirectus.server.ts(still referencingenv.DIRECTUS_ADMIN_TOKENif 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:
ContextRoleandSessionContextare deleted.SessionValuesmatches the Data model above (userId,displayName,accessToken,refreshToken,expiresAt— nocontexts, noactiveContextKey).sessionValues()no longer readscontexts/activeContextKeyoff the cookie session.commitSessionValues,getActiveSession,refreshActiveSession,clearSessionCookieneed no logic change beyond following the narrower type — refreshing/clearing a session isn't context-aware today either.pnpm typecheckwill still fail onauth.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:
resolveContextsis deleted in full (it currently doesn't compile: an unreferencedcharactersand a dangling empty element in itsPromise.allarray).- The
directusAsAdminimport is removed. signInbuildsSessionValuesdirectly from thedirectusLoginresult andreadMe({ fields: ["id", "first_name"] })— nocontextsfield, noactiveContextKeyfield.SETUP_FAILED_MESSAGEno 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. signOutis unchanged (already domain-agnostic).pnpm typecheckpasses forapp/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
CardDescriptioncopy no longer says "join your campaigns" (e.g. something generic like "Sign in with your account."). - No change to form fields,
loader, oractionlogic — 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.tsxstill compiles unchanged (signOut(values.refreshToken),clearSessionCookie,sessionValues— none of these signatures changed shape in a way that affects this call site).pnpm typecheckpasses with no errors anywhere in the repo.pnpm formatproduces no diff.- Manual check:
pnpm dev, visit/logindirectly, sign in with a valid Directus account, confirm redirect to/with a session cookie set; thenPOST /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).