From 54f828c50774df391b0f5deac128907b8d90074b Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Thu, 8 Oct 2026 23:38:03 +0100 Subject: [PATCH 1/3] docs: describe approval-gated OAuth account creation (#531) --- apps/backend/app/extraction/support_matrix.py | 2 +- docs/CAPABILITIES.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/app/extraction/support_matrix.py b/apps/backend/app/extraction/support_matrix.py index a5172f3f..f2891235 100644 --- a/apps/backend/app/extraction/support_matrix.py +++ b/apps/backend/app/extraction/support_matrix.py @@ -852,7 +852,7 @@ def _code_list(items: tuple[str, ...]) -> str: "authentication-isolation", "Authentication and owner isolation", PublicStatus.IMPLEMENTED, - "Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and never create an account. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404.", + "Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404.", ("product.authentication-isolation",), ), PublicCapability( diff --git a/docs/CAPABILITIES.md b/docs/CAPABILITIES.md index 3d2341b7..ada8d9c4 100644 --- a/docs/CAPABILITIES.md +++ b/docs/CAPABILITIES.md @@ -31,7 +31,7 @@ release; `dev` carries active development. | --- | --- | --- | | Archive upload and public GitHub import | **Implemented** | ZIP/TAR-family archives and shallow public GitHub HTTPS clones; size and path-safety limits apply. Private GitHub cloning and other repository hosts are not supported. | | Repository explorer | **Implemented** | Owner-scoped file tree plus bounded text/image preview, binary detection, and truncation. | -| Authentication and owner isolation | **Implemented** | Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and never create an account. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404. | +| Authentication and owner isolation | **Implemented** | Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404. | | Analysis lifecycle | **Implemented** | Database-backed, cancellable job with progress, bounded retry, lease renewal, and stale-worker recovery. | | Repository Intelligence | **Implemented with disclosed limits** | Immutable, revision-addressed `ri.v1` snapshots with normalized facts, evidence, query APIs, and a total canonical graph hash. Semantic extraction is strongest for supported Python and TypeScript/JavaScript constructs. | | Repository lineage | **Implemented with disclosed limits** | Repeated imports of the same repository and branch are grouped into a durable, owner-scoped lineage with duplicate-revision detection (RFC-0002). `GET /repositories/{id}/lineage` returns the ordered history and the repository detail page renders it. Refresh and cross-revision comparison on top of a lineage are not built. | From f8c0c4ddc78392d1ca9e380c024e87ccdbab9f9a Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Fri, 9 Oct 2026 00:20:27 +0100 Subject: [PATCH 2/3] docs(auth): clarify bootstrap account has no administrator role --- apps/backend/app/extraction/support_matrix.py | 2 +- docs/CAPABILITIES.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/backend/app/extraction/support_matrix.py b/apps/backend/app/extraction/support_matrix.py index f2891235..757fc6d6 100644 --- a/apps/backend/app/extraction/support_matrix.py +++ b/apps/backend/app/extraction/support_matrix.py @@ -852,7 +852,7 @@ def _code_list(items: tuple[str, ...]) -> str: "authentication-isolation", "Authentication and owner isolation", PublicStatus.IMPLEMENTED, - "Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404.", + "Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which is permitted without prior approval and receives no administrator role. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404.", ("product.authentication-isolation",), ), PublicCapability( diff --git a/docs/CAPABILITIES.md b/docs/CAPABILITIES.md index ada8d9c4..fc12ca2b 100644 --- a/docs/CAPABILITIES.md +++ b/docs/CAPABILITIES.md @@ -31,7 +31,7 @@ release; `dev` carries active development. | --- | --- | --- | | Archive upload and public GitHub import | **Implemented** | ZIP/TAR-family archives and shallow public GitHub HTTPS clones; size and path-safety limits apply. Private GitHub cloning and other repository hosts are not supported. | | Repository explorer | **Implemented** | Owner-scoped file tree plus bounded text/image preview, binary detection, and truncation. | -| Authentication and owner isolation | **Implemented** | Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which becomes its owner. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404. | +| Authentication and owner isolation | **Implemented** | Email/password, Argon2, short-lived access tokens, rotating refresh tokens with reuse detection. Google and GitHub OAuth sign-in and account linking are implemented but inert until provider credentials are configured, and can create an account for a verified new identity only through the same email-approval/bootstrap gate as password registration. Registration is gated by an admin-managed email allowlist in every environment except `development` (the default when running from source), where any address may register, and except the first account on an empty instance, which is permitted without prior approval and receives no administrator role. Registration does not verify email ownership, so an approved address can be claimed by whoever registers it first; there is no admin role or in-app approval. Protected resources are owner-scoped; non-owner access returns 404. | | Analysis lifecycle | **Implemented** | Database-backed, cancellable job with progress, bounded retry, lease renewal, and stale-worker recovery. | | Repository Intelligence | **Implemented with disclosed limits** | Immutable, revision-addressed `ri.v1` snapshots with normalized facts, evidence, query APIs, and a total canonical graph hash. Semantic extraction is strongest for supported Python and TypeScript/JavaScript constructs. | | Repository lineage | **Implemented with disclosed limits** | Repeated imports of the same repository and branch are grouped into a durable, owner-scoped lineage with duplicate-revision detection (RFC-0002). `GET /repositories/{id}/lineage` returns the ordered history and the repository detail page renders it. Refresh and cross-revision comparison on top of a lineage are not built. | From 6854c14541c389012ae56b0327f9cc4ca8da97e9 Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Fri, 9 Oct 2026 00:30:59 +0100 Subject: [PATCH 3/3] test(auth): verify OAuth capability matches account creation gate --- apps/backend/tests/test_oauth_service.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/backend/tests/test_oauth_service.py b/apps/backend/tests/test_oauth_service.py index 2fb302ee..2e8de2af 100644 --- a/apps/backend/tests/test_oauth_service.py +++ b/apps/backend/tests/test_oauth_service.py @@ -719,3 +719,24 @@ def test_lists_only_the_requested_users_identities(self, db): service = _make_service(db, {}) assert [i.provider for i in service.linked_identities(user_a.id)] == ["google"] assert [i.provider for i in service.linked_identities(user_b.id)] == ["github"] + + +@pytest.mark.parametrize("approved", [False, True]) +def test_public_oauth_capability_matches_verified_identity_creation_gate(db, approved): + import asyncio + from app.extraction.support_matrix import PUBLIC_CAPABILITIES + + capability = next(item for item in PUBLIC_CAPABILITIES if item.id == "authentication-isolation") + assert "can create an account for a verified new identity" in capability.statement + assert "same email-approval/bootstrap gate" in capability.statement + _create_user(db, "existing-contract@example.com") + email = "capability-newcomer@example.com" + if approved: + approve_email(db, email) + identity = OAuthIdentityInfo(subject="contract-identity", email=email, email_verified=True, display_name="Contract") + service = _make_service(db, {"google": FakeProviderClient(identity=identity)}) + url = service.start("google", intent="login", frontend_redirect_base="http://localhost:5173") + state = url.split("state=")[1].split("&")[0] + _, result = asyncio.run(service.complete_callback("google", state=state, code="c", provider_error=None)) + assert (result.kind == "session") is approved + assert db.query(User).filter(User.email == email).count() == int(approved)