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.
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.
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.
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.
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.
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;
});
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.
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.
Removes the stored PAT for the current session. Subsequent file or repository operations will fail until a new token is provided.
Clears the active session cookie on the server so subsequent requests are unauthenticated.
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.
Returns the currently selected repository for the authenticated user in the form
{ repo: string }
.
Returns { username, avatar, patValid, repo }
for the authenticated user.
patValid
indicates whether the stored PAT successfully fetches the account
information from GitHub.
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.
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
.
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.
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.