Skip to content

Your user will own their own data! This is a backend code that implements user state persistance into their own personal GitHub repository.

License

Notifications You must be signed in to change notification settings

viktor-shcherb/open-user-state

Repository files navigation

Open User State Backend

Coverage Backend Status

This project hosts a Cloudflare Worker that serves as a backend for frontend. The aim is to authenticate users via GitHub and sync editor state to a repository using a Personal Access Token (PAT).

The code is split into small modules under src/: auth.ts handles OAuth and token encryption, repo.ts manages repository preferences and files.ts wraps the GitHub file APIs. index.ts wires these together and exposes the HTTP routes.

Development

Install dependencies and start a local dev server:

npm install
npm run dev

Run the test suite with coverage:

npm run coverage

Vitest expects a Node runtime that exposes the standard webcrypto API on globalThis.crypto (Node 18+). When running on older versions the tests will shim the API automatically.

OAuth & State Storage

Users authenticate via GitHub OAuth. The worker stores a short session identifier in SESSIONS once the callback exchange succeeds. Subsequent requests use this cookie to look up the GitHub login and numeric id.

A separate POST /api/token call persists a fine grained PAT encrypted in the USER_PAT_STORE namespace. Repository preferences are saved per user in USER_REPO_STORE. Editor state is committed as plain text files; each file is written individually and overwrites any existing blob at that path.

API Endpoints

The worker exposes a small set of routes used by the frontend to authenticate with GitHub, store a Personal Access Token (PAT) and manage the repository where user state is kept.

GET /api/health

Returns a small JSON payload { status: 'ok' } which can be used by the frontend to verify that the backend is running. A HEAD request returns the same headers without the body.

GET|POST /api/auth/github

Initiates the OAuth login flow. The backend responds with a redirect to GitHub and accepts both GET and POST methods. From the browser you can trigger the flow with:

await fetch('/api/auth/github', { method: 'POST', credentials: 'include' })
  .then(res => {
    if (res.redirected) window.location.href = res.url;
  });

GET /api/auth/github/callback

GitHub redirects back to this route after the user approves the OAuth request. The worker exchanges the code parameter for a short‑lived access token, creates a session cookie, and then redirects the user to /. This endpoint is handled automatically as part of the OAuth redirect and does not need to be called manually from the frontend.

POST /api/token

Stores a fine‑grained PAT for the authenticated user. The request must include the session cookie set during OAuth login.

await fetch('/api/token', {
  method: 'POST',
  credentials: 'include',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ pat }),
});

The token will be encrypted and stored securely in the worker's user-pat-store KV namespace.

DELETE /api/token

Removes the stored PAT for the current session. Subsequent file or repository operations will fail until a new token is provided.

POST /api/logout

Clears the active session cookie on the server so subsequent requests are unauthenticated.

POST /api/repository

Persists the GitHub repository where user state files will be written. The body must include { repo: 'owner/name' } and the request requires a valid session cookie.

GET /api/repository

Returns the currently selected repository for the authenticated user in the form { repo: string }.

GET /api/profile

Returns { username, avatar, patValid, repo } for the authenticated user. patValid indicates whether the stored PAT successfully fetches the account information from GitHub.

POST /api/file

Commits a text file to the selected repository. The JSON payload should include path, content and optionally a commit message. The file is created if it does not exist or overwritten when the contents differ. The repository will be created automatically if missing.

GET /api/file

Retrieves the raw text at the given path from the selected repository. The path is provided as a query parameter. When the file or repository is missing the response is 404.

GET /api/files

Lists the entries under a directory in the selected repository. Pass the path query parameter to specify the directory (or omit for the repo root). The response is an array like { files: string[] } containing file and folder names.

Error Codes

All error responses use the JSON shape { "error": "CODE" }. Consult docs/errors.md for a list of possible codes and the routes that may produce them.

About

Your user will own their own data! This is a backend code that implements user state persistance into their own personal GitHub repository.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Contributors 2

  •  
  •