trails/apps/journal/app/lib/jwt.server.ts
Ullrich Schäfer 40e541fcce Implement Planner-Journal handoff (Group 9)
JWT-based handoff between Journal and Planner:

Journal side:
- JWT token generation scoped to route_id with 7-day expiry (jose)
- "Edit in Planner" button on route detail page — generates JWT,
  redirects to Planner /new with callback URL, token, and GPX
- Callback endpoint (POST /api/routes/:id/callback) validates JWT
  and creates new route version from received GPX

Planner side:
- /new route accepts callback, token, returnUrl, gpx params
- Creates session with callback metadata, initializes with GPX
- "Save to Journal" button POSTs GPX with Bearer token to callback
- "Return to Journal" link shown after successful save

All 6 Group 9 tasks complete.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 20:22:58 +00:00

27 lines
841 B
TypeScript

import { SignJWT, jwtVerify } from "jose";
const JWT_SECRET = new TextEncoder().encode(
process.env.JWT_SECRET ?? "dev-jwt-secret-change-in-production",
);
const ISSUER = process.env.ORIGIN ?? "http://localhost:3000";
export async function createRouteToken(routeId: string, permissions: string[] = ["read", "write"]): Promise<string> {
return new SignJWT({ route_id: routeId, permissions })
.setProtectedHeader({ alg: "HS256" })
.setIssuer(ISSUER)
.setExpirationTime("7d")
.setIssuedAt()
.sign(JWT_SECRET);
}
export async function verifyRouteToken(token: string): Promise<{ routeId: string; permissions: string[] }> {
const { payload } = await jwtVerify(token, JWT_SECRET, {
issuer: ISSUER,
});
return {
routeId: payload.route_id as string,
permissions: payload.permissions as string[],
};
}