capauth 0.3.10__tar.gz → 0.3.12__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {capauth-0.3.10 → capauth-0.3.12}/CHANGELOG.md +7 -0
- {capauth-0.3.10/src/capauth.egg-info → capauth-0.3.12}/PKG-INFO +1 -1
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/app.js +75 -4
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/index.html +12 -4
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane_authorizer.py +5 -5
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane_session.py +2 -2
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/rotation.py +15 -7
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/provider.py +58 -14
- {capauth-0.3.10 → capauth-0.3.12/src/capauth.egg-info}/PKG-INFO +1 -1
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_authorizer.py +12 -4
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_session.py +27 -1
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_idp.py +11 -3
- capauth-0.3.12/tests/test_passkey_enrollment_ux.py +44 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_rotation.py +1 -3
- capauth-0.3.10/tests/test_passkey_enrollment_ux.py +0 -24
- {capauth-0.3.10 → capauth-0.3.12}/.cursorrules +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.dockerignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/ci.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/docs-check.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/nextcloud-appstore.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/publish.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/pytest.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/secret-scan.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.gitignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.gitleaks-baseline.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/.gitleaks.toml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/AGENTS.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/AI-ADVOCATE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/ARCHITECTURE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/CLAUDE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/CODE_OF_CONDUCT.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/CONTRIBUTING.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/Dockerfile +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/Dockerfile.authentik-capauth +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/LICENSE +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/MANIFEST.in +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/MISSION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/README.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/SECURITY.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/SKILL.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/SOP.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/capauth_migrate.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/imghdr.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/user_settings.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/bin/cli.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/.gitignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/README.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/RELEASING.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/background.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/autofill.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/cross_origin_signing.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/detector.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/provider.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/provider_bridge.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon.svg +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon128.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon16.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon48.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/bunker-e2e.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/keyqr.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/keyvault.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/openpgp.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/signer-backends.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/manifest.firefox.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/manifest.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/capauth_signer.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/com.capauth.signer.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/install.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/options/options.html +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/options/options.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/package-lock.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/package.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.css +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.html +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/scripts/build.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/scripts/generate-icons.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/AuthenticatorSelectionStage.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/CapAuthStage.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/QRDisplayStage.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/SignatureVerificationStage.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/StagePipeline.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/submission/STORE_LISTING.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/submission/privacy-policy.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/fixtures/test_key.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/background.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/bunker_e2e.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/detector.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/keyqr.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/keyvault.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/openpgp.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/provider_api.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/provider_tierb.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/remote_bunker.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/signer_backends.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/browser-extension/vitest.config.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/.env.example +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/deploy.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/docker-compose.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/systemd/capauth-backup.service +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/systemd/capauth-backup.timer +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/.env.example +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/docker-compose.yml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/preflight.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/AIRGAPPED_SIGNING_WORKSTATION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/ARCHITECTURE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_CUSTOM_STAGE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_DEPLOYMENT_SKSSO.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_FORGEJO_DEPLOYMENT.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_STRATEGY_DECISION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH-WEBAUTHN-05-QUALIFICATION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH_BUNKER_REMOTE_SIGNER.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH_OIDC_IDP.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/CLAIMS.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/CRYPTO_SPEC.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/DELEGATED_CAPABILITIES.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/DOWNSTREAM_DURABLE_BACKENDS.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/ENTERPRISE_MANAGED_KEYS.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/HUMAN_IDENTITY_SETUP_AND_ROTATION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/IDENTITY_ESTATE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/INTEGRATION_BLUEPRINT.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/M0-capauth-api-v1.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/M0-capauth-importer-inventory.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/PASSPHRASE_ROTATION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/PQC_CAPAUTH_REVISIT.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/PQC_ROOT_MIGRATION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/PROTOCOL.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/ROOT_ROTATION_CEREMONY.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/SKDASHBOARD_CONTROL_PLANE_POLICY.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/TRUST_GRAPH_COORD_PROJECTION.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/ZERO_KNOWLEDGE_PROFILE.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/audits/BLAST-RADIUS-CAPAUTH-01.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/audits/BLAST-RADIUS-CAPAUTH-01.sha256 +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/authentik-capauth.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/deploy-plan/capauth-bulletproof-deploy.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-MUTATION-ENVELOPE-2026-08-25.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-ROSTER-2026-08-25.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/index.d.ts +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/index.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/package.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/src/index.ts +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/src/openclaw.plugin.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/package.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/icons/icon-192.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/icons/icon-512.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/bunker-e2e.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/bunker-signer.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/canonical.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/keyqr.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/keyvault.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/manifest.webmanifest +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/sw.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/vendor/openpgp.min.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/phone-signer/vendor/qrcode-generator.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/pyproject.toml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/build_authentik_capauth_image.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/capauth-backup.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/capauth-restore.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/gitleaks-scope.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/migrate_canonical_subjects.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/pqc_ceremony_dryrun.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/scripts/provision_agent_profiles.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/setup.cfg +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/skill.yaml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/agent_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/apps.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/api.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/blueprints/capauth-pgp-login.yaml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/claims_mapper.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/nonce_store.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/stage.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/urls.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/verifier.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authz.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/cli.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/base.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/gnupg_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/pgpy_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/sequoia_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/skpgp_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/custody.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/delegated.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/did.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/base.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/file_discovery.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/mdns.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/syncthing.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/estate.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/exceptions.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/identity_class.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integration.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/cli.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/config.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/forgejo_api.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/oidc_provider.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/.gitignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/.nextcloudignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/CSR-PR.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/INFO-XML-CHANGES.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/PUBLISHING.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/README.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/appinfo/info.xml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/appinfo/routes.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/build-release.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/.gitignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/capauth.crt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/capauth.csr +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/composer.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/css/login.css +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/js/login.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/krankerl.toml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/AlternativeLogin/CapAuthLogin.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/AppInfo/Application.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/BackgroundJob/PruneExpiredNonces.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Controller/LoginController.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Controller/TokenController.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Db/KeyRegistry.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Exception/CapAuthUnauthorizedException.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Middleware/PgpVerificationMiddleware.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Migration/Version000300Date20260301000000.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Provider/CapAuthProvider.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/ChallengeService.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/GroupSyncService.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/UserProvisioningService.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/VerifierService.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/User/Backend.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/phpunit.xml +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/screenshots/README.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/screenshots/login.png +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/templates/login.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/bootstrap.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/integration/FullAuthFlowTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Controller/LoginControllerTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Db/KeyRegistryTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Provider/CapAuthProviderTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Service/ChallengeServiceTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Service/VerifierServiceTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/User/BackendTest.php +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/key_transparency.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/login.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/manifest.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/mesh.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/0001_initial.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/0002_widen_fingerprint_to_64.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/models.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/canonicalize.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/kernel.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/operator_session.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/proof.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/records.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/store.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/window.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pma.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_confidentiality.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_root_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_tokens.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/profile.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/provisioning.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/registry.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/seal.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/app.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/bunker.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/bunker_e2e.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/keystore.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/clients.example.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/clients.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/passkey.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/signing_key.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/lib/bunker-e2e.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/lib/keyvault.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/vendor/openpgp.min.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/store.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/push.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/server.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/subject.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/sync.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/testing.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/tokens.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/calibration.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/graph.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/SOURCES.txt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/dependency_links.txt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/entry_points.txt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/requires.txt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/top_level.txt +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/.gitignore +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/package-lock.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/package.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/CapAuthStage.test.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/CapAuthStage.vue +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/index.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/vite.config.js +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/__init__.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/conftest.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/fixtures/bunker_e2e_v1_vector.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/fixtures/canonical_nonce_v2_vector.json +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_agent_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_agentrun_capabilities.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_mint_helper.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_tokens.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_ttl_seconds.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz_legacy_grace.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz_signature_gate.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_backup_restore.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_bunker.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_bunker_e2e.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_canonicalize.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_capauth_service_deploy.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_change_capabilities.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_claims_mapper.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_capabilities.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_crypto.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_custody.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_delegated_capabilities.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_device_key_proof.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_did_capability_relationships.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_enrollment_proof_helper.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_estate.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_fingerprint_v6.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_forgejo_preflight.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_home_resolution.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity_class.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity_ttl.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_installed_bunker_assets.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_integration.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_key_revocation_expiry.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_key_transparency.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_login.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_manifest.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_mesh.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_models_pqc_q0.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_nextcloud_app.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_no_subapp_dependency.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_nonce_store.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_durable_store.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_hardening.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_idp_e2e.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_refresh_family.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_operator_session.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pairing.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pairing_base_dir_env.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_passkey.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pma.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_confidentiality.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_identity.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_root_integration.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_t3_gate.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_tokens.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_profile.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_provisioning.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_push.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_py313_compat.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_qr_login.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_real_pgp_e2e.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_register_cli.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_registry.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_seal.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_sequoia_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_service.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_service_authz_decide.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_service_v6_fingerprint.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_skpgp_backend.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_subject.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_sync.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_syncthing_discovery.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_testing_helper.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_token_gc.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_token_store_flag.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_tokens.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_calibration.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_graph.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_graph_coord_audit.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_verifier.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_version_honesty.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tests/test_zk_profile.py +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/tools/build-sq.sh +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/urllib.error +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/web/README.md +0 -0
- {capauth-0.3.10 → capauth-0.3.12}/web/stages/capauth/CapAuthStage.ts +0 -0
|
@@ -6,6 +6,13 @@ All notable changes to `capauth` are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
- Raise operator proof absolute and idle ceilings to 24 hours while keeping
|
|
10
|
+
access capabilities bounded to five minutes (card `4af0ae7f`).
|
|
11
|
+
|
|
12
|
+
- Normalize control-plane authorization decisions to the actual reconstructed
|
|
13
|
+
token lifetime before exact owner-policy evaluation, while retaining the raw
|
|
14
|
+
signed decision for replay and currentness receipts (card `9b0c6dce`).
|
|
15
|
+
|
|
9
16
|
- Fail closed when a subject has no capability-ceiling assignment. Add explicit
|
|
10
17
|
`service` and `connector` ceiling roles over the existing five-class fqid
|
|
11
18
|
grammar, with external dispatch structurally forbidden to generic services.
|
|
@@ -19,6 +19,28 @@ import { chunkPayload, parseKeyFrame, KeyFrameCollector } from "./lib/keyqr.js";
|
|
|
19
19
|
const openpgp = globalThis.openpgp;
|
|
20
20
|
const STORE_KEY = "capauth_bunker_envelope";
|
|
21
21
|
const FP_KEY = "capauth_bunker_fp";
|
|
22
|
+
const RETURN_KEY = "capauth_bunker_return";
|
|
23
|
+
|
|
24
|
+
function safeSetupReturn() {
|
|
25
|
+
const raw = sessionStorage.getItem(RETURN_KEY) || "";
|
|
26
|
+
sessionStorage.removeItem(RETURN_KEY);
|
|
27
|
+
if (!raw || raw.length > 2048 || /[\u0000-\u001F\u007F]/.test(raw)) return "";
|
|
28
|
+
try {
|
|
29
|
+
const target = new URL(raw, location.origin);
|
|
30
|
+
if (
|
|
31
|
+
target.origin !== location.origin ||
|
|
32
|
+
target.username ||
|
|
33
|
+
target.password ||
|
|
34
|
+
target.hash ||
|
|
35
|
+
target.pathname !== "/oidc/authorize"
|
|
36
|
+
) {
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
return target.pathname + target.search;
|
|
40
|
+
} catch {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
22
44
|
|
|
23
45
|
const session = {
|
|
24
46
|
armoredKey: null, // decrypted (from vault) armored key, in-memory only
|
|
@@ -117,6 +139,8 @@ async function storeKey() {
|
|
|
117
139
|
$("vault-pass-confirm").value = "";
|
|
118
140
|
setStatus($("key-status"), "Identity saved in this browser. Continue to passkey setup.", "ok");
|
|
119
141
|
renderKeyState();
|
|
142
|
+
const returnTo = safeSetupReturn();
|
|
143
|
+
if (returnTo) location.assign(returnTo);
|
|
120
144
|
} catch (err) {
|
|
121
145
|
setStatus($("key-status"), "Could not read/store key: " + err.message, "err");
|
|
122
146
|
}
|
|
@@ -145,14 +169,44 @@ async function unlockKey() {
|
|
|
145
169
|
}
|
|
146
170
|
}
|
|
147
171
|
|
|
148
|
-
function
|
|
172
|
+
function clearLocalIdentity(message) {
|
|
149
173
|
localStorage.removeItem(STORE_KEY);
|
|
150
174
|
localStorage.removeItem(FP_KEY);
|
|
151
175
|
session.armoredKey = null;
|
|
152
176
|
session.signingKey = null;
|
|
153
177
|
session.fingerprint = "";
|
|
154
178
|
renderKeyState();
|
|
155
|
-
setStatus($("key-status"),
|
|
179
|
+
setStatus($("key-status"), message, "");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function forgetKey() {
|
|
183
|
+
if (!confirm("Remove this encrypted identity from this browser? Make sure you have another protected copy first.")) return;
|
|
184
|
+
clearLocalIdentity("Identity removed from this browser.");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function replaceKey() {
|
|
188
|
+
if (!confirm("Replace this browser's encrypted identity? Make sure you have another protected copy first.")) return;
|
|
189
|
+
clearLocalIdentity("Choose the identity file you want to use on this browser.");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function downloadEncryptedBackup() {
|
|
193
|
+
const envelope = localStorage.getItem(STORE_KEY);
|
|
194
|
+
const fingerprint = localStorage.getItem(FP_KEY);
|
|
195
|
+
if (!envelope || !fingerprint) return setStatus($("key-status"), "No local identity is available to back up.", "err");
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(envelope);
|
|
198
|
+
if (!isEncryptedEnvelope(parsed)) throw new Error("invalid encrypted vault");
|
|
199
|
+
const payload = JSON.stringify({ version: 1, fingerprint, encryptedEnvelope: parsed }, null, 2);
|
|
200
|
+
const url = URL.createObjectURL(new Blob([payload], { type: "application/json" }));
|
|
201
|
+
const link = document.createElement("a");
|
|
202
|
+
link.href = url;
|
|
203
|
+
link.download = `capauth-${fingerprint.slice(-8).toLowerCase()}-encrypted-backup.json`;
|
|
204
|
+
link.click();
|
|
205
|
+
URL.revokeObjectURL(url);
|
|
206
|
+
setStatus($("key-status"), "Encrypted backup downloaded. Store it somewhere protected.", "ok");
|
|
207
|
+
} catch (err) {
|
|
208
|
+
setStatus($("key-status"), "Could not create the encrypted backup: " + err.message, "err");
|
|
209
|
+
}
|
|
156
210
|
}
|
|
157
211
|
|
|
158
212
|
function renderKeyState() {
|
|
@@ -170,7 +224,7 @@ function renderKeyState() {
|
|
|
170
224
|
} else {
|
|
171
225
|
$("key-import").classList.remove("hidden");
|
|
172
226
|
$("key-unlock").classList.add("hidden");
|
|
173
|
-
setStatus($("key-status"), "Setup needed: choose your existing identity file.");
|
|
227
|
+
setStatus($("key-status"), "Setup needed: choose your existing identity or encrypted backup file.");
|
|
174
228
|
}
|
|
175
229
|
}
|
|
176
230
|
|
|
@@ -179,8 +233,23 @@ async function loadKeyFile() {
|
|
|
179
233
|
if (!file) return;
|
|
180
234
|
try {
|
|
181
235
|
const armored = await file.text();
|
|
236
|
+
if (file.name.toLowerCase().endsWith(".json")) {
|
|
237
|
+
const backup = JSON.parse(armored);
|
|
238
|
+
const fp = String(backup.fingerprint || "").trim().toUpperCase();
|
|
239
|
+
if (backup.version !== 1 || ![40, 64].includes(fp.length) || !/^[0-9A-F]+$/.test(fp)) {
|
|
240
|
+
throw new Error("This is not a valid CapAuth encrypted backup.");
|
|
241
|
+
}
|
|
242
|
+
if (!isEncryptedEnvelope(backup.encryptedEnvelope)) throw new Error("The encrypted vault is invalid.");
|
|
243
|
+
localStorage.setItem(STORE_KEY, JSON.stringify(backup.encryptedEnvelope));
|
|
244
|
+
localStorage.setItem(FP_KEY, fp);
|
|
245
|
+
setStatus($("key-status"), "Encrypted identity restored. Use its browser vault passphrase to sign in.", "ok");
|
|
246
|
+
renderKeyState();
|
|
247
|
+
const returnTo = safeSetupReturn();
|
|
248
|
+
if (returnTo) location.assign(returnTo);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
182
251
|
if (!armored.includes("BEGIN PGP PRIVATE KEY BLOCK")) {
|
|
183
|
-
throw new Error("
|
|
252
|
+
throw new Error("Choose an armored PGP private key or CapAuth encrypted backup.");
|
|
184
253
|
}
|
|
185
254
|
$("priv-key").value = armored;
|
|
186
255
|
setStatus($("key-status"), "Identity file loaded. Complete the two passphrase fields below.", "ok");
|
|
@@ -498,6 +567,8 @@ $("btn-store").onclick = storeKey;
|
|
|
498
567
|
$("key-file").onchange = loadKeyFile;
|
|
499
568
|
$("btn-unlock").onclick = unlockKey;
|
|
500
569
|
$("btn-forget").onclick = forgetKey;
|
|
570
|
+
$("btn-replace").onclick = replaceKey;
|
|
571
|
+
$("btn-backup").onclick = downloadEncryptedBackup;
|
|
501
572
|
$("btn-pair").onclick = connect;
|
|
502
573
|
$("btn-push").onclick = enablePush;
|
|
503
574
|
$("btn-scan").onclick = startScan;
|
|
@@ -94,6 +94,8 @@
|
|
|
94
94
|
max-height:140px; overflow:auto; margin-top:6px;
|
|
95
95
|
}
|
|
96
96
|
.warn { color:#f59e0b; font-size:11px; margin-top:8px; }
|
|
97
|
+
.management { border-top:1px solid rgba(124,58,237,.25); margin-top:16px; padding-top:12px; }
|
|
98
|
+
.management summary { cursor:pointer; color:var(--accent2); font-size:12px; font-weight:600; }
|
|
97
99
|
</style>
|
|
98
100
|
</head>
|
|
99
101
|
<body>
|
|
@@ -107,9 +109,9 @@
|
|
|
107
109
|
<h2>1. Load your existing identity</h2>
|
|
108
110
|
<div id="key-status" class="status">Checking this browser...</div>
|
|
109
111
|
<div id="key-import">
|
|
110
|
-
<label for="key-file">Choose your
|
|
111
|
-
<input id="key-file" type="file" accept=".asc,.pgp,.gpg,text/plain" />
|
|
112
|
-
<div class="hint">
|
|
112
|
+
<label for="key-file">Choose your identity file</label>
|
|
113
|
+
<input id="key-file" type="file" accept=".asc,.pgp,.gpg,.json,text/plain,application/json" />
|
|
114
|
+
<div class="hint">Use an armored PGP private key such as <code>jarvis-private.asc</code>, or a CapAuth encrypted backup. The file is read only in this browser and is never uploaded.</div>
|
|
113
115
|
<details class="hint">
|
|
114
116
|
<summary>Advanced: paste an armored key instead</summary>
|
|
115
117
|
<label for="priv-key">Armored PGP private key</label>
|
|
@@ -150,11 +152,17 @@
|
|
|
150
152
|
<input id="unlock-pass" type="password" autocomplete="current-password" />
|
|
151
153
|
<div class="row">
|
|
152
154
|
<button id="btn-unlock">Unlock</button>
|
|
153
|
-
<button id="btn-forget" class="secondary">Forget key</button>
|
|
154
155
|
</div>
|
|
155
156
|
<button id="btn-push" class="secondary">🔔 Enable background approvals</button>
|
|
156
157
|
<div id="push-status" class="hint">Get a notification to approve sign-ins even when this page is closed.</div>
|
|
157
158
|
</details>
|
|
159
|
+
<details class="management" id="device-management">
|
|
160
|
+
<summary>Manage identity on this device</summary>
|
|
161
|
+
<p class="hint">Download an encrypted browser-vault backup, replace this browser's identity, or remove its local copy. CapAuth never exports an unencrypted private key here.</p>
|
|
162
|
+
<button id="btn-backup" class="secondary">Download encrypted backup</button>
|
|
163
|
+
<button id="btn-replace" class="secondary">Replace identity</button>
|
|
164
|
+
<button id="btn-forget" class="danger">Remove from this browser</button>
|
|
165
|
+
</details>
|
|
158
166
|
</div>
|
|
159
167
|
</section>
|
|
160
168
|
|
|
@@ -641,12 +641,12 @@ class ControlPlaneDecisionAuthorizer:
|
|
|
641
641
|
if not issued_at <= now < expires_at:
|
|
642
642
|
return closed(_result(DecisionState.DENY, DecisionCode.EXPIRED))
|
|
643
643
|
|
|
644
|
-
|
|
644
|
+
effective_capauth = capauth.model_copy(update={"scope": binding.capability_scope()})
|
|
645
|
+
first = self._owner_decision(binding, effective_capauth)
|
|
645
646
|
if first is None:
|
|
646
647
|
return closed(
|
|
647
648
|
_result(DecisionState.UNAVAILABLE, DecisionCode.OWNER_POLICY_UNAVAILABLE)
|
|
648
649
|
)
|
|
649
|
-
effective_capauth = capauth.model_copy(update={"scope": binding.capability_scope()})
|
|
650
650
|
first_joined = join_policy_decisions(binding, effective_capauth, first)
|
|
651
651
|
if not first_joined.allow:
|
|
652
652
|
return closed(_result(first_joined.state, first_joined.code))
|
|
@@ -670,14 +670,14 @@ class ControlPlaneDecisionAuthorizer:
|
|
|
670
670
|
return closed(_capauth_denial(exc.decision.reason))
|
|
671
671
|
except Exception:
|
|
672
672
|
return closed(_result(DecisionState.UNAVAILABLE, DecisionCode.CAPAUTH_UNAVAILABLE))
|
|
673
|
-
|
|
673
|
+
effective_capauth = capauth.model_copy(update={"scope": binding.capability_scope()})
|
|
674
|
+
second = self._owner_decision(binding, effective_capauth)
|
|
674
675
|
if second is None:
|
|
675
676
|
return closed(
|
|
676
677
|
_result(DecisionState.UNAVAILABLE, DecisionCode.OWNER_POLICY_UNAVAILABLE)
|
|
677
678
|
)
|
|
678
679
|
if first != second:
|
|
679
680
|
return closed(_result(DecisionState.DENY, DecisionCode.BINDING_MISMATCH))
|
|
680
|
-
effective_capauth = capauth.model_copy(update={"scope": binding.capability_scope()})
|
|
681
681
|
joined = join_policy_decisions(binding, effective_capauth, second)
|
|
682
682
|
if not joined.allow:
|
|
683
683
|
return closed(_result(joined.state, joined.code))
|
|
@@ -689,7 +689,7 @@ class ControlPlaneDecisionAuthorizer:
|
|
|
689
689
|
context = SanitizedControlPlaneDecisionV1(
|
|
690
690
|
binding=binding,
|
|
691
691
|
boundary=invocation.boundary,
|
|
692
|
-
capauth_decision=
|
|
692
|
+
capauth_decision=effective_capauth,
|
|
693
693
|
joined_decision=joined,
|
|
694
694
|
authenticated_identity_ref=_identity_ref(binding, capauth),
|
|
695
695
|
issued_at=issued_at,
|
|
@@ -38,8 +38,8 @@ from .control_plane_authorizer import (
|
|
|
38
38
|
from .delegated import CapabilityScope, PresentedCapability, Principal, parse_presented_token
|
|
39
39
|
|
|
40
40
|
UTC = timezone.utc
|
|
41
|
-
MAX_SESSION_TTL = timedelta(hours=
|
|
42
|
-
MAX_SESSION_IDLE = timedelta(
|
|
41
|
+
MAX_SESSION_TTL = timedelta(hours=24)
|
|
42
|
+
MAX_SESSION_IDLE = timedelta(hours=24)
|
|
43
43
|
_OPS = {entry.capability: entry.operation.value for entry in CAPABILITY_MATRIX if entry.capability}
|
|
44
44
|
_REQUEST_FACTORY = object()
|
|
45
45
|
_VERIFIER_FACTORY = object()
|
|
@@ -70,9 +70,7 @@ class RotationPlan:
|
|
|
70
70
|
if not isinstance(item, dict):
|
|
71
71
|
raise RotationError(f"custody_bundles[{index}] must be an object")
|
|
72
72
|
bundle = local(item.get("path"), f"custody_bundles[{index}].path")
|
|
73
|
-
checksum = local(
|
|
74
|
-
item.get("checksum_path"), f"custody_bundles[{index}].checksum_path"
|
|
75
|
-
)
|
|
73
|
+
checksum = local(item.get("checksum_path"), f"custody_bundles[{index}].checksum_path")
|
|
76
74
|
bundles.append((bundle, checksum))
|
|
77
75
|
|
|
78
76
|
operator = str(raw.get("operator") or "").strip()
|
|
@@ -269,9 +267,13 @@ def rotate_passphrase(
|
|
|
269
267
|
else:
|
|
270
268
|
absent_before.add(checksum)
|
|
271
269
|
try:
|
|
272
|
-
bundle_plaintexts.append(
|
|
270
|
+
bundle_plaintexts.append(
|
|
271
|
+
pgpy.PGPMessage.from_blob(data).decrypt(old_passphrase).message
|
|
272
|
+
)
|
|
273
273
|
except Exception as exc:
|
|
274
|
-
raise RotationError(
|
|
274
|
+
raise RotationError(
|
|
275
|
+
f"old passphrase could not decrypt custody bundle: {bundle}"
|
|
276
|
+
) from exc
|
|
275
277
|
|
|
276
278
|
rotated_key = _rewrap_key(key, old_passphrase, new_passphrase)
|
|
277
279
|
fingerprint = _validate_new_key(rotated_key, public_bytes, new_passphrase)
|
|
@@ -312,7 +314,10 @@ def rotate_passphrase(
|
|
|
312
314
|
for path, data in replacements.items():
|
|
313
315
|
_atomic_write(path, data)
|
|
314
316
|
changed.append(path)
|
|
315
|
-
if
|
|
317
|
+
if (
|
|
318
|
+
_validate_new_key(plan.private_key.read_bytes(), public_bytes, new_passphrase)
|
|
319
|
+
!= fingerprint
|
|
320
|
+
):
|
|
316
321
|
raise RotationError("installed key fingerprint changed unexpectedly")
|
|
317
322
|
for bundle, checksum in plan.custody_bundles:
|
|
318
323
|
installed = bundle.read_bytes()
|
|
@@ -345,7 +350,10 @@ def rollback_rotation(journal_path: Path, *, approved: bool = False) -> tuple[st
|
|
|
345
350
|
journal = json.loads(journal_path.read_text(encoding="utf-8"))
|
|
346
351
|
except (OSError, json.JSONDecodeError) as exc:
|
|
347
352
|
raise RotationError(f"invalid rollback journal: {exc}") from exc
|
|
348
|
-
if
|
|
353
|
+
if (
|
|
354
|
+
not isinstance(journal, dict)
|
|
355
|
+
or journal.get("schema") != "capauth-passphrase-rotation-rollback-v1"
|
|
356
|
+
):
|
|
349
357
|
raise RotationError("unsupported rollback journal")
|
|
350
358
|
items = journal.get("items")
|
|
351
359
|
if not isinstance(items, list) or not items:
|
|
@@ -177,14 +177,20 @@ _LOGIN_PAGE = """<!DOCTYPE html>
|
|
|
177
177
|
color:#e2e8f0;padding:.6rem .8rem;font-size:.88rem;margin-bottom:1rem;
|
|
178
178
|
font-family:monospace}}
|
|
179
179
|
textarea{{min-height:130px;resize:vertical}}
|
|
180
|
-
button{{width:100%;background:#7C3AED;color:#fff;border:none;border-radius:
|
|
181
|
-
padding:.
|
|
180
|
+
button{{width:100%;background:#7C3AED;color:#fff;border:none;border-radius:10px;
|
|
181
|
+
padding:.85rem;font-size:1rem;cursor:pointer;font-weight:650}}
|
|
182
182
|
button:hover{{background:#6d28d9}}
|
|
183
183
|
.nonce-box{{background:#0f0f1a;border:1px solid #334155;border-radius:8px;padding:.7rem;
|
|
184
184
|
margin-bottom:.5rem;font-family:monospace;font-size:.78rem;color:#00e5ff;
|
|
185
185
|
white-space:pre-wrap;word-break:break-all}}
|
|
186
186
|
.copy{{width:auto;padding:.45rem .7rem;font-size:.78rem;background:#334155;margin-bottom:1rem}}
|
|
187
187
|
.err{{color:#f87171;font-size:.85rem;margin-top:.6rem;display:none}}
|
|
188
|
+
.browser-auth{{background:#111827;border:1px solid #334155;border-radius:12px;padding:1rem;margin:1rem 0}}
|
|
189
|
+
.browser-auth button+button{{margin-top:.6rem}}
|
|
190
|
+
.method-label{{color:#c4b5fd;font-size:.72rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;margin-bottom:.65rem}}
|
|
191
|
+
.secondary-action{{background:#334155}}
|
|
192
|
+
details{{margin-top:1rem}}
|
|
193
|
+
summary{{cursor:pointer;color:#a78bfa;font-weight:600}}
|
|
188
194
|
code{{color:#a78bfa}}
|
|
189
195
|
</style>
|
|
190
196
|
</head>
|
|
@@ -194,15 +200,18 @@ _LOGIN_PAGE = """<!DOCTYPE html>
|
|
|
194
200
|
<p class="sub">Authenticate to <strong>{client_name}</strong> with your PGP key.
|
|
195
201
|
No password. Use a passkey if you already added one.</p>
|
|
196
202
|
|
|
197
|
-
<
|
|
198
|
-
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
<
|
|
203
|
+
<div class="browser-auth">
|
|
204
|
+
<div class="method-label" id="primary-label">Best option for this browser</div>
|
|
205
|
+
<button id="pk-btn" onclick="passkeyLogin()" style="background:#0e7490">Continue with a passkey</button>
|
|
206
|
+
<button id="ld-btn" onclick="capauthLocalSign()" style="background:#10b981">Continue with local identity</button>
|
|
207
|
+
<button id="setup-btn" onclick="setupBrowser()" style="background:#7C3AED">Set up this browser</button>
|
|
208
|
+
<p class="sub" id="browser-status" style="font-size:.74rem;margin:.6rem 0 0">Checking this browser for your encrypted identity.</p>
|
|
209
|
+
<a href="{base_url}/oidc/passkey/enroll" id="add-passkey" style="display:block;color:#a78bfa;font-size:.76rem;margin-top:.7rem">Add a passkey to this browser</a>
|
|
204
210
|
</div>
|
|
205
211
|
|
|
212
|
+
<details id="manual-pgp">
|
|
213
|
+
<summary>Other sign-in and recovery options</summary>
|
|
214
|
+
<p class="sub" style="font-size:.74rem;margin:.6rem 0">The text below is the message to sign. It is not a signature. Paste only a fresh ASCII-armored PGP signature into the signature box.</p>
|
|
206
215
|
<div class="step">1 — Your PGP fingerprint</div>
|
|
207
216
|
<label for="fp">Fingerprint (40- or 64-hex chars)</label>
|
|
208
217
|
<input id="fp" type="text" maxlength="64" placeholder="ABCDEF0123..." autocomplete="off"/>
|
|
@@ -218,12 +227,7 @@ _LOGIN_PAGE = """<!DOCTYPE html>
|
|
|
218
227
|
<p class="sub">This fingerprint must already have an approved CapAuth enrollment.</p>
|
|
219
228
|
|
|
220
229
|
<button onclick="submitSig()">Verify & Continue</button>
|
|
221
|
-
<div class="err" id="err"></div>
|
|
222
230
|
|
|
223
|
-
<div style="display:flex;align-items:center;gap:.6rem;margin:1.1rem 0 .2rem;color:#475569;font-size:.74rem">
|
|
224
|
-
<span style="flex:1;height:1px;background:#334155"></span>OR<span style="flex:1;height:1px;background:#334155"></span>
|
|
225
|
-
</div>
|
|
226
|
-
<button id="ld-btn" onclick="capauthLocalSign()" style="background:#10b981;margin-top:.5rem">Sign on this device (key in your bunker)</button>
|
|
227
231
|
<button id="ph-btn" onclick="capauthPhoneLogin()" style="background:#7C3AED;margin-top:.5rem">Sign from another device (QR)</button>
|
|
228
232
|
<div id="pq" style="display:none;margin-top:1rem;text-align:center">
|
|
229
233
|
<img id="pq-img" alt="pairing QR" style="width:200px;height:200px;background:#fff;border-radius:10px"/>
|
|
@@ -235,6 +239,8 @@ _LOGIN_PAGE = """<!DOCTYPE html>
|
|
|
235
239
|
Manual GPG: copy the complete message, then use <code>gpg --armor --sign</code> for an inline signed message
|
|
236
240
|
or <code>gpg --armor --detach-sign</code> for a detached signature. The browser extension can sign automatically.
|
|
237
241
|
</p>
|
|
242
|
+
</details>
|
|
243
|
+
<div class="err" id="err"></div>
|
|
238
244
|
</div>
|
|
239
245
|
|
|
240
246
|
<script src="{base_url}/oidc/passkey.js?v=15"></script>
|
|
@@ -245,6 +251,34 @@ const BASE = "{base_url}";
|
|
|
245
251
|
const REQUEST_ID = "{request_id}";
|
|
246
252
|
window._capauthBase = BASE; window._capauthReqId = REQUEST_ID; window._capauthCh = null;
|
|
247
253
|
|
|
254
|
+
function setupBrowser(){{
|
|
255
|
+
const target=window.location.pathname+window.location.search;
|
|
256
|
+
if(target.length>2048||/[\\u0000-\\u001F\\u007F]/.test(target)||window.location.pathname!=="/oidc/authorize"){{
|
|
257
|
+
return setErr("This sign-in address cannot be carried into browser setup. Start again from the application.");
|
|
258
|
+
}}
|
|
259
|
+
sessionStorage.setItem("capauth_bunker_return",target);
|
|
260
|
+
sessionStorage.setItem("capauth_bunker_auto_sign","1");
|
|
261
|
+
window.location.assign(BASE+"/bunker/?mode=setup");
|
|
262
|
+
}}
|
|
263
|
+
|
|
264
|
+
function refreshBrowserAuth(){{
|
|
265
|
+
const fp=(localStorage.getItem("capauth_bunker_fp")||"").trim().toUpperCase().replace(/\\s/g,"");
|
|
266
|
+
const ready=[40,64].includes(fp.length)&&/^[0-9A-F]+$/.test(fp)&&Boolean(localStorage.getItem("capauth_bunker_envelope"));
|
|
267
|
+
const status=document.getElementById("browser-status");
|
|
268
|
+
const setup=document.getElementById("setup-btn");
|
|
269
|
+
const local=document.getElementById("ld-btn");
|
|
270
|
+
if(ready){{
|
|
271
|
+
status.textContent="Local identity "+fp.slice(0,8)+"..."+fp.slice(-8)+" is ready. Its encrypted key stays in this browser.";
|
|
272
|
+
setup.hidden=true;
|
|
273
|
+
local.hidden=false;
|
|
274
|
+
}}else{{
|
|
275
|
+
status.textContent="No local identity was found. Load your existing identity once, then CapAuth returns here to sign a fresh challenge.";
|
|
276
|
+
setup.hidden=false;
|
|
277
|
+
local.hidden=true;
|
|
278
|
+
}}
|
|
279
|
+
}}
|
|
280
|
+
refreshBrowserAuth();
|
|
281
|
+
|
|
248
282
|
async function passkeyLogin(){{
|
|
249
283
|
document.getElementById("err").style.display="none";
|
|
250
284
|
if(!window.capauthWebAuthn || !window.capauthWebAuthn.available()){{
|
|
@@ -569,6 +603,16 @@ import { decryptPrivateKey, isEncryptedEnvelope } from "/bunker/lib/keyvault.js"
|
|
|
569
603
|
window.location.href = d.redirect_to;
|
|
570
604
|
} catch (e) { err("Phone sign-in: " + e.message); status(""); }
|
|
571
605
|
};
|
|
606
|
+
|
|
607
|
+
// The one-time Bunker setup can return to a fresh OIDC authorization page
|
|
608
|
+
// and immediately continue the local signing flow. The marker is removed
|
|
609
|
+
// before prompting so cancel or failure never creates a reload loop.
|
|
610
|
+
if (window._capauthReqId && sessionStorage.getItem("capauth_bunker_auto_sign") === "1") {
|
|
611
|
+
sessionStorage.removeItem("capauth_bunker_auto_sign");
|
|
612
|
+
const fp = (localStorage.getItem("capauth_bunker_fp") || "").toUpperCase();
|
|
613
|
+
const raw = localStorage.getItem("capauth_bunker_envelope");
|
|
614
|
+
if ([40, 64].includes(fp.length) && raw) setTimeout(window.capauthLocalSign, 0);
|
|
615
|
+
}
|
|
572
616
|
})();
|
|
573
617
|
"""
|
|
574
618
|
|
|
@@ -672,13 +672,21 @@ def test_signed_expiry_constraint_is_an_upper_ceiling() -> None:
|
|
|
672
672
|
rig = Rig()
|
|
673
673
|
rig.binding = rig.binding.model_copy(update={"expires_at": NOW + timedelta(seconds=61)})
|
|
674
674
|
|
|
675
|
-
|
|
675
|
+
class ExactScopeOwner:
|
|
676
|
+
def decide(self, binding, capauth_decision):
|
|
677
|
+
return (
|
|
678
|
+
_owner(binding) if capauth_decision.scope == binding.capability_scope() else None
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
result = _allow(
|
|
682
|
+
rig,
|
|
683
|
+
bearer=rig.bearer(ttl_seconds=60),
|
|
684
|
+
owner=ExactScopeOwner(),
|
|
685
|
+
)
|
|
676
686
|
|
|
677
687
|
assert result.context.expires_at == NOW + timedelta(seconds=60)
|
|
678
688
|
assert result.context.binding.expires_at == result.context.expires_at
|
|
679
|
-
assert result.context.capauth_decision.scope
|
|
680
|
-
rig.binding.capability_scope().constraints
|
|
681
|
-
)
|
|
689
|
+
assert result.context.capauth_decision.scope == result.context.binding.capability_scope()
|
|
682
690
|
assert result.context.joined_decision.scope == result.context.binding.capability_scope()
|
|
683
691
|
|
|
684
692
|
|
|
@@ -102,7 +102,9 @@ class InjectedIssuer:
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
class OwnerPolicy:
|
|
105
|
-
def decide(self, binding,
|
|
105
|
+
def decide(self, binding, capauth_decision):
|
|
106
|
+
if capauth_decision.scope != binding.capability_scope():
|
|
107
|
+
return None
|
|
106
108
|
return OwnerPolicyDecision(
|
|
107
109
|
state=DecisionState.ALLOW,
|
|
108
110
|
revision=binding.owner_policy_revision,
|
|
@@ -160,6 +162,30 @@ def new_session(manager, *, device=DEVICE):
|
|
|
160
162
|
return material.take()
|
|
161
163
|
|
|
162
164
|
|
|
165
|
+
def test_operator_session_accepts_24_hour_absolute_and_idle_lifetime():
|
|
166
|
+
manager = OperatorSessionManager(
|
|
167
|
+
backend=InMemoryOperatorSessionBackendForTests(),
|
|
168
|
+
current_revisions=lambda _: REV,
|
|
169
|
+
enabled=True,
|
|
170
|
+
clock=Clock(),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
cookie, csrf = manager.create(
|
|
174
|
+
principal=P,
|
|
175
|
+
acting_principal=A,
|
|
176
|
+
device_fingerprint=DEVICE,
|
|
177
|
+
capability_ceiling=frozenset({"skdashboard.read"}),
|
|
178
|
+
purpose="reporting",
|
|
179
|
+
allowed_origin=ORIGIN,
|
|
180
|
+
revisions=REV,
|
|
181
|
+
ttl_seconds=24 * 60 * 60,
|
|
182
|
+
idle_seconds=24 * 60 * 60,
|
|
183
|
+
).take()
|
|
184
|
+
|
|
185
|
+
assert cookie
|
|
186
|
+
assert csrf
|
|
187
|
+
|
|
188
|
+
|
|
163
189
|
def make_rig(
|
|
164
190
|
backend=None,
|
|
165
191
|
*,
|
|
@@ -261,8 +261,8 @@ def test_login_page_documents_exact_signing_contract(oidc_app):
|
|
|
261
261
|
)
|
|
262
262
|
|
|
263
263
|
assert resp.status_code == 200
|
|
264
|
-
assert ">
|
|
265
|
-
assert "
|
|
264
|
+
assert ">Continue with a passkey<" in resp.text
|
|
265
|
+
assert "Best option for this browser" in resp.text
|
|
266
266
|
assert 'currentPayload=["CAPAUTH_NONCE_V1", "nonce="+ch.nonce' in resp.text
|
|
267
267
|
assert '"client_nonce="+ch.client_nonce_echo' in resp.text
|
|
268
268
|
assert '"timestamp="+ch.timestamp' in resp.text
|
|
@@ -271,7 +271,15 @@ def test_login_page_documents_exact_signing_contract(oidc_app):
|
|
|
271
271
|
assert "gpg --armor --sign" in resp.text
|
|
272
272
|
assert "gpg --armor --detach-sign" in resp.text
|
|
273
273
|
assert "capauth sign --nonce" not in resp.text
|
|
274
|
-
assert "
|
|
274
|
+
assert "Continue with local identity" in resp.text
|
|
275
|
+
assert "Set up this browser" in resp.text
|
|
276
|
+
assert "Other sign-in and recovery options" in resp.text
|
|
277
|
+
assert "local.hidden=true" in resp.text
|
|
278
|
+
assert '<details id="manual-pgp">' in resp.text
|
|
279
|
+
assert "The text below is the message to sign. It is not a signature." in resp.text
|
|
280
|
+
assert 'window.location.pathname!=="/oidc/authorize"' in resp.text
|
|
281
|
+
assert 'sessionStorage.setItem("capauth_bunker_return",target)' in resp.text
|
|
282
|
+
assert 'sessionStorage.setItem("capauth_bunker_auto_sign","1")' in resp.text
|
|
275
283
|
assert "Sign from another device (QR)" in resp.text
|
|
276
284
|
|
|
277
285
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Focused source checks for the guided passkey enrollment workflow."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
ROOT = Path(__file__).parents[1]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_bunker_guides_browser_identity_setup() -> None:
|
|
9
|
+
"""The linked setup path stays short and allows a bounded login return."""
|
|
10
|
+
page = (ROOT / "phone-signer" / "index.html").read_text()
|
|
11
|
+
assert "Load your existing identity" in page
|
|
12
|
+
assert "Choose your identity file" in page
|
|
13
|
+
assert "CapAuth encrypted backup" in page
|
|
14
|
+
assert "jarvis-private.asc" in page
|
|
15
|
+
assert "Enter the browser vault passphrase again" in page
|
|
16
|
+
assert "Continue to passkey setup" in page
|
|
17
|
+
assert "Advanced: unlock for remote signing" in page
|
|
18
|
+
assert "Manage identity on this device" in page
|
|
19
|
+
assert "Download encrypted backup" in page
|
|
20
|
+
assert "Replace identity" in page
|
|
21
|
+
assert "Remove from this browser" in page
|
|
22
|
+
assert "never exports an unencrypted private key" in page
|
|
23
|
+
|
|
24
|
+
controller = (ROOT / "phone-signer" / "app.js").read_text()
|
|
25
|
+
assert 'new URLSearchParams(location.search).get("mode") === "setup"' in controller
|
|
26
|
+
assert '$("pair-card").classList.add("hidden")' in controller
|
|
27
|
+
assert '$("continue-passkey").classList.remove("hidden")' in controller
|
|
28
|
+
assert "vaultPass !== vaultConfirm" in controller
|
|
29
|
+
assert 'const RETURN_KEY = "capauth_bunker_return"' in controller
|
|
30
|
+
assert "target.origin !== location.origin" in controller
|
|
31
|
+
assert 'target.pathname !== "/oidc/authorize"' in controller
|
|
32
|
+
assert "raw.length > 2048" in controller
|
|
33
|
+
assert "/[\\u0000-\\u001F\\u007F]/" in controller
|
|
34
|
+
assert "target.username" in controller
|
|
35
|
+
assert "target.password" in controller
|
|
36
|
+
assert "target.hash" in controller
|
|
37
|
+
assert "location.assign(returnTo)" in controller
|
|
38
|
+
assert "isEncryptedEnvelope(parsed)" in controller
|
|
39
|
+
assert "encryptedEnvelope: parsed" in controller
|
|
40
|
+
assert "backup.version !== 1" in controller
|
|
41
|
+
assert "isEncryptedEnvelope(backup.encryptedEnvelope)" in controller
|
|
42
|
+
assert "Encrypted identity restored" in controller
|
|
43
|
+
assert 'confirm("Remove this encrypted identity from this browser?' in controller
|
|
44
|
+
assert "confirm(\"Replace this browser's encrypted identity?" in controller
|
|
@@ -50,9 +50,7 @@ def _fixture(tmp_path: Path) -> tuple[RotationPlan, dict[Path, bytes]]:
|
|
|
50
50
|
"private_key": str(private),
|
|
51
51
|
"public_key": str(public),
|
|
52
52
|
"credential_consumers": [str(consumer)],
|
|
53
|
-
"custody_bundles": [
|
|
54
|
-
{"path": str(bundle), "checksum_path": str(checksum)}
|
|
55
|
-
],
|
|
53
|
+
"custody_bundles": [{"path": str(bundle), "checksum_path": str(checksum)}],
|
|
56
54
|
"rollback_root": str(tmp_path / "rollback"),
|
|
57
55
|
"proton_pass_entry": "test entry",
|
|
58
56
|
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
"""Focused source checks for the guided passkey enrollment workflow."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
ROOT = Path(__file__).parents[1]
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def test_bunker_guides_browser_identity_setup() -> None:
|
|
9
|
-
"""The linked setup path stays short and returns to passkey enrollment."""
|
|
10
|
-
page = (ROOT / "phone-signer" / "index.html").read_text()
|
|
11
|
-
assert "Load your existing identity" in page
|
|
12
|
-
assert "Choose your armored PGP private key file" in page
|
|
13
|
-
assert "jarvis-private.asc" in page
|
|
14
|
-
assert "Enter the browser vault passphrase again" in page
|
|
15
|
-
assert "Continue to passkey setup" in page
|
|
16
|
-
assert "Advanced: unlock for remote signing" in page
|
|
17
|
-
|
|
18
|
-
controller = (ROOT / "phone-signer" / "app.js").read_text()
|
|
19
|
-
assert 'new URLSearchParams(location.search).get("mode") === "setup"' in controller
|
|
20
|
-
assert '$("pair-card").classList.add("hidden")' in controller
|
|
21
|
-
assert '$("continue-passkey").classList.remove("hidden")' in controller
|
|
22
|
-
assert "vaultPass !== vaultConfirm" in controller
|
|
23
|
-
assert "requestedNext" not in controller
|
|
24
|
-
assert "safeNext" not in controller
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|