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>
This commit is contained in:
parent
f47eca565c
commit
40e541fcce
12 changed files with 287 additions and 11 deletions
27
apps/journal/app/lib/jwt.server.ts
Normal file
27
apps/journal/app/lib/jwt.server.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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[],
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue