Gate signup with IDENTITY_SIGNUP_MODE (#78) - #88
Merged
Conversation
Add an identity workspace config key that gates provisioning of first-seen identities: open (default) | invite | waitlist. Login and Invite are never gated. Unrecognised values fail startup closed via ParseSignupMode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AntoineToussaint
force-pushed
the
issue-78-signup-mode
branch
from
August 4, 2026 23:24
1b37d99 to
d048dc4
Compare
…xpiry shadowing The invite- and waitlist-mode signup gates authorized on email equality without requiring EmailVerified, so an identity presenting an unverified but matching email could sign up — in invite mode inheriting an org and role addressed to that email. Check EmailVerified before any email-keyed lookup (no enumeration oracle, fails closed), matching the invite-token path and the Claims contract. requireApprovedWaitlist now takes *Claims so the verification fact travels with the address. Invited signup also picked the newest pending invitation and then checked expiry in Go, letting a newer expired invitation shadow an older valid one. Move the expiry predicate into SQL (expires_at > CURRENT_TIMESTAMP), the codebase's canonical usable-invitation predicate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first-party vulnerability gate (npm audit --omit=dev --audit-level=high) went red after new advisories landed: fast-uri (GHSA-7p8r-x3mc-p8w7, high) and brace-expansion (GHSA-rgw5-rvv9-x895, high). The existing overrides pinned the previously-patched versions (fast-uri 3.1.4, brace-expansion 5.0.8), which the new advisories now cover. Widen the override selectors to the whole vulnerable range and pin the patched versions (fast-uri 3.1.5, brace-expansion 5.0.9). Both are within their consumers' semver ranges, so no direct dependency changes. Lockfile regenerated and base manifest updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ync-drift The nextjs:0.0.124 companion no longer emits src/gen/saas-starter_auth-sidecar_grpc_pb.ts — the proto carries only a file descriptor, so the current protoc-gen-es produces nothing for it. The committed file is a leftover from an older generator, and it went undetected until this PR touched frontend/package.json, which marks the frontend service affected and runs its sync-drift check. Remove the stale artifact (matching `codefly sync service` against the pinned companion) so generated sources match the companion, and update the base manifest. No source references the deleted file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n start Codefly's runtime health-checks test dependencies (minio, redis, …) at host.docker.internal. Docker Desktop provides that name on macOS/Windows, but Linux GitHub runners do not resolve it on the host — and a runner-image change dropped it, so the frontend test phase began failing on every frontend-affected PR with: minio is not ready: Get "http://host.docker.internal:60430/minio/health/live": dial tcp: lookup host.docker.internal on 127.0.0.53:53: no such host Frontend tests passed on 2026-08-03 and broke by 2026-08-07, matching a runner change rather than any repo change. Published dependency ports bind to the host loopback, so map host.docker.internal → 127.0.0.1 in the runner's /etc/hosts before the gate. Idempotent and additive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The first-party vulnerability gate went red again on advisories published since the last override refresh: - js-yaml GHSA-5p4m-2wfm-xmqj (CVE-2026-59870, high) — frontend - nanoid GHSA-2v37-7h3g-55p8 (high) — frontend and marketing js-yaml is a direct frontend dependency, so bump its declared range to ^4.3.1 (patched) rather than override it (npm rejects an override that conflicts with a direct dependency). nanoid is transitive in both apps — pin it to 3.3.17 via a range-scoped override. Both are patch bumps within their consumers' ranges. Lockfiles regenerated, base manifest updated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Closes #78.
Summary
identityworkspace config key,IDENTITY_SIGNUP_MODE = open | invite | waitlist, that gates provisioning of first-seen identities only — Login and Invite are never gated, so an invitee can always obtain an account while a stranger cannot, and existing users are never locked out.openis the default (empty value → open), so upgrading an existing deployment changes nothing. Unrecognised values fail startup closed viaauth.ParseSignupMode, consistent withOAuthRequestPolicy/CORS; the parser also returns the most-restrictive mode on error so an ignored error still rejects strangers.invitewires the existing invitations subsystem into access control: signup requires a pending, unexpired invitation for the verified email, and the invitee is provisioned into the inviting org.waitlistwires the existingWAITLIST_STATE_*machine in for the first time: signup requires anapprovedorinvitedentry.Implementation notes
SetSignupModemirrors the existingSetBootstrapAdminEmailcomposition setter;work.goreadsIDENTITY_SIGNUP_MODEthrough the Codeflyidentityconfiguration and refuses to start on an unrecognised value.acceptInvitationhelper used by both the token-based invite flow and the invite-mode signup flow.auth.invitation.acceptedbusiness event that the explicit token flow does; the membership and acceptance are still persisted transactionally in the resolver.module/docs/IDENTITY_ACCESS_PLAN.mddesign doc does not exist in the repo; this change follows the issue's stated scope and the#77intent split directly.Test plan
pure:TestParseSignupMode/TestParseSignupMode_UnrecognisedFailsClosedcover the parser, the empty→open default, and fail-closed on garbage.pipeline(real Postgres), one case per mode:open: unknown email signs up successfullyinvite: unknown email with no invitation is rejected and provisions nothinginvite: invited email signs up successfully and lands in the inviting orginvite: an existing user can still log in (login is not gated)waitlist: pending entry rejected; approved entry acceptedpkg/auth/pgsuite green (confirms theresolveInviterefactor did not regress the token flow).node module/tools/base-integrity.mjs checkpasses;base-manifest.jsonregenerated for the edited canonical files.🤖 Generated with Claude Code