{"id":30539037,"url":"https://github.com/viktor-shcherb/open-user-state","last_synced_at":"2025-08-27T21:24:38.967Z","repository":{"id":304965306,"uuid":"1020713055","full_name":"viktor-shcherb/open-user-state","owner":"viktor-shcherb","description":"Your user will own their own data! This is a backend code that implements user state persistance into their own personal GitHub repository.","archived":false,"fork":false,"pushed_at":"2025-07-16T17:00:13.000Z","size":110,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-07-17T13:55:44.979Z","etag":null,"topics":["aes-gcm","cloudflare-workers","github-oauth","kv-storage","personal-access-token","state-management","typescript","vitest"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/viktor-shcherb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"docs/security.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-07-16T09:28:50.000Z","updated_at":"2025-07-16T16:20:03.000Z","dependencies_parsed_at":"2025-07-17T17:23:31.757Z","dependency_job_id":"41ec0a3b-a37b-40fe-a2c1-f0485708f67b","html_url":"https://github.com/viktor-shcherb/open-user-state","commit_stats":null,"previous_names":["viktor-shcherb/open-user-state"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/viktor-shcherb/open-user-state","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viktor-shcherb%2Fopen-user-state","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viktor-shcherb%2Fopen-user-state/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viktor-shcherb%2Fopen-user-state/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viktor-shcherb%2Fopen-user-state/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/viktor-shcherb","download_url":"https://codeload.github.com/viktor-shcherb/open-user-state/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viktor-shcherb%2Fopen-user-state/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272382608,"owners_count":24924972,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-27T02:00:09.397Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes-gcm","cloudflare-workers","github-oauth","kv-storage","personal-access-token","state-management","typescript","vitest"],"created_at":"2025-08-27T21:24:36.989Z","updated_at":"2025-08-27T21:24:38.961Z","avatar_url":"https://github.com/viktor-shcherb.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003c!--\n  This README describes the Cloudflare Worker backend for Open User State.\n  It explains local development steps and documents the REST API used by the frontend.\n--\u003e\n# Open User State Backend\n\n[![Coverage](https://codecov.io/gh/viktor-shcherb/open-user-state/branch/master/graph/badge.svg)](https://codecov.io/gh/viktor-shcherb/open-user-state)\n[![Backend Status](https://img.shields.io/website?url=https%3A%2F%2Fopen-user-state-personal-website.viktoroo-sch.workers.dev%2Fapi%2Fhealth)](https://open-user-state-personal-website.viktoroo-sch.workers.dev/api/health)\n\nThis project hosts a Cloudflare Worker that serves as a backend for\n[frontend](https://github.com/viktor-shcherb/viktor-shcherb.github.io). The aim\nis to authenticate users via GitHub and sync editor state to a repository using\na Personal Access Token (PAT).\n\nThe code is split into small modules under `src/`:\n`auth.ts` handles OAuth and token encryption, `repo.ts` manages repository\npreferences and `files.ts` wraps the GitHub file APIs. `index.ts` wires these\ntogether and exposes the HTTP routes.\n\u003c!--\n  Development instructions demonstrate how to run the worker locally\n  and execute tests so contributors can verify changes quickly.\n--\u003e\n\n## Development\n\nInstall dependencies and start a local dev server:\n\n```bash\nnpm install\nnpm run dev\n```\n\nRun the test suite with coverage:\n\n```bash\nnpm run coverage\n```\n\nVitest expects a Node runtime that exposes the standard `webcrypto` API on\n`globalThis.crypto` (Node 18+). When running on older versions the tests will\nshim the API automatically.\n\n## OAuth \u0026 State Storage\n\nUsers authenticate via GitHub OAuth. The worker stores a short session\nidentifier in `SESSIONS` once the callback exchange succeeds. Subsequent\nrequests use this cookie to look up the GitHub `login` and numeric `id`.\n\nA separate `POST /api/token` call persists a fine grained PAT encrypted in the\n`USER_PAT_STORE` namespace. Repository preferences are saved per user in\n`USER_REPO_STORE`. Editor state is committed as plain text files; each file is\nwritten individually and overwrites any existing blob at that path.\n\n\u003c!--\n  The API Endpoints section catalogs each HTTP route exposed by the worker\n  so the frontend knows how to authenticate and store user state.\n--\u003e\n\n## API Endpoints\n\nThe worker exposes a small set of routes used by the frontend to authenticate\nwith GitHub, store a Personal Access Token (PAT) and manage the repository\nwhere user state is kept.\n### `GET /api/health`\n\nReturns a small JSON payload `{ status: 'ok' }` which can be used by the\nfrontend to verify that the backend is running. A `HEAD` request returns the\nsame headers without the body.\n\n\n### `GET|POST /api/auth/github`\n\nInitiates the OAuth login flow. The backend responds with a redirect to GitHub\nand accepts both `GET` and `POST` methods. From the browser you can trigger the\nflow with:\n\n```ts\nawait fetch('/api/auth/github', { method: 'POST', credentials: 'include' })\n  .then(res =\u003e {\n    if (res.redirected) window.location.href = res.url;\n  });\n```\n\n### `GET /api/auth/github/callback`\n\nGitHub redirects back to this route after the user approves the OAuth request.\nThe worker exchanges the `code` parameter for a short‑lived access token,\ncreates a session cookie, and then redirects the user to `/`.\nThis endpoint is handled automatically as part of the OAuth redirect and does\nnot need to be called manually from the frontend.\n\n### `POST /api/token`\n\nStores a fine‑grained PAT for the authenticated user. The request must include\nthe session cookie set during OAuth login.\n\n```ts\nawait fetch('/api/token', {\n  method: 'POST',\n  credentials: 'include',\n  headers: { 'Content-Type': 'application/json' },\n  body: JSON.stringify({ pat }),\n});\n```\n\nThe token will be encrypted and stored securely in the worker's `user-pat-store`\nKV namespace.\n\n### `DELETE /api/token`\n\nRemoves the stored PAT for the current session. Subsequent file or repository\noperations will fail until a new token is provided.\n\n### `POST /api/logout`\n\nClears the active session cookie on the server so subsequent requests are\nunauthenticated.\n\n### `POST /api/repository`\n\nPersists the GitHub repository where user state files will be written. The body\nmust include `{ repo: 'owner/name' }` and the request requires a valid session\ncookie.\n\n### `GET /api/repository`\n\nReturns the currently selected repository for the authenticated user in the form\n`{ repo: string }`.\n\n### `GET /api/profile`\n\nReturns `{ username, avatar, patValid, repo }` for the authenticated user.\n`patValid` indicates whether the stored PAT successfully fetches the account\ninformation from GitHub.\n\n### `POST /api/file`\n\nCommits a text file to the selected repository. The JSON payload should include\n`path`, `content` and optionally a commit `message`. The file is created if it\ndoes not exist or overwritten when the contents differ. The repository will be\ncreated automatically if missing.\n\n### `GET /api/file`\n\nRetrieves the raw text at the given `path` from the selected repository. The\npath is provided as a query parameter. When the file or repository is missing\nthe response is `404`.\n\n### `GET /api/files`\n\nLists the entries under a directory in the selected repository. Pass the `path`\nquery parameter to specify the directory (or omit for the repo root). The\nresponse is an array like `{ files: string[] }` containing file and folder\nnames.\n\n## Error Codes\n\nAll error responses use the JSON shape `{ \"error\": \"CODE\" }`. Consult\n[docs/errors.md](docs/errors.md) for a list of possible codes and the routes\nthat may produce them.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviktor-shcherb%2Fopen-user-state","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fviktor-shcherb%2Fopen-user-state","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviktor-shcherb%2Fopen-user-state/lists"}