Skip to content

Server and access architecture

The @mauriciodmo/framekit/server entrypoint is Node/server-only. It exposes the HTTP handlers and the rendering primitives that the App Router routes bind to a generated template registry.

The canonical route in the first-party app and generated template is a Node.js, dynamic catch-all route. It creates one createFrameKitApiHandler(templates) and exports it for GET, POST, PATCH, and DELETE.

The handler has two branches:

  • POST /api/framekit/images/render goes to the image handler.
  • Other supported /api/framekit/... paths go to the access handler, which matches exact paths and methods before invoking a route handler.

Cookie-authenticated mutations require a same-origin request. Authorization is performed server-side; hiding a control in the client does not grant access.

The single server-side auth switch is FRAMEKIT_AUTH_ENABLED. Missing or false selects open mode: the access branch returns not found, /editor and /brand render without a user, /login redirects to /editor, and the image handler skips credential checks while retaining renderer defenses. true enables the access branch, users, sessions, API tokens, and protected Studio routes. Other values are configuration errors; there is no NODE_ENV, credential, or SQLite fallback.

When authentication is enabled, the access layer opens SQLite lazily and runs the current schema migration. The default database path is .framekit-data/framekit.sqlite; FRAMEKIT_DATABASE_PATH can select another path or :memory: for a process. The connection enables foreign keys, WAL mode, and a busy timeout. Open mode does not initialize SQLite or create an anonymous administrator.

The schema currently contains:

  • users, with admin and user roles and an active flag;
  • sessions, which stores a hash of each session secret and its expiry; and
  • api_tokens, which stores token metadata, a token hash, last use, and revocation state.

On the first login against an empty database, and only when auth is enabled, FRAMEKIT_ADMIN_PASSWORD and the optional FRAMEKIT_ADMIN_USERNAME bootstrap the first active administrator. Passwords are hashed before storage. The database returns a safe StudioUser DTO, not password or token secrets.

flowchart LR
  login["POST /api/framekit/login"] --> database["SQLite users and sessions"]
  database --> cookie["framekit_session cookie"]
  cookie --> studioPage["createStudioPage"]
  studioPage --> sessionCheck["getSession and active-user check"]
  sessionCheck --> userDto["StudioUser"]
  bearer["Authorization: Bearer fk_..."] --> tokenCheck["authenticateApiToken"]
  tokenCheck --> imageHandler["Image handler authorization"]
  cookie --> imageHandler
  userDto --> studioRoutes["Studio sections"]
  sessionCheck --> accessRoutes["requireSession"]
  accessRoutes --> roleCheck["requireAdministrator and ownership checks"]
  roleCheck --> managementRoutes["authorized access routes"]

Sessions are 30-day records. The client receives only the user id, username, and role. A session for an expired, inactive, or deleted user is rejected.

API token secrets begin with fk_. The full secret is returned only when the token is created; SQLite stores its hash and later listings expose metadata only. An active owner can use an unrevoked token. A normal user can manage its own tokens, while an administrator can manage users and inspect or revoke another user’s token metadata.

When auth is enabled, the protected Studio sections are /editor, /brand, and /settings; the login page renders when no valid session exists and redirects an existing session to /editor. In open mode, /editor and /brand are direct, /settings is absent, and /login redirects to /editor. Access route details belong in the access API reference and the account guide.

The image handler validates render configuration first. When auth is enabled, it then authenticates before reading the request body or loading a template. In open mode it does not require a credential. It then parses the render request, loads the matching registry entry, prepares field data and image inputs, resolves the canonical template data, and calls renderTemplateImage.

renderTemplateImage reserves bounded render capacity, creates an in-memory render job with a short-lived identifier and token, opens a headless Chromium context at the template dimensions, and navigates only to the private render route. The route accepts the job token in x-framekit-render-token, resolves the payload, and renders through the generated client. Chromium waits for the ready marker and image decoding before capturing one PNG root. The job, page, context, and capacity lease are cleaned up afterward.

The image response is image/png with no-store behavior. Remote image inputs are prepared and constrained before Chromium loads them. See image rendering for the supported request and error contract.