Automated Cloudflare deploys (cert/production) + company-scoped OAuth tokens - #32
Merged
Merged
Conversation
Introduce named Wrangler environments and a CI-gated deploy workflow so pushes to `cert` deploy the cert Worker and (in the future) pushes to `main` deploy a separate production Worker. - wrangler.jsonc: top-level config is now the local "dev" environment (drops the unused `database_id`, since local dev runs with `--local`). Add `env.cert` (Worker `frc-design-app-cert`, reuses the existing remote D1/KV/R2) and `env.production` (Worker `frc-design-app-production`, with placeholder resource IDs to be filled in once prod resources exist). - onshape-api.ts: hardcode the Onshape API base path and version as constants (identical across environments) instead of repeating them as per-environment vars. Only `ADMIN_TEAM` remains a var. - deploy.yml: new workflow. Runs tsc/lint/test, then deploys the matching environment via cloudflare/wrangler-action. The Vite plugin selects the target env at build time via CLOUDFLARE_ENV; migrations run with `--env`. Production runs in a protected GitHub Environment and is dormant until a `main` branch exists. - ci.yml: drop the redundant push-to-cert trigger (now covered by deploy). - vite.config.ts: only enable the HTTPS dev server when the mkcert files exist, so `vite build` works in CI where they are absent. - package.json: replace `deploy` with `deploy:cert` / `deploy:production`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdsRyJRctf2K3v73QdtgGo
Onshape access tokens are company-scoped, but the app previously ignored the `sessionCompanyId` the Onshape iframe passes on `/init`, so a user who authorized in one context could end up with a token scoped to the wrong company (or personal) when opening another company's document. - isAuthenticated now fetches session info (which also serves as the auth ping) and compares the token's company (`sessioninfo.company.id`, null = personal "cad") against the request's `sessionCompanyId`. On a mismatch it returns false, so `/init` restarts the sign-in flow. - Every token request now passes `company_id` to the OAuth token endpoint via a `companyTokenEndpoint` helper — for both the authorization-code exchange in doCallback and refreshes in getOnshapeApiForSessionId — so the minted token is company-scoped. - The requested company is captured at sign-in (from the request or the `/init` redirectUrl) into the login session, and persisted onto the stored tokens so refreshes keep the scope. - Type getSessionInfo with a SessionInfo interface exposing `company.id`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EdsRyJRctf2K3v73QdtgGo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch contains two related-but-independent changes.
1. Automated Cloudflare deployments (dev / cert / production)
Introduces named Wrangler environments and a CI-gated deploy workflow so pushes to
certdeploy the cert Worker and (in the future) pushes tomaindeploy a separate production Worker.wrangler.jsonc: top-level config is now the local dev environment (drops the unuseddatabase_id, since local dev runs--local). Addsenv.cert(Workerfrc-design-app-cert, reusing the existing remote D1/KV/R2) andenv.production(Workerfrc-design-app-production, with placeholder resource IDs to fill in once prod resources exist). Bindings/vars are redeclared per env since they don't inherit.onshape-api.ts: the Onshape API base path and version are identical across environments, so they're hardcoded as constants ingetBaseUrl()instead of repeated as per-env vars. OnlyADMIN_TEAMremains a var..github/workflows/deploy.yml(new): runs tsc/lint/test, then deploys the matching environment viacloudflare/wrangler-action@v3. The Vite plugin selects the target env at build time viaCLOUDFLARE_ENV; migrations run with--env. Production runs in a protected GitHub Environment and is dormant until amainbranch exists.ci.yml: drops the redundant push-to-cert trigger (now covered by the deploy workflow).vite.config.ts: only enables the HTTPS dev server when the mkcert files exist, sovite buildworks in CI where they're absent (previously an unconditionalreadFileSyncwould crash the build).package.json: replacesdeploywithdeploy:cert/deploy:production.Follow-up needed before cert deploys go live (outside this PR)
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID.certandproduction(required reviewer onproduction).frc-design-app-certWorker — set its runtime secrets, repoint the custom domain from the oldfrc-design-appWorker, then delete the old Worker. (Domain-keyed OAuth/Onshape URLs keep working.)2. Company-scoped OAuth tokens
Onshape access tokens are company-scoped, but the app previously ignored the
sessionCompanyIdthe Onshape iframe passes on/init, so a user who authorized in one context could end up with a token scoped to the wrong company when opening another company's document.isAuthenticatedfetches session info (which also serves as the auth ping) and compares the token's company (sessioninfo.company.id, null = personal"cad") against the request'ssessionCompanyId. On a mismatch it returnsfalse, so/initrestarts the sign-in flow.company_idto the OAuth token endpoint via acompanyTokenEndpointhelper — for both the authorization-code exchange indoCallbackand refreshes ingetOnshapeApiForSessionId— so the minted token is company-scoped./initredirectUrl) into the login session, and persisted onto the stored tokens so refreshes keep the scope.getSessionInfois now typed with aSessionInfointerface exposingcompany.id.Confirm before merge: that Onshape's
sessioninfoexposes the scoped company atcompany.id(the?? "cad"default treats null as personal).Verification
npm run tsc,npm run lint, andnpm test(94 tests) all pass.wrangler deploy --dry-run/ build validated for the base, cert, and production environments.🤖 Generated with Claude Code
Generated by Claude Code