mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 00:07:00 +01:00
merge: reconcile paid hosting with latest main
This commit is contained in:
commit
ded6f905fd
180 files changed
+21709
-3614
No files matched your search
@@ -19,7 +19,10 @@ Configure:
|
||||
- Repository permissions: **Contents: read-only**, **Metadata: read-only**,
|
||||
**Pull requests: read-only**, **Pages: read-only**. Do not grant write permissions.
|
||||
Pull requests read also permits [reading PR issue comments](https://docs.github.com/en/rest/issues/comments#list-issue-comments); Issues permission is unnecessary.
|
||||
- Keep user access token expiration enabled. Private email permission is unnecessary.
|
||||
- User permissions: **Email addresses: read-only** to save a verified address at
|
||||
sign-in, including addresses hidden from the public GitHub profile. If this
|
||||
permission is unavailable, owners can enter an address in the email form.
|
||||
- Keep user access token expiration enabled.
|
||||
- Leave **Request user authorization (OAuth) during installation** unchecked.
|
||||
Anonymous GitHub authorizes the user before opening installation; the setup
|
||||
redirect must remain available afterward.
|
||||
@@ -105,6 +108,14 @@ Public reads use a [repository-scoped App user token](https://docs.github.com/en
|
||||
Each API request renews through its own repository binding, rechecking identity
|
||||
and public visibility while keeping the same user quota.
|
||||
Existing installation bindings retain their installation checks.
|
||||
|
||||
If a previously public source becomes private, its existing public-only binding
|
||||
continues to reject private reads. Select the repository in the App installation,
|
||||
then open the anonymization's Edit page and click **Repair GitHub access**. This
|
||||
verifies installation access to the same GitHub repository ID and published commit
|
||||
before replacing the binding. It preserves the anonymous URL, snapshot and settings.
|
||||
Signing in again alone does not change a resource's binding. The Connections page
|
||||
also provides **Reconnect read-only access** for this purpose.
|
||||
This uses GitHub's documented [public resource access for App user tokens](https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/choosing-permissions-for-a-github-app).
|
||||
|
||||
The explicit **Use existing OAuth access** choice handles repositories not yet
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Markdown rendering
|
||||
|
||||
Repository previews and Markdown project pages use `src/markdown.ts`. The browser build bundles that module into the lazy-loaded `markdown.min.js`; the project-page route calls it directly. Each document gets fresh parser extensions, references and heading IDs.
|
||||
|
||||
The compatibility suite covers:
|
||||
|
||||
- CommonMark blocks and inline formatting, GFM tables, task lists, strikethrough and autolinks.
|
||||
- GitHub heading IDs, repeated headings, custom anchors, relative links and images, picture sources, disclosures, keyboard text, subscript and superscript.
|
||||
- Emoji shortcodes, footnotes with repeated references, and all five GitHub alerts.
|
||||
- Inline dollar and dollar/backtick math, display math and fenced `math` blocks.
|
||||
- Syntax highlighting, Mermaid, GeoJSON, TopoJSON and ASCII STL fences. Invalid map/model blocks retain their source.
|
||||
- Sanitization, escaped syntax, code-block isolation, document-to-document state isolation and browser/server output parity.
|
||||
|
||||
Maps render their supplied geometry with zoom controls. STL models support zoom and rotation. These viewers do not reproduce GitHub's map tiles or 3D material controls. Math uses KaTeX rather than GitHub's MathJax, so MathJax-only commands are not guaranteed. Highlighting uses highlight.js's common language bundle; unrecognized languages remain readable code. Mermaid uses the locally shipped version, which may differ from GitHub's deployed version.
|
||||
|
||||
GitHub service features such as user mentions, issue-status badges, commit references and attachment uploads require GitHub identity or repository context. The anonymous viewer does not resolve those identities. It also does not execute repository HTML, arbitrary CSS or scripts.
|
||||
|
||||
The sanitizer runs on all Markdown-produced HTML. Math and diagram renderers insert their own generated markup through unpredictable, per-document markers after sanitization. TeX runs with `trust: false`; Mermaid runs with `securityLevel: strict`. Source HTML cannot opt into this bypass. Task inputs are always disabled checkboxes.
|
||||
|
||||
Run `npm run build:ui` before `npx mocha test/markdown-compatibility.test.js test/markdown-sanitization.test.js test/markdown-security.test.js test/mermaid-security.test.js test/asset-build.test.js`. The browser tests exercise the built bundle, not a copy of the renderer.
|
||||
|
||||
References: [GitHub basic syntax](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax), [math](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions), [diagrams](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams), and the [GFM specification](https://github.github.com/gfm/).
|
||||
|
||||
Diagram inputs are limited to one million characters and 60,000 coordinates. Maps also enforce nesting and TopoJSON expansion limits before conversion. Rejected diagrams display as code.
|
||||
|
||||
Mermaid, DOMPurify and KaTeX are npm dependencies so dependency audits cover their versions. After upgrading them, run `npm run vendor:markdown` followed by `npm run build:ui` and commit the refreshed assets. Security tests require the vendored files to match the installed packages.
|
||||
@@ -88,3 +88,40 @@ validators avoid fetching and rendering content again; changed terms or content
|
||||
invalidate them.
|
||||
Raw Markdown served after a rendering failure has a separate validator, so later
|
||||
requests retry rendering when the transient failure clears.
|
||||
|
||||
## Production failures and restarts
|
||||
|
||||
Run `node scripts/check-production-logs.js 15` from a machine with Node and the
|
||||
Docker CLI to summarize recent application logs and container states. For a
|
||||
remote Docker host, use
|
||||
`DOCKER_HOST=ssh://ubuntu@host node scripts/check-production-logs.js 15`.
|
||||
The optional
|
||||
window is 1–1440 minutes. The report includes completed 5xx responses, interrupted
|
||||
requests, token-refresh failures, missing-file warnings, native unhandled stream
|
||||
errors, and Docker restart counts. It prints counts and container metadata;
|
||||
request URLs, repository IDs, file paths, credentials, and Docker environment
|
||||
variables are excluded. Restart counts cover the container's lifetime, while log
|
||||
counts cover the selected window. Compare restart counts with the last deployment
|
||||
baseline even when every container is currently healthy.
|
||||
|
||||
The API and streamers log `process_fatal_error` through Node's
|
||||
`uncaughtExceptionMonitor`. This records fatal errors before the default process
|
||||
exit; it does not resume execution or swallow uncaught exceptions. Native stderr
|
||||
may describe the same crash as well, so fatal log indicators are not a count of
|
||||
unique process exits. Use Docker restart counts to confirm exits.
|
||||
|
||||
Legacy OAuth reset requests are coalesced by owner, credential, and OAuth app.
|
||||
A failed 401/404 reset backs off for 30 minutes; transient failures back off for
|
||||
one minute. A replaced credential or changed OAuth configuration gets a new key.
|
||||
GitHub App and personal access tokens do not use the OAuth reset endpoint. Refresh
|
||||
failure leaves the credential unchanged and continues through normal token
|
||||
validation and fallback.
|
||||
|
||||
Confirmed GitHub content 404s back off for 30 seconds in a bounded process-local
|
||||
cache. The key includes repository, path, commit, blob SHA, cache generation,
|
||||
revision, and credential. Permission failures and transport errors are not cached.
|
||||
This reduces repeated upstream misses without changing the file tree or treating
|
||||
an unavailable file as a successful cache fill. Persistent 404s still require
|
||||
checking the repository's source snapshot. Application rate limits remain in
|
||||
force; 429 replies now include `rate_limited`, `resetAt`, and `Retry-After` so
|
||||
clients can wait for the actual store reset.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Returning owner consent to review
|
||||
|
||||
An optional callback registry maps the authenticated intent's client and callback IDs to one exact HTTPS URL. Configuration rejects credentials, queries, fragments, noncanonical paths, duplicate IDs and duplicate JSON fields. It permits at most 32 entries and a 4 KiB form-action policy. Callback selection never uses a browser-supplied address.
|
||||
|
||||
After recording consent, the browser can request `POST /api/review-consent/handoff` with its signed ticket. The existing transport requires the current authenticated session, same origin and a session-bound CSRF token. The backend resolves the signed intent's callback before issuing a completion code and checks the current repository authority and saved consent. The transport reloads the session before returning the result. A missing registry leaves this endpoint unavailable.
|
||||
|
||||
The page submits the completion envelope as the sole `completion` field in a native HTML POST form. Codes never enter URL parameters or browser storage. The page's form-action policy allows only the configured destinations and its own origin. It removes the temporary form when CSP blocks submission and shows a retry/status message. A late result after an account change cannot submit a form. The signed ticket stays in a controller closure for a retry; it is not submitted to the callback.
|
||||
|
||||
The initial cross-site POST does not carry a review session cookie configured with SameSite=Strict. The review receiver must serve a non-caching landing page, then use a same-origin authenticated request and saved initiating-browser state before exchanging the code. Receiving the POST alone must not establish a binding. That receiver and startup activation are separate unfinished work. This change does not mount the upstream consent router or enable an integration.
|
||||
|
||||
Validation covers callback configuration, current-session HTTP access, concurrent logout, signed callback scope, consent requirements, browser POST contents, account changes and malformed completions. The disposable MongoDB suite contains 23 passing tests. The local 100-replay binding benchmark measured median 2.20 ms and p95 2.69 ms on a one-CPU, 1 GiB container; this excludes provider traffic. Browser fixture results use synthetic HTTP backend responses and test TLS certificates, so they do not establish provider or certificate-verification behavior.
|
||||
|
||||
The full local suite passed 762 tests, with 74 pending tests for optional fixtures. The targeted browser/page suite passed 17 tests, registry/HTTP tests passed 16 with one optional performance case pending, and TypeScript, targeted lint and the UI build passed. Chromium at 1280 and 320 pixels completed the native cross-site POST with no referrer or Strict cookie, no overflow, no external requests, no script errors and no WCAG A/AA violations. A third case blocked the destination with CSP and verified zero callback deliveries and removal of the code form. The first browser harness waited for a navigation that CSP intentionally blocked; changing the assertion to inspect the current document completed this case without changing application code.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Completion codes and binding receipts
|
||||
|
||||
The owner consent backend now saves the authenticated review intent with its immutable consent receipt. Existing consent records that lack this scope cannot issue completion codes. No caller may supply replacement submission, entitlement or policy fields.
|
||||
|
||||
`completion(actor, ticket)` requires the same current browser account and session, a matching confirmed consent and current repository authority. It issues one code per intent, expiring at the earliest of five minutes, intent expiry or retention expiry. Repeating issuance returns the same code without extending its lifetime. The database stores a random nonce and a code hash. A domain-separated HMAC using the private consent signing key makes the code recoverable after response loss without storing it in plaintext. Retain that key for the code lifetime; changing it fails closed for outstanding issuance retries.
|
||||
|
||||
`exchange(authenticatedClientId, command)` requires a separately verified service client. The argument is an internal authority boundary, not authentication by itself. First exchange checks the code, expiry, current account and repository authority, then atomically inserts a pending binding, a client-scoped request key and the original receipt. A later write failure rolls all three back. Competing transactions may report unavailable; callers explicitly retry the original command. The backend never automatically repeats a transaction.
|
||||
|
||||
Exact authenticated exchanges recover the original receipt after response loss, code expiry or later owner changes. They cannot create another binding. Changed codes under the committed request ID conflict, a spent code cannot be used with another request ID, and an existing request ID cannot bind another intent. `receipt(authenticatedClientId, intentId)` provides post-expiry reconciliation without accepting an expired code for first use. Both operations remain client scoped and expose only protocol identifiers and policy. They never return source coordinates, account IDs or repository IDs.
|
||||
|
||||
Bindings remain pending. No snapshot, artifact access, retained bytes or entitlement fulfillment is claimed. Browser completion POSTs, callback registration, service HTTP authentication, private configuration, startup wiring and activation remain separate tasks. The existing browser confirmation response intentionally does not expose the completion code yet.
|
||||
|
||||
Receipt recovery uses majority reads outside transactions. The factory limits active authority transactions and service reads to four each. MongoDB queries have three-second limits, majority writes have a three-second acknowledgment timeout and commits have a five-second limit. The serving connection must also have bounded selection/socket/pool waits. Secrets and encoded completion payloads must never be logged.
|
||||
|
||||
Tests use a disposable local MongoDB replica set and cover current ownership, absent consent, cross-session and cross-client attempts, altered codes, expiry, explicit replay, concurrent exchange, request-key reuse, rollback and persistence without raw tokens or codes. Set `TEST_REVIEW_CONSENT_MONGO` to the disposable replica-set URI and run `npx mocha test/review-owner-consent.test.js test/review-consent-http.test.js`. `TEST_REVIEW_COMPLETION_PERF_REPORT` records 100 immutable receipt replays; it excludes browser, TLS and provider latency.
|
||||
@@ -0,0 +1,15 @@
|
||||
# Completion service transport
|
||||
|
||||
`createReviewCompletionService` serves the exact `POST /service/v1/completions/exchange` and `GET /service/v1/intents/{intentId}/receipt` routes when explicitly mounted before body parsing and browser authentication. Startup does not mount it. An absent scoped registry returns typed 404 responses without touching the backend.
|
||||
|
||||
The private registry has exactly `version: 1` and `keys`. Each key contains `clientId`, `keyId`, `tokenSHA256`, `notBefore`, `notAfter` and `scopes`. Identifiers, ASCII-token hashing and whole-second validity windows follow the capabilities registry. Each key must explicitly authorize `completion.exchange`, `receipt.read`, or both. The existing capabilities-only configuration is rejected because it has no operation scopes. Limits are 64 KiB, 64 keys, 32 clients and 16 keys per client. Duplicate fields, key identities, digests and scope entries are rejected.
|
||||
|
||||
Requests use `X-4open-Artifact-Client-Id`, `X-4open-Artifact-Service-Key-Id` and `Authorization: Bearer <token>`. TLS is required, directly or through explicitly trusted proxy configuration. Browser cookies, origin/referrer/fetch metadata, duplicate headers, encoded bodies, query strings and route aliases are rejected. Authorization precedes body reads. Constant-time digest comparison and the key window are checked, with validity rechecked before backend execution and before returning a result.
|
||||
|
||||
The transport accepts at most 64 KiB of strict JSON. The shared decoder rejects invalid UTF-8, duplicate decoded keys and excessive nesting. Commands have exactly the v1 exchange fields. A successful receipt must contain exactly the expected client/intent scope and v1 receipt fields. Errors contain a fixed protocol code and fresh random correlation ID, never provider or database diagnostics. Responses disable caching, referrers and CORS. Rejections close the connection.
|
||||
|
||||
A client is limited to 60 requests per minute across its rotation keys. At most four operations run concurrently. Each request has a 15-second deadline. A disconnected or timed-out caller does not release an in-progress backend slot until the backend settles. A write may commit after response loss; callers must retain and explicitly retry their original request. No automatic retry occurs here.
|
||||
|
||||
Tests use real loopback HTTP, synthetic trusted-proxy TLS metadata and a simulated backend. They do not measure incoming TLS, MongoDB or provider latency. Set `TEST_REVIEW_COMPLETION_HTTP_PERF` to capture 50 authenticated receipt reads. The opt-in review-repository interoperability test also passes against this actual TypeScript handler over certificate-verified HTTPS. It covers explicit replay, changed-request conflicts, post-expiry receipt reads, policy mismatch, scope separation and untrusted certificates. Its backend remains synthetic. Credentials, startup wiring, browser callback handoff and activation remain unfinished; capabilities still advertise no available integration features.
|
||||
|
||||
The dedicated CI MongoDB fixture uses one CPU, a 1 GiB memory cap and a 512 MiB temporary data mount. The earlier 256 MiB mount failed during replica-set initialization with a no-space error; the increased fixture is still isolated and removed after testing. This does not change production storage or resource limits.
|
||||
@@ -0,0 +1,21 @@
|
||||
# Browser transport for review consent
|
||||
|
||||
`createReviewConsentRouter` exposes the consent backend through three routes when explicitly mounted at `/api/review-consent`: `GET /csrf`, `POST /preview` and `POST /confirm`. Application startup does not mount this router. Browser UI, private configuration, startup ordering and activation remain unfinished.
|
||||
|
||||
The factory requires a canonical HTTPS origin, the consent backend and a dedicated private 32-byte CSRF key shared by serving processes. Mount it after Passport/session authentication and before any JSON parser. The current startup ordering must change before integration; mounting after a parser fails closed. Transport must be HTTPS directly or through the application's explicitly trusted proxy configuration.
|
||||
|
||||
Identity comes from Passport's authenticated account and a fresh lookup in the session store. Neither request JSON nor a bearer credential can supply it. POSTs require the exact configured Origin and a constant-time-checked CSRF token bound to the account and session. CSRF tokens are derived with a protocol-specific HMAC; issuing one never saves a session. A late CSRF response therefore cannot recreate a session destroyed by a concurrent logout. Authority is reloaded after backend work before returning a preview or confirmation response.
|
||||
|
||||
Requests use exact paths without queries, strict methods, closed JSON fields and literal access/retention acceptance. Cross-origin or cross-site context, bearer authentication, duplicate security headers, content encoding and unexpected content types are rejected. The CSRF GET accepts no body. JSON is limited to 12 KiB; tickets to 8 KiB. A 15-second total deadline closes incomplete requests, and browser disconnects cancel pending preview consumption. Authenticated requests are limited to 60 per account per minute with at most 1,024 live counters.
|
||||
|
||||
Responses disable caching and referrers. Confirmations include only the request ID, accepted policy and confirmation time. Internal account/repository identifiers and provider/database errors are omitted. A confirmation already accepted by the backend can commit despite a lost response; the UI must preserve the same ticket and request ID for explicit recovery. This transport does not create artifact bindings or bypass final review-side authority checks.
|
||||
|
||||
## Validation
|
||||
|
||||
The tests use real Express and express-session middleware with a disposable in-memory session store, a synthetic backend and loopback HTTP representing the trusted TLS proxy boundary. They do not measure real MongoDB or provider latency. TLS verification of the service client and MongoDB authority transactions are tested separately.
|
||||
|
||||
Coverage includes session-derived identity, CSRF replay across sessions, cross-origin/bearer rejection, bounded closed payloads, exact routes, session revocation during preview, no session resurrection during a CSRF/logout race, response redaction, fixed errors, rate limits, unauthorized incomplete uploads, browser cancellation and the authenticated 15-second slow-upload deadline. The optional benchmark performs 50 fresh loopback HTTP preview requests. The CI database job runs these HTTP tests alongside the consent transaction tests and retains both performance reports.
|
||||
|
||||
The final local targeted run passes 12 cases in 16 seconds. Transport-only timings are mean 4.94 ms, median 4.51 ms and p95 7.26 ms. TypeScript and targeted lint pass.
|
||||
|
||||
The final local broader suite passes 723 cases with 63 opt-in/environment-dependent cases pending. The separate CI replica-set job exercises the otherwise skipped consent database tests as well as this transport suite.
|
||||
@@ -0,0 +1,13 @@
|
||||
# Artifact consent page
|
||||
|
||||
`/review-link` serves a separate page without the ordinary site's third-party widget or profile header. Its content security policy allows only first-party scripts and connections. The page rejects URL queries and noncanonical aliases, disables caching and referrers, and accepts only filename-shaped manifest entries.
|
||||
|
||||
The handoff fragment contains exactly `clientId`, `intentId` and `token`. The application removes it from the current history entry before starting application requests or the router. Intent credentials and signed preview tickets stay in a controller closure. They are not rendered or written to browser storage. Reloading loses the handoff; users must reopen a new link from their submission. Signed-out users sign in before reopening the link.
|
||||
|
||||
Owners enter the anonymous repository ID and request a preview. The backend supplies the repository name, access policy, retention deadline and expiry. Confirmation requires two separate unchecked approvals. A failed confirmation keeps the same signed ticket and random request ID for an explicit retry. Repository changes are disabled while that confirmation is uncertain. Account changes and page disposal cancel requests and discard late results. Success says that consent is recorded and that artifact linking remains incomplete.
|
||||
|
||||
The page does not mount the consent API or advertise available integration capabilities. Startup configuration, final binding exchange, provider activation and a review-side link launcher remain unfinished. The consent HTTP router still needs to be mounted after authentication and before body parsing.
|
||||
|
||||
Validation uses the compiled frontend bundles. Targeted tests cover fragment removal, storage, approval flags, replay identity, departure during CSRF, account changes during preview, invalid handoffs and policies, endpoint unavailability, expiry, sign-in and mismatched receipts. Server tests check canonical paths, headers and unsafe manifests. Chromium checks at 1280 and 320 pixels exercise the full synthetic form flow with CSP enabled and check overflow, external requests, script errors and WCAG A/AA rules. No live database or provider request is involved.
|
||||
|
||||
Run `npm run build:ui`, then `npx mocha test/review-consent-page.test.js test/review-consent-ui.test.js`. Set `TEST_REVIEW_PAGE_PERF_REPORT` to a private output path to measure 100 isolated HTML responses over loopback HTTP. This benchmark excludes browser rendering, database operations and provider latency.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Review intent consumption client
|
||||
|
||||
`src/server/service/review-intent-client.ts` consumes a saved review-side link intent over authenticated HTTPS. The caller supplies a trusted, exact HTTPS origin, a client-bound service credential with a validity window, and an explicit request ID. The returned policy and opaque identifiers come from the authenticated review response. Browser-supplied access, retention and callback fields are rejected.
|
||||
|
||||
The factory returns only `consume`; it copies credential values into a closure. A call makes one POST to `/service/v1/artifact-intents/consume`, validates the peer certificate with Node's trust store, and sends no browser cookies or origin. Redirects and automatic retries are disabled. An uncertain result requires the caller to retain the same request ID for an explicit retry.
|
||||
|
||||
Limits are four active attempts, a ten-second total deadline, five-second inactivity timeout, 16 KiB response headers and 64 KiB body. An AbortSignal cancels the attempt. Invalid credentials or payloads fail before I/O. Responses must match the configured client and requested intent, use the v1 closed schema and valid future timestamps, and omit cookies and content encoding. Duplicate JSON keys, including escaped spellings, invalid UTF-8 and unknown fields fail. Errors expose only a fixed category; remote bodies and credentials are not included.
|
||||
|
||||
This module is not wired into a route or configured in production. It does not authenticate an artifact owner, obtain consent, create a binding, exchange a completion code, or establish browser callback authority. Those are separate pending tasks. Credential distribution and upstream deployment also remain pending.
|
||||
|
||||
## Validation
|
||||
|
||||
Local tests use a disposable certificate and loopback HTTPS server. A child process loads that certificate through `NODE_EXTRA_CA_CERTS` at startup; an independent child with ordinary trust rejects it before sending credentials. No TLS bypass is used. Tests cover successful policy reads, copied configuration, request-ID replay, rejection before I/O, status classification without retry, malformed and oversized responses, duplicate keys, four-slot saturation and recovery, cancellation, credential expiry, inactivity and slow-stream total deadlines.
|
||||
|
||||
- `npx mocha test/review-intent-client.test.js`: 11 behavioral cases; one performance case is opt-in.
|
||||
- `npm test`: 712 pass, 50 pending opt-in or environment-dependent cases.
|
||||
- Full TypeScript check and targeted ESLint pass.
|
||||
- Opt-in 100-request performance run: fresh loopback TLS connections, mean 12.36 ms, median 12 ms, p95 15 ms. These are local synthetic timings, not an external-service latency promise.
|
||||
|
||||
Run the measured test with `TEST_REVIEW_INTENT_PERF=1 TEST_REVIEW_INTENT_PERF_REPORT=/tmp/review-intent-perf.json npx mocha test/review-intent-client.test.js`.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Review owner consent backend
|
||||
|
||||
`createReviewOwnerConsent` prepares a signed policy preview from the authenticated review intent consumer and records explicit access/retention acceptance in MongoDB. It is an unwired backend. Browser routes, CSRF protection, UI, completion-code exchange and activation remain unfinished. Capabilities continue to advertise no available integration features.
|
||||
|
||||
The caller must provide an account ID and session ID derived from a current authenticated browser session. Never populate this principal from request JSON or treat a service credential as an owner's identity. The factory also needs the configured intent consumer, a dedicated random 32-byte signing key held outside Git, and a connected MongoDB replica set. Configure bounded server-selection and socket timeouts on that connection. Tests use MongoDB 7 with a three-second server-selection timeout and ten-second socket timeout.
|
||||
|
||||
Preview checks the current account and repository before consuming the review intent, then checks them again afterward. The network request holds no MongoDB transaction or authority lock. The account must be active or have the legacy unset active status; the repository must be ready and unexpired. Ownership uses the current account ObjectId. Coauthors must match the account's current numeric GitHub ID. Legacy username-only coauthors and unrelated administrators cannot provide consent.
|
||||
|
||||
The signed preview binds the saved intent and exact policy to the account, hashed session ID and repository ObjectId. Changing the account, session, repository or policy invalidates confirmation. The signing context is specific to this protocol; key rotation invalidates outstanding previews. Confirmation requires both access and retention acceptance and an explicit 32-hex request ID. Preview and replay expire with the intent or retention policy.
|
||||
|
||||
Confirmation writes one immutable `review_owner_consents` record per client/intent. It records the account, repository, authority role, exact policy, request ID, confirmation time and a hash of the signed preview. It stores no raw session ID, intent token, service credential or preview ticket. Explicit retries return the same receipt only after current authority checks; another request ID or repository conflicts. No binding is created by accepting a policy.
|
||||
|
||||
Transactions touch the current account and repository before writing or reading the receipt. Ownership transfer, coauthor removal and account disablement therefore serialize with confirmation. The private `reviewConsentRevision` fields are excluded from ordinary Mongoose reads. Four transactions may be active per factory. Database commands and commits have limits; transactions are not automatically retried. A conflict or uncertain commit returns a fixed `unavailable` error, and the caller must retain the same ticket and request ID for explicit recovery.
|
||||
|
||||
The final binding flow must still revalidate the initiating review session and saved intent scope with the review service. This backend proves current upstream owner/coauthor consent, not continuing review-side author authority, immutable snapshots, restricted file access or preservation. Receipt retention/export and the HTTP adapter remain separate work; this change enables no provider or public route.
|
||||
|
||||
## Validation
|
||||
|
||||
The opt-in suite runs against a disposable MongoDB replica set. It covers literal acceptance, immutable replay, stable-ID coauthors, administrator/username rejection, ticket tampering, account/session binding, disabled accounts, ownership changes, removed coauthors, archived/expired repositories, permission changes during upstream I/O, concurrent confirmations, expiry, second-repository conflicts and authority-field privacy. A held ownership-change transaction forces a real write conflict; the explicit retry is denied and no receipt is created.
|
||||
|
||||
Run `TEST_REVIEW_CONSENT_MONGO='mongodb://127.0.0.1:28743/?replicaSet=review-consent-test' npx mocha test/review-owner-consent.test.js`. Set `TEST_REVIEW_CONSENT_PERF_REPORT=/tmp/review-consent-perf.json` to include 100 durable receipt replays. The CI job starts and removes its own pinned MongoDB image and preserves the performance report. Local timings are synthetic and are not a production latency guarantee.
|
||||
|
||||
Local validation passes 12 targeted cases with the benchmark enabled, 723 full-suite cases with 51 opt-in/environment-dependent cases pending, the full TypeScript check and targeted lint. The final 100-replay run measured mean 12.85 ms, median 12.82 ms and p95 14.89 ms against a one-CPU, 512 MiB disposable database. An earlier performance attempt returned `unavailable` while the privacy fixture was automatically building indexes. The log does not establish the underlying database error. The final fixture disables automatic schema/index creation and creates its collections explicitly.
|
||||
@@ -0,0 +1,84 @@
|
||||
# Review service capabilities
|
||||
|
||||
`GET /service/v1/capabilities` is an optional server-to-server endpoint for the
|
||||
`4open.artifacts/1` contract. It returns the authenticated client ID, the supported
|
||||
contract version, and an empty `available` list. Linking, snapshots, protected
|
||||
files, retention, publication, entitlements and usage are not advertised. Clients
|
||||
must decline those workflows until their required capabilities are implemented.
|
||||
|
||||
The endpoint is mounted before JSON parsing, browser sessions and passport. The
|
||||
entire `/service` namespace returns a typed 404 when configuration is absent.
|
||||
Only the exact GET path is accepted. Query strings, browser context headers,
|
||||
cookies, bodies, duplicate authentication headers and unknown paths are rejected.
|
||||
Rejections close the connection immediately, including requests whose declared
|
||||
body has not arrived. Responses contain no credentials, owner identity, repository data, cookies or CORS
|
||||
permission; they have `no-store` and `no-referrer` headers. No database, queue or
|
||||
provider call is involved.
|
||||
|
||||
## Private configuration
|
||||
|
||||
Leave `REVIEW_SERVICE_KEYS` unset until activation is approved. If configured,
|
||||
its value is a JSON object with exactly `version: 1` and `keys`. Each key has
|
||||
exactly these fields:
|
||||
|
||||
- `clientId`: 32 lowercase hexadecimal characters.
|
||||
- `keyId`: 1–64 ASCII letters, digits, underscores or hyphens.
|
||||
- `tokenSHA256`: SHA-256 of the **ASCII 64-character lowercase hexadecimal bearer
|
||||
token**, stored as 64 lowercase hexadecimal characters. Do not hash the decoded
|
||||
token bytes and do not store a plaintext token in this registration.
|
||||
- `notBefore` and `notAfter`: UTC timestamps, `YYYY-MM-DDTHH:mm:ssZ`, in 2000–2099.
|
||||
The start is inclusive and the end exclusive.
|
||||
|
||||
Use private runtime configuration outside Git. The caller sends
|
||||
`Authorization: Bearer <token>`, `X-4open-Artifact-Client-Id` and
|
||||
`X-4open-Artifact-Service-Key-Id`. Keys grant only capabilities reads. Generate a
|
||||
separate token for this operation and environment; never reuse GitHub credentials,
|
||||
webhook secrets or future exchange credentials. The Go review client requires
|
||||
HTTPS with a trusted server certificate; a deployment must provide that transport.
|
||||
|
||||
The registry is bounded to 64 KiB, 64 keys, 32 clients and 16 keys per client.
|
||||
Duplicate client/key pairs and reused digests are rejected. Unknown fields,
|
||||
plaintext secret fields, invalid timestamps and empty configuration fail startup
|
||||
with a generic diagnostic. Configuration uses ordinary JSON object parsing.
|
||||
Credentials are copied into a private registry and compared by constant-time
|
||||
SHA-256 digest equality. Overlapping validity windows permit rotation. Remove a
|
||||
key and restart to revoke it; this is not hot reloading.
|
||||
|
||||
Authenticated clients share 60 reads per minute across their keys. Counters are
|
||||
bounded by registered clients, reset lazily, and have no timer. Limits are local
|
||||
to each server process; they are not a distributed quota. A 429 response supplies
|
||||
a typed failure and a bounded retry delay. This endpoint does not accept browser
|
||||
session or repository-owner authority.
|
||||
|
||||
## Verification
|
||||
|
||||
`mocha --no-config --no-package test/review-capabilities.test.js` passes eight
|
||||
HTTP/configuration scenarios, including 60 concurrent reads, cross-client and
|
||||
wrong-key rejection, raw duplicate headers, exact path checks, browser context,
|
||||
expiry, overlapping keys and restart revocation. Add
|
||||
`RUN_REVIEW_SERVICE_BENCHMARK=1` for a 150-request synthetic loopback measurement.
|
||||
The clean-install run averaged 2.035 ms, median 1.732 ms and p95 2.417 ms with fresh
|
||||
HTTP connections. These are local tooling measurements, not production latency.
|
||||
|
||||
A local HTTPS interoperability rehearsal uses the existing Go
|
||||
`artifactclient.NewCapabilitiesReader` at review source `ee07d06`. It verifies
|
||||
certificate trust, accepts the empty capability response, rejects both unsupported
|
||||
access policies and rejects a wrong credential. No external provider is contacted.
|
||||
|
||||
A clean `npm ci` install, the full TypeScript check and emitted build, UI build,
|
||||
and targeted ESLint pass. The full test suite reports 700 passing in 36 seconds
|
||||
and 49 pending opt-in cases; those skipped cases are not claimed as tested.
|
||||
The capability suite separately passes eight cases including its benchmark.
|
||||
|
||||
An initial check used dependencies copied from another checkout and produced
|
||||
TypeScript diagnostics. Those installed versions differed from the lockfile;
|
||||
reinstalling the locked dependencies resolved every diagnostic without source
|
||||
changes. Use the lockfile when reproducing these results.
|
||||
|
||||
The endpoint is implemented and tested locally. Upstream deployment, private key
|
||||
distribution, ownership consent and the remaining artifact workflows are pending.
|
||||
|
||||
A raw-socket regression first reproduced a rejected request waiting for its unread
|
||||
10 MB body. Closing failed connections makes the same header-only request return
|
||||
400 and close immediately; the complete eight-case endpoint suite and full
|
||||
TypeScript checking pass. Full PR CI is repeated for this correction.
|
||||
Reference in new issue
Block a user