The three pre-legal-disclaimer users (ullrich, pistazie, nelli) have
NULL terms_version, and any future Terms update would leave every
existing user in the same state. Close the loop now that we have
version storage by redirecting any logged-in user whose
users.terms_version doesn't match the currently-published
TERMS_VERSION to a dedicated acceptance page.
Changes:
- auth.server: new recordTermsAcceptance(userId, version) helper that
writes both terms_accepted_at and terms_version.
- root loader: if the session user has a stale or NULL terms_version,
throw redirect("/auth/accept-terms?returnTo=<pathname>") unless the
request is already on an allow-listed path
(/auth/accept-terms, /auth/logout, /legal/*) so Terms are reachable
and logout works.
- New route /auth/accept-terms (GET renders the prompt, POST records
acceptance and bounces to a sanitised returnTo). Same-origin check
on returnTo to avoid open-redirect abuse. Logout button is provided
as an escape hatch.
- i18n: new auth.reaccept.* keys for EN and DE.
- Spec: new Requirement + five scenarios (redirect, allow-list,
successful re-accept, missing consent, returnTo sanitisation).
No action on the three legacy users is required beyond what they'll
experience on their next visit — the gate takes care of it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
49 lines
2.8 KiB
TypeScript
49 lines
2.8 KiB
TypeScript
import { type RouteConfig, index, route } from "@react-router/dev/routes";
|
|
|
|
export default [
|
|
index("routes/home.tsx"),
|
|
route(".well-known/trails-cool", "routes/api.well-known.trails-cool.ts"),
|
|
route("oauth/authorize", "routes/oauth.authorize.tsx"),
|
|
route("oauth/token", "routes/oauth.token.ts"),
|
|
route("auth/register", "routes/auth.register.tsx"),
|
|
route("auth/login", "routes/auth.login.tsx"),
|
|
route("auth/verify", "routes/auth.verify.tsx"),
|
|
route("auth/logout", "routes/auth.logout.tsx"),
|
|
route("auth/accept-terms", "routes/auth.accept-terms.tsx"),
|
|
route("api/auth/register", "routes/api.auth.register.ts"),
|
|
route("api/auth/login", "routes/api.auth.login.ts"),
|
|
route("routes", "routes/routes._index.tsx"),
|
|
route("routes/new", "routes/routes.new.tsx"),
|
|
route("routes/:id", "routes/routes.$id.tsx"),
|
|
route("routes/:id/edit", "routes/routes.$id.edit.tsx"),
|
|
route("api/routes/:id/callback", "routes/api.routes.$id.callback.ts"),
|
|
route("api/routes/:id/edit-in-planner", "routes/api.routes.$id.edit-in-planner.ts"),
|
|
route("api/routes/:id/gpx", "routes/api.routes.$id.gpx.ts"),
|
|
route("activities", "routes/activities._index.tsx"),
|
|
route("activities/new", "routes/activities.new.tsx"),
|
|
route("activities/:id", "routes/activities.$id.tsx"),
|
|
route("users/:username", "routes/users.$username.tsx"),
|
|
route("settings", "routes/settings.tsx"),
|
|
route("api/settings/profile", "routes/api.settings.profile.ts"),
|
|
route("api/settings/email", "routes/api.settings.email.ts"),
|
|
route("api/settings/passkey/delete", "routes/api.settings.passkey.delete.ts"),
|
|
route("api/settings/delete-account", "routes/api.settings.delete-account.ts"),
|
|
route("sync/import/:provider", "routes/sync.import.$provider.tsx"),
|
|
route("api/sync/connect/:provider", "routes/api.sync.connect.$provider.ts"),
|
|
route("api/sync/callback/:provider", "routes/api.sync.callback.$provider.ts"),
|
|
route("api/sync/disconnect/:provider", "routes/api.sync.disconnect.$provider.ts"),
|
|
route("api/sync/webhook/:provider", "routes/api.sync.webhook.$provider.ts"),
|
|
route("privacy", "routes/privacy.tsx"),
|
|
route("legal/imprint", "routes/legal.imprint.tsx"),
|
|
route("legal/terms", "routes/legal.terms.tsx"),
|
|
route("legal/privacy", "routes/legal.privacy.tsx"),
|
|
// REST API v1
|
|
route("api/v1/routes", "routes/api.v1.routes._index.ts"),
|
|
route("api/v1/routes/compute", "routes/api.v1.routes.compute.ts"),
|
|
route("api/v1/routes/:id", "routes/api.v1.routes.$id.ts"),
|
|
route("api/v1/activities", "routes/api.v1.activities._index.ts"),
|
|
route("api/v1/activities/:id", "routes/api.v1.activities.$id.ts"),
|
|
route("api/v1/auth/devices", "routes/api.v1.auth.devices.ts"),
|
|
route("api/v1/auth/devices/:id", "routes/api.v1.auth.devices.$id.ts"),
|
|
route("api/v1/uploads", "routes/api.v1.uploads.ts"),
|
|
] satisfies RouteConfig;
|