capauth 0.3.11__tar.gz → 0.3.13__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.11 → capauth-0.3.13}/.github/workflows/ci.yml +5 -0
- capauth-0.3.13/.github/workflows/image.yml +90 -0
- {capauth-0.3.11 → capauth-0.3.13}/CHANGELOG.md +34 -0
- {capauth-0.3.11 → capauth-0.3.13}/Dockerfile +4 -0
- {capauth-0.3.11 → capauth-0.3.13}/PKG-INFO +3 -1
- {capauth-0.3.11 → capauth-0.3.13}/README.md +2 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/CAPAUTH_OIDC_IDP.md +8 -1
- capauth-0.3.13/docs/SIGN_CHALLENGE.md +80 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/app.js +75 -4
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/index.html +12 -4
- {capauth-0.3.11 → capauth-0.3.13}/scripts/capauth-backup.sh +10 -2
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/cli.py +41 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/control_plane_session.py +2 -2
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/clients.py +9 -1
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/provider.py +97 -25
- capauth-0.3.13/src/capauth/sign_challenge.py +192 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/PKG-INFO +3 -1
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/SOURCES.txt +7 -0
- capauth-0.3.13/src/capauth.egg-info/scm_file_list.json +408 -0
- capauth-0.3.13/tests/test_backup_fallback.py +66 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_control_plane_session.py +24 -0
- capauth-0.3.13/tests/test_dockerfile_version.py +17 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_integration.py +4 -1
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_oidc_hardening.py +92 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_oidc_idp.py +41 -3
- capauth-0.3.13/tests/test_passkey_enrollment_ux.py +44 -0
- capauth-0.3.13/tests/test_sign_challenge.py +229 -0
- {capauth-0.3.11 → capauth-0.3.13}/urllib.error +1 -1
- capauth-0.3.11/tests/test_passkey_enrollment_ux.py +0 -24
- {capauth-0.3.11 → capauth-0.3.13}/.cursorrules +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.dockerignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.github/workflows/docs-check.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.github/workflows/nextcloud-appstore.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.github/workflows/publish.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.github/workflows/pytest.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.github/workflows/secret-scan.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.gitignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.gitleaks-baseline.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/.gitleaks.toml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/AGENTS.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/AI-ADVOCATE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/ARCHITECTURE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/CLAUDE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/CODE_OF_CONDUCT.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/CONTRIBUTING.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/Dockerfile.authentik-capauth +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/LICENSE +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/MANIFEST.in +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/MISSION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/SECURITY.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/SKILL.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/SOP.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/authentik-custom/capauth_migrate.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/authentik-custom/imghdr.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/authentik-custom/user_settings.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/bin/cli.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/.gitignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/README.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/RELEASING.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/background.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/content_scripts/autofill.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/content_scripts/cross_origin_signing.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/content_scripts/detector.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/content_scripts/provider.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/content_scripts/provider_bridge.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/icons/icon.svg +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/icons/icon128.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/icons/icon16.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/icons/icon48.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/lib/bunker-e2e.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/lib/keyqr.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/lib/keyvault.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/lib/openpgp.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/lib/signer-backends.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/manifest.firefox.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/manifest.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/native-host/capauth_signer.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/native-host/com.capauth.signer.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/native-host/install.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/options/options.html +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/options/options.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/package-lock.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/package.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/popup/popup.css +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/popup/popup.html +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/popup/popup.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/scripts/build.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/scripts/generate-icons.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/stages/AuthenticatorSelectionStage.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/stages/CapAuthStage.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/stages/QRDisplayStage.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/stages/SignatureVerificationStage.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/stages/StagePipeline.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/submission/STORE_LISTING.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/submission/privacy-policy.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/fixtures/test_key.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/background.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/bunker_e2e.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/detector.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/keyqr.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/keyvault.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/openpgp.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/provider_api.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/provider_tierb.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/remote_bunker.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/tests/unit/signer_backends.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/browser-extension/vitest.config.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/capauth-service/.env.example +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/capauth-service/deploy.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/capauth-service/docker-compose.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/capauth-service/systemd/capauth-backup.service +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/capauth-service/systemd/capauth-backup.timer +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/forgejo-capauth/.env.example +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/forgejo-capauth/docker-compose.yml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/deploy/forgejo-capauth/preflight.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/AIRGAPPED_SIGNING_WORKSTATION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/ARCHITECTURE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/AUTHENTIK_CUSTOM_STAGE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/AUTHENTIK_DEPLOYMENT_SKSSO.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/AUTHENTIK_FORGEJO_DEPLOYMENT.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/AUTHENTIK_STRATEGY_DECISION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/CAPAUTH-WEBAUTHN-05-QUALIFICATION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/CAPAUTH_BUNKER_REMOTE_SIGNER.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/CLAIMS.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/CRYPTO_SPEC.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/DELEGATED_CAPABILITIES.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/DOWNSTREAM_DURABLE_BACKENDS.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/ENTERPRISE_MANAGED_KEYS.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/HUMAN_IDENTITY_SETUP_AND_ROTATION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/IDENTITY_ESTATE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/INTEGRATION_BLUEPRINT.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/M0-capauth-api-v1.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/M0-capauth-importer-inventory.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/PASSPHRASE_ROTATION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/PQC_CAPAUTH_REVISIT.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/PQC_ROOT_MIGRATION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/PROTOCOL.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/ROOT_ROTATION_CEREMONY.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/SKDASHBOARD_CONTROL_PLANE_POLICY.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/TRUST_GRAPH_COORD_PROJECTION.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/ZERO_KNOWLEDGE_PROFILE.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/audits/BLAST-RADIUS-CAPAUTH-01.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/audits/BLAST-RADIUS-CAPAUTH-01.sha256 +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/authentik-capauth.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/deploy-plan/capauth-bulletproof-deploy.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-MUTATION-ENVELOPE-2026-08-25.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-ROSTER-2026-08-25.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/index.d.ts +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/index.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/package.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/src/index.ts +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/src/openclaw.plugin.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/package.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/icons/icon-192.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/icons/icon-512.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/lib/bunker-e2e.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/lib/bunker-signer.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/lib/canonical.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/lib/keyqr.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/lib/keyvault.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/manifest.webmanifest +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/sw.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/vendor/openpgp.min.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/phone-signer/vendor/qrcode-generator.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/pyproject.toml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/build_authentik_capauth_image.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/capauth-restore.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/gitleaks-scope.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/migrate_canonical_subjects.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/pqc_ceremony_dryrun.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/scripts/provision_agent_profiles.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/setup.cfg +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/skill.yaml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/agent_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/apps.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/api.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/blueprints/capauth-pgp-login.yaml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/claims_mapper.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/nonce_store.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/stage.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/urls.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authentik/verifier.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/authz.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/control_plane.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/control_plane_authorizer.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/base.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/gnupg_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/pgpy_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/sequoia_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/crypto/skpgp_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/custody.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/delegated.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/did.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/discovery/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/discovery/base.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/discovery/file_discovery.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/discovery/mdns.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/discovery/syncthing.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/estate.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/exceptions.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/identity_class.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integration.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/cli.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/config.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/forgejo_api.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/oidc_provider.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/.gitignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/.nextcloudignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/CSR-PR.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/INFO-XML-CHANGES.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/PUBLISHING.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/README.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/appinfo/info.xml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/appinfo/routes.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/build-release.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/.gitignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/capauth.crt +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/capauth.csr +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/composer.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/css/login.css +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/js/login.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/krankerl.toml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/AlternativeLogin/CapAuthLogin.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/AppInfo/Application.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/BackgroundJob/PruneExpiredNonces.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Controller/LoginController.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Controller/TokenController.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Db/KeyRegistry.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Exception/CapAuthUnauthorizedException.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Middleware/PgpVerificationMiddleware.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Migration/Version000300Date20260301000000.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Provider/CapAuthProvider.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/ChallengeService.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/GroupSyncService.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/UserProvisioningService.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/VerifierService.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/User/Backend.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/phpunit.xml +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/screenshots/README.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/screenshots/login.png +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/templates/login.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/bootstrap.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/integration/FullAuthFlowTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Controller/LoginControllerTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Db/KeyRegistryTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Provider/CapAuthProviderTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Service/ChallengeServiceTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Service/VerifierServiceTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/User/BackendTest.php +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/key_transparency.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/login.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/manifest.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/mesh.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/migrations/0001_initial.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/migrations/0002_widen_fingerprint_to_64.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/migrations/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/models.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/canonicalize.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/kernel.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/operator_session.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/proof.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/records.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/store.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pairing/window.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pma.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pqc_confidentiality.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pqc_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pqc_root_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/pqc_tokens.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/profile.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/provisioning.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/registry.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/rotation.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/seal.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/app.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/bunker.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/bunker_e2e.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/keystore.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/clients.example.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/passkey.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/signing_key.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/lib/bunker-e2e.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/lib/keyvault.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/vendor/openpgp.min.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/oidc/store.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/push.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/service/server.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/subject.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/sync.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/testing.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/tokens.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/trust/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/trust/calibration.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth/trust/graph.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/dependency_links.txt +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/entry_points.txt +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/requires.txt +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/src/capauth.egg-info/top_level.txt +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/.gitignore +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/package-lock.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/package.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/src/CapAuthStage.test.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/src/CapAuthStage.vue +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/src/index.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/stages/ak-stage-capauth/vite.config.js +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/__init__.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/conftest.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/fixtures/bunker_e2e_v1_vector.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/fixtures/canonical_nonce_v2_vector.json +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_agent_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_agentrun_capabilities.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_audience_mint_helper.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_audience_tokens.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_audience_ttl_seconds.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_authz.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_authz_legacy_grace.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_authz_signature_gate.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_backup_restore.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_bunker.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_bunker_e2e.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_canonicalize.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_capauth_service_deploy.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_change_capabilities.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_claims_mapper.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_control_plane.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_control_plane_authorizer.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_control_plane_capabilities.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_crypto.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_custody.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_delegated_capabilities.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_device_key_proof.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_did_capability_relationships.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_enrollment_proof_helper.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_estate.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_fingerprint_v6.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_forgejo_preflight.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_home_resolution.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_identity_class.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_identity_ttl.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_installed_bunker_assets.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_key_revocation_expiry.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_key_transparency.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_login.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_manifest.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_mesh.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_models_pqc_q0.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_nextcloud_app.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_no_subapp_dependency.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_nonce_store.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_oidc_durable_store.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_oidc_idp_e2e.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_oidc_refresh_family.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_operator_session.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pairing.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pairing_base_dir_env.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_passkey.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pma.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pqc_confidentiality.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pqc_identity.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pqc_root_integration.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pqc_t3_gate.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_pqc_tokens.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_profile.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_provisioning.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_push.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_py313_compat.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_qr_login.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_real_pgp_e2e.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_register_cli.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_registry.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_rotation.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_seal.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_sequoia_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_service.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_service_authz_decide.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_service_v6_fingerprint.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_skpgp_backend.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_subject.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_sync.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_syncthing_discovery.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_testing_helper.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_token_gc.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_token_store_flag.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_tokens.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_trust_calibration.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_trust_graph.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_trust_graph_coord_audit.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_verifier.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_version_honesty.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tests/test_zk_profile.py +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/tools/build-sq.sh +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/web/README.md +0 -0
- {capauth-0.3.11 → capauth-0.3.13}/web/stages/capauth/CapAuthStage.ts +0 -0
|
@@ -37,11 +37,16 @@ jobs:
|
|
|
37
37
|
# `import skcapstone` succeeds without it while
|
|
38
38
|
# `from skcapstone.coordination import Board` does not. --no-deps means we
|
|
39
39
|
# have to name it ourselves.
|
|
40
|
+
# skcapstone requires Python 3.11+, capauth supports 3.10: on 3.10 the
|
|
41
|
+
# integration tests skip (pytest.importorskip); on 3.11+ the import is
|
|
42
|
+
# asserted here so those tests can never skip silently.
|
|
40
43
|
- name: Install skcapstone (optional integration backbone) from source
|
|
44
|
+
if: matrix.python-version != '3.10'
|
|
41
45
|
run: |
|
|
42
46
|
pip install --no-deps --no-cache-dir \
|
|
43
47
|
"skcapstone @ git+https://github.com/smilinTux/skcapstone" \
|
|
44
48
|
"skcoord @ git+https://github.com/smilinTux/skcoord"
|
|
49
|
+
python -c "from skcapstone.scheduler_jobs import load_jobs_with_dropins"
|
|
45
50
|
- name: Run tests with coverage
|
|
46
51
|
run: python -m pytest tests/ -v --tb=short --cov=capauth --cov-report=xml --cov-report=term-missing
|
|
47
52
|
- name: Upload coverage
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: Container image
|
|
2
|
+
|
|
3
|
+
# Publishes the CapAuth service image (the OIDC IdP and verify service) for a
|
|
4
|
+
# release tag as ghcr.io/smilintux/capauth:<version>, and records its digest.
|
|
5
|
+
# Consumers pin <version>@sha256:<digest> (SKStacks sksso.CAPAUTH_IMAGE refuses
|
|
6
|
+
# anything else). A published version is never overwritten.
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
tags: ["v*"]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
tag:
|
|
13
|
+
description: "Existing release tag to (re)build if its image is missing, e.g. v0.3.13"
|
|
14
|
+
required: true
|
|
15
|
+
|
|
16
|
+
permissions:
|
|
17
|
+
contents: read
|
|
18
|
+
packages: write
|
|
19
|
+
|
|
20
|
+
concurrency:
|
|
21
|
+
group: image-${{ inputs.tag || github.ref }}
|
|
22
|
+
cancel-in-progress: false
|
|
23
|
+
|
|
24
|
+
env:
|
|
25
|
+
IMAGE: ghcr.io/smilintux/capauth
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
image:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@v4
|
|
32
|
+
with:
|
|
33
|
+
ref: ${{ inputs.tag || github.ref }}
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
fetch-tags: true
|
|
36
|
+
- name: Resolve the version and refuse a tag that is not on main
|
|
37
|
+
id: v
|
|
38
|
+
env:
|
|
39
|
+
TAG: ${{ inputs.tag || github.ref_name }}
|
|
40
|
+
run: |
|
|
41
|
+
set -euo pipefail
|
|
42
|
+
case "$TAG" in v[0-9]*.[0-9]*.[0-9]*) ;; *) echo "::error::not a release tag: $TAG"; exit 1 ;; esac
|
|
43
|
+
version="${TAG#v}"
|
|
44
|
+
if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
45
|
+
echo "::error::not a release version: $version"; exit 1
|
|
46
|
+
fi
|
|
47
|
+
git fetch --no-tags origin main:refs/remotes/origin/main
|
|
48
|
+
sha="$(git rev-parse "refs/tags/$TAG^{commit}")"
|
|
49
|
+
git merge-base --is-ancestor "$sha" refs/remotes/origin/main || {
|
|
50
|
+
echo "::error::$TAG ($sha) is not on main"; exit 1; }
|
|
51
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
52
|
+
echo "sha=$sha" >> "$GITHUB_OUTPUT"
|
|
53
|
+
- name: Log in to ghcr.io
|
|
54
|
+
env:
|
|
55
|
+
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
56
|
+
run: echo "$TOKEN" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
|
|
57
|
+
- name: Refuse to overwrite a published version
|
|
58
|
+
run: |
|
|
59
|
+
if docker manifest inspect "$IMAGE:${{ steps.v.outputs.version }}" >/dev/null 2>&1; then
|
|
60
|
+
echo "::error::$IMAGE:${{ steps.v.outputs.version }} already exists; versions are immutable"
|
|
61
|
+
exit 1
|
|
62
|
+
fi
|
|
63
|
+
- name: Build
|
|
64
|
+
run: |
|
|
65
|
+
docker build \
|
|
66
|
+
--build-arg CAPAUTH_VERSION="${{ steps.v.outputs.version }}" \
|
|
67
|
+
--label org.opencontainers.image.source="https://github.com/${{ github.repository }}" \
|
|
68
|
+
--label org.opencontainers.image.revision="${{ steps.v.outputs.sha }}" \
|
|
69
|
+
--label org.opencontainers.image.version="${{ steps.v.outputs.version }}" \
|
|
70
|
+
-t "$IMAGE:${{ steps.v.outputs.version }}" .
|
|
71
|
+
- name: Smoke test (version, service answers)
|
|
72
|
+
run: |
|
|
73
|
+
set -euo pipefail
|
|
74
|
+
v="${{ steps.v.outputs.version }}"
|
|
75
|
+
docker run --rm "$IMAGE:$v" capauth --version | tee /dev/stderr | grep -F "$v"
|
|
76
|
+
docker run -d --name smoke -p 18420:8420 "$IMAGE:$v"
|
|
77
|
+
for i in $(seq 1 30); do
|
|
78
|
+
curl -fsS http://127.0.0.1:18420/capauth/v1/status >/dev/null && break
|
|
79
|
+
sleep 2
|
|
80
|
+
done
|
|
81
|
+
curl -fsS http://127.0.0.1:18420/capauth/v1/status >/dev/null
|
|
82
|
+
docker rm -f smoke
|
|
83
|
+
- name: Push and record the digest
|
|
84
|
+
run: |
|
|
85
|
+
set -euo pipefail
|
|
86
|
+
v="${{ steps.v.outputs.version }}"
|
|
87
|
+
docker push "$IMAGE:$v"
|
|
88
|
+
ref="$(docker inspect --format '{{index .RepoDigests 0}}' "$IMAGE:$v")"
|
|
89
|
+
echo "Pinned reference: \`$IMAGE:$v@${ref#*@}\`" >> "$GITHUB_STEP_SUMMARY"
|
|
90
|
+
echo "pinned: $IMAGE:$v@${ref#*@}"
|
|
@@ -6,6 +6,40 @@ All notable changes to `capauth` are documented here. The format is based on
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
- Release tags (`v*`) now also publish the service image
|
|
10
|
+
`ghcr.io/smilintux/capauth:<version>` (`.github/workflows/image.yml`): the
|
|
11
|
+
tag must be on main, a published version is never overwritten, the image is
|
|
12
|
+
smoke-tested (`capauth --version`, `/capauth/v1/status`) before the push,
|
|
13
|
+
and the job summary records the digest to pin (`<version>@sha256:...`).
|
|
14
|
+
|
|
15
|
+
- `capauth sign-challenge`: reads the CapAuth login challenge from the
|
|
16
|
+
clipboard (or `--stdin`), refuses anything that is not a complete
|
|
17
|
+
`CAPAUTH_NONCE_V1`/`V2` challenge, signs it with gpg (gpg-agent's pinentry
|
|
18
|
+
asks for the passphrase; the tool never handles it) and puts the armored
|
|
19
|
+
signature back on the clipboard (or `--stdout`), printing only
|
|
20
|
+
`signed, paste it now`. Key: `--key`, `$CAPAUTH_SIGN_KEY`, or the profile.
|
|
21
|
+
The OIDC login page gains a "Paste signature and continue" button, names
|
|
22
|
+
the command in its steps and remembers the fingerprint of the last manual
|
|
23
|
+
login, so a repeat login is copy, sign, paste. Docs: `docs/SIGN_CHALLENGE.md`.
|
|
24
|
+
|
|
25
|
+
- Service image builds from a plain checkout again: the Dockerfile takes
|
|
26
|
+
`--build-arg CAPAUTH_VERSION=<version>` (default `0.0.0+docker`) for
|
|
27
|
+
setuptools-scm, because `.dockerignore` keeps `.git` out of the context.
|
|
28
|
+
|
|
29
|
+
- OIDC IdP: a static client may set `require_nonce: false` so stock
|
|
30
|
+
Authentik's OAuth source, which never sends an OIDC `nonce`, can use the
|
|
31
|
+
authorization code flow. State, PKCE S256 and the confidential client secret
|
|
32
|
+
stay mandatory; the default is unchanged (nonce required), and a nonce that
|
|
33
|
+
is sent must still be well formed.
|
|
34
|
+
|
|
35
|
+
- CI: green on main again. `ruff format` (pinned 0.15.4) applied to
|
|
36
|
+
`tests/test_backup_fallback.py`; the Python 3.10 job no longer installs
|
|
37
|
+
skcapstone (it requires 3.11+), and the skcapstone integration tests skip
|
|
38
|
+
there while 3.11+ asserts the import so they cannot skip silently.
|
|
39
|
+
|
|
40
|
+
- Raise operator proof absolute and idle ceilings to 24 hours while keeping
|
|
41
|
+
access capabilities bounded to five minutes (card `4af0ae7f`).
|
|
42
|
+
|
|
9
43
|
- Normalize control-plane authorization decisions to the actual reconstructed
|
|
10
44
|
token lifetime before exact owner-policy evaluation, while retaining the raw
|
|
11
45
|
signed decision for replay and currentness receipts (card `9b0c6dce`).
|
|
@@ -16,6 +16,10 @@ COPY src/ ./src/
|
|
|
16
16
|
# resolves these relative to the package root → /app/phone-signer.
|
|
17
17
|
COPY phone-signer/ ./phone-signer/
|
|
18
18
|
|
|
19
|
+
# The build context has no .git (.dockerignore), so setuptools-scm cannot
|
|
20
|
+
# derive a version: pass one with --build-arg CAPAUTH_VERSION=<version>.
|
|
21
|
+
ARG CAPAUTH_VERSION=0.0.0+docker
|
|
22
|
+
ENV SETUPTOOLS_SCM_PRETEND_VERSION=${CAPAUTH_VERSION}
|
|
19
23
|
RUN pip install --no-cache-dir -e ".[service]"
|
|
20
24
|
RUN pip install --no-cache-dir python-multipart>=0.0.6
|
|
21
25
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: capauth
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.13
|
|
4
4
|
Summary: Capability-based Authentication — decentralized, PGP-based identity and authorization
|
|
5
5
|
Author-email: smilinTux <admin@smilintux.org>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -254,6 +254,7 @@ capauth pqc-report # live PQC posture per surface
|
|
|
254
254
|
|
|
255
255
|
# Auth & integration
|
|
256
256
|
capauth login <service_url> [--no-claims] # passwordless PGP login (caches OIDC token)
|
|
257
|
+
capauth sign-challenge # sign the copied browser login challenge, paste the result
|
|
257
258
|
capauth setup forgejo --capauth-url <url> # generate Forgejo OIDC app.ini block
|
|
258
259
|
|
|
259
260
|
# Mesh & membership
|
|
@@ -294,6 +295,7 @@ rides in the payload `event` field so routing stays severity-based.
|
|
|
294
295
|
| **[Integration Blueprint](docs/INTEGRATION_BLUEPRINT.md)** | third-party integration guide |
|
|
295
296
|
| **[Cold-Machine Bootstrap & DR](docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md)** | standing capauth back up on a blank box + disaster recovery: the restore-not-regenerate rule, the ordered restore chain, and the operator checklist |
|
|
296
297
|
| **[authentik-capauth](docs/authentik-capauth.md)** | the custom Authentik image with the CapAuth PGP stage baked in — build (`AK_VERSION`, version-agnostic venv install, frontend rebuild), SKStacks deploy + `lifecycle.migrate` override, and the four build/migrate gotchas |
|
|
298
|
+
| **[Sign a login: copy, sign, paste](docs/SIGN_CHALLENGE.md)** | `capauth sign-challenge`: the four-step browser login with your gpg key, one-time setup, desktop shortcut |
|
|
297
299
|
| **[AI Advocate](AI-ADVOCATE.md)** | how AI advocates manage a sovereign profile on your behalf |
|
|
298
300
|
|
|
299
301
|
## Why it matters
|
|
@@ -191,6 +191,7 @@ capauth pqc-report # live PQC posture per surface
|
|
|
191
191
|
|
|
192
192
|
# Auth & integration
|
|
193
193
|
capauth login <service_url> [--no-claims] # passwordless PGP login (caches OIDC token)
|
|
194
|
+
capauth sign-challenge # sign the copied browser login challenge, paste the result
|
|
194
195
|
capauth setup forgejo --capauth-url <url> # generate Forgejo OIDC app.ini block
|
|
195
196
|
|
|
196
197
|
# Mesh & membership
|
|
@@ -231,6 +232,7 @@ rides in the payload `event` field so routing stays severity-based.
|
|
|
231
232
|
| **[Integration Blueprint](docs/INTEGRATION_BLUEPRINT.md)** | third-party integration guide |
|
|
232
233
|
| **[Cold-Machine Bootstrap & DR](docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md)** | standing capauth back up on a blank box + disaster recovery: the restore-not-regenerate rule, the ordered restore chain, and the operator checklist |
|
|
233
234
|
| **[authentik-capauth](docs/authentik-capauth.md)** | the custom Authentik image with the CapAuth PGP stage baked in — build (`AK_VERSION`, version-agnostic venv install, frontend rebuild), SKStacks deploy + `lifecycle.migrate` override, and the four build/migrate gotchas |
|
|
235
|
+
| **[Sign a login: copy, sign, paste](docs/SIGN_CHALLENGE.md)** | `capauth sign-challenge`: the four-step browser login with your gpg key, one-time setup, desktop shortcut |
|
|
234
236
|
| **[AI Advocate](AI-ADVOCATE.md)** | how AI advocates manage a sovereign profile on your behalf |
|
|
235
237
|
|
|
236
238
|
## Why it matters
|
|
@@ -175,10 +175,17 @@ Add an entry to your CapAuth client registry for Authentik:
|
|
|
175
175
|
"client_secret": "<long-random-secret>",
|
|
176
176
|
"redirect_uris": ["https://<authentik-host>/source/oauth/callback/capauth/"],
|
|
177
177
|
"name": "Authentik",
|
|
178
|
-
"scopes": ["openid", "profile", "email", "groups"]
|
|
178
|
+
"scopes": ["openid", "profile", "email", "groups"],
|
|
179
|
+
"require_nonce": false
|
|
179
180
|
}
|
|
180
181
|
```
|
|
181
182
|
|
|
183
|
+
`require_nonce: false` is needed because Authentik's OAuth source never sends
|
|
184
|
+
an OIDC `nonce` (checked against Authentik 2025.12). Without it `/oidc/authorize`
|
|
185
|
+
answers `400 invalid_nonce`. The flow stays bound by `state`, PKCE S256 and the
|
|
186
|
+
client secret. Set the source's **PKCE** field to **S256** (Authentik 2025.2+;
|
|
187
|
+
older Authentik has no source PKCE and cannot use this IdP).
|
|
188
|
+
|
|
182
189
|
> The redirect URI Authentik uses for an OAuth source is
|
|
183
190
|
> `https://<authentik-host>/source/oauth/callback/<source-slug>/`. If you name
|
|
184
191
|
> the source slug `capauth`, the URI is the one above. Confirm the exact value
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Signing a CapAuth login: copy, sign, paste
|
|
2
|
+
|
|
3
|
+
`capauth sign-challenge` is the simple way to log in with your PGP key when an
|
|
4
|
+
application (for example skmesh, through Authentik) offers "Sign in with
|
|
5
|
+
CapAuth". You never type a key, a fingerprint (after the first time) or a gpg
|
|
6
|
+
command.
|
|
7
|
+
|
|
8
|
+
## The four steps
|
|
9
|
+
|
|
10
|
+
1. On the login page click **Sign in with CapAuth** (the button next to the
|
|
11
|
+
normal username and password form). You land on the CapAuth page; open
|
|
12
|
+
"Other sign-in and recovery options" if it is closed.
|
|
13
|
+
2. Click **Copy message**. (The first time only: type your key's fingerprint
|
|
14
|
+
into the fingerprint box first. The page remembers it in this browser, so
|
|
15
|
+
next time the section is already open with a fresh message.)
|
|
16
|
+
3. Run **`capauth sign-challenge`** in a terminal, or press your desktop
|
|
17
|
+
shortcut (below). gpg shows its own passphrase window; type the key's
|
|
18
|
+
passphrase there. The terminal prints `signed, paste it now`.
|
|
19
|
+
4. Click **Paste signature and continue** (or paste into the box with Ctrl+V
|
|
20
|
+
and click Verify & Continue). You are logged in.
|
|
21
|
+
|
|
22
|
+
The message you copy is only valid for a few minutes. If it expired, reload
|
|
23
|
+
the page and start again at step 2.
|
|
24
|
+
|
|
25
|
+
## What the command does, and what it does not
|
|
26
|
+
|
|
27
|
+
- Reads the copied text from the clipboard (Wayland `wl-paste`, X11 `xclip`
|
|
28
|
+
or `xsel`, macOS `pbpaste`), or from stdin with `--stdin`.
|
|
29
|
+
- Refuses anything that is not a complete CapAuth login challenge
|
|
30
|
+
(`CAPAUTH_NONCE_V1` or `CAPAUTH_NONCE_V2` with every field in order). It
|
|
31
|
+
never signs arbitrary clipboard text.
|
|
32
|
+
- Signs with `gpg --armor --detach-sign --local-user <key>`. The passphrase
|
|
33
|
+
goes from you to gpg-agent's pinentry dialog; the command never sees,
|
|
34
|
+
stores or prints it.
|
|
35
|
+
- Puts the ASCII-armored signature on the clipboard (or prints it with
|
|
36
|
+
`--stdout`) and prints only `signed, paste it now`.
|
|
37
|
+
|
|
38
|
+
Which key: `--key <fingerprint>`, else `$CAPAUTH_SIGN_KEY`, else the key of
|
|
39
|
+
your CapAuth profile (`capauth profile show`).
|
|
40
|
+
|
|
41
|
+
## One-time setup
|
|
42
|
+
|
|
43
|
+
1. Install capauth (`pip install capauth`) and a clipboard tool
|
|
44
|
+
(`sudo apt install wl-clipboard` on Wayland, `xclip` on X11).
|
|
45
|
+
2. gpg must hold the private key. A CapAuth profile keeps it in
|
|
46
|
+
`<CapAuth home>/identity/private.asc` (home: `~/.skcapstone/capauth`, or
|
|
47
|
+
`capauth --home`). Import it once; gpg asks for its passphrase in the
|
|
48
|
+
pinentry window and keeps it protected with that passphrase:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
gpg --import ~/.skcapstone/capauth/identity/private.asc
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
This is a second protected copy of the key (in `~/.gnupg`). If the key's
|
|
55
|
+
passphrase is ever changed, change it in gpg too (`gpg --edit-key <fp>`,
|
|
56
|
+
then `passwd`), or delete the gpg copy and import again.
|
|
57
|
+
3. The key must be enrolled and approved in CapAuth and linked to your user
|
|
58
|
+
(the admin does the approval and the link; you send them your **public**
|
|
59
|
+
key: `capauth export-pubkey`).
|
|
60
|
+
|
|
61
|
+
### Desktop shortcut
|
|
62
|
+
|
|
63
|
+
Bind a keyboard shortcut (GNOME: Settings, Keyboard, Custom Shortcuts;
|
|
64
|
+
Cinnamon and KDE have the same) to:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
capauth sign-challenge
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
gpg-agent needs a graphical pinentry for this (`pinentry-gnome3` or
|
|
71
|
+
`pinentry-qt`, the default on desktop installs). Then the flow is: Copy
|
|
72
|
+
message, shortcut, passphrase window, Paste signature and continue.
|
|
73
|
+
|
|
74
|
+
## Later upgrade: sign on the phone
|
|
75
|
+
|
|
76
|
+
The CapAuth Bunker phone signer ([CAPAUTH_BUNKER_REMOTE_SIGNER.md](CAPAUTH_BUNKER_REMOTE_SIGNER.md))
|
|
77
|
+
keeps the key on your phone and signs by scanning a QR code ("Sign from
|
|
78
|
+
another device (QR)" on the same page). It needs the `/bunker/` routes and a
|
|
79
|
+
relay configured on the server side, so it is a follow-up to this copy and
|
|
80
|
+
paste flow.
|
|
@@ -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
|
|
|
@@ -152,8 +152,16 @@ backup_keystore() {
|
|
|
152
152
|
# Online backup: consistent snapshot of a live DB, source untouched.
|
|
153
153
|
sqlite3 "$DB_PATH" ".backup '$dst'"
|
|
154
154
|
else
|
|
155
|
-
|
|
156
|
-
|
|
155
|
+
log "sqlite3 CLI not found; using Python SQLite online backup"
|
|
156
|
+
python3 - "$DB_PATH" "$dst" <<'PY'
|
|
157
|
+
import sqlite3
|
|
158
|
+
import sys
|
|
159
|
+
|
|
160
|
+
source, destination = sys.argv[1:]
|
|
161
|
+
with sqlite3.connect(f"file:{source}?mode=ro", uri=True) as live:
|
|
162
|
+
with sqlite3.connect(destination) as backup:
|
|
163
|
+
live.backup(backup)
|
|
164
|
+
PY
|
|
157
165
|
fi
|
|
158
166
|
chmod 600 "$dst"
|
|
159
167
|
manifest "keys.db: source=$DB_PATH bytes=$(stat -c%s "$dst" 2>/dev/null || stat -f%z "$dst") sha256=$(sha "$dst")"
|
|
@@ -646,6 +646,47 @@ def export_pubkey(ctx: click.Context, output: Optional[str]) -> None:
|
|
|
646
646
|
raise SystemExit(1)
|
|
647
647
|
|
|
648
648
|
|
|
649
|
+
@main.command("sign-challenge")
|
|
650
|
+
@click.option(
|
|
651
|
+
"--key",
|
|
652
|
+
default=None,
|
|
653
|
+
help="Fingerprint to sign with (default: $CAPAUTH_SIGN_KEY, then your CapAuth profile).",
|
|
654
|
+
)
|
|
655
|
+
@click.option("--stdin", "from_stdin", is_flag=True, help="Read the challenge from stdin.")
|
|
656
|
+
@click.option("--stdout", "to_stdout", is_flag=True, help="Print the signature instead.")
|
|
657
|
+
@click.pass_context
|
|
658
|
+
def sign_challenge_cmd(
|
|
659
|
+
ctx: click.Context, key: Optional[str], from_stdin: bool, to_stdout: bool
|
|
660
|
+
) -> None:
|
|
661
|
+
"""Sign the CapAuth login challenge on the clipboard.
|
|
662
|
+
|
|
663
|
+
On the login page click "Copy message", run this, then paste: the
|
|
664
|
+
signature replaces the challenge on the clipboard. gpg-agent asks for the
|
|
665
|
+
key's passphrase in its own dialog; this command never sees it.
|
|
666
|
+
"""
|
|
667
|
+
from .sign_challenge import (
|
|
668
|
+
ChallengeError,
|
|
669
|
+
normalize_challenge,
|
|
670
|
+
read_clipboard,
|
|
671
|
+
resolve_key,
|
|
672
|
+
write_clipboard,
|
|
673
|
+
)
|
|
674
|
+
from .sign_challenge import sign as gpg_sign
|
|
675
|
+
|
|
676
|
+
try:
|
|
677
|
+
fp = resolve_key(key, ctx.obj.get("home"))
|
|
678
|
+
text = click.get_text_stream("stdin").read() if from_stdin else read_clipboard()
|
|
679
|
+
signature = gpg_sign(normalize_challenge(text), fp)
|
|
680
|
+
if to_stdout:
|
|
681
|
+
click.echo(signature, nl=False)
|
|
682
|
+
else:
|
|
683
|
+
write_clipboard(signature)
|
|
684
|
+
click.echo("signed, paste it now")
|
|
685
|
+
except ChallengeError as exc:
|
|
686
|
+
click.echo(f"Error: {exc}", err=True)
|
|
687
|
+
raise SystemExit(1)
|
|
688
|
+
|
|
689
|
+
|
|
649
690
|
@main.command("verify")
|
|
650
691
|
@click.option(
|
|
651
692
|
"--pubkey",
|
|
@@ -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()
|
|
@@ -17,10 +17,16 @@ Each client entry:
|
|
|
17
17
|
"client_secret": "change-me",
|
|
18
18
|
"redirect_uris": ["https://authentik.example/source/oauth/callback/capauth/"],
|
|
19
19
|
"name": "Authentik",
|
|
20
|
-
"scopes": ["openid", "profile", "email", "groups"]
|
|
20
|
+
"scopes": ["openid", "profile", "email", "groups"],
|
|
21
|
+
"require_nonce": true
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
``redirect_uris`` is matched EXACTLY (no wildcard) per OAuth2 security guidance.
|
|
25
|
+
|
|
26
|
+
``require_nonce`` (default true) may be set to false for a relying party that
|
|
27
|
+
never sends an OIDC ``nonce``, such as stock Authentik's OAuth source. The code
|
|
28
|
+
flow stays bound by state, PKCE S256 and the confidential client secret; a
|
|
29
|
+
nonce that is sent must still be well formed and is echoed in the ID token.
|
|
24
30
|
"""
|
|
25
31
|
|
|
26
32
|
from __future__ import annotations
|
|
@@ -44,6 +50,7 @@ class OIDCClient:
|
|
|
44
50
|
redirect_uris: list[str]
|
|
45
51
|
name: str = ""
|
|
46
52
|
scopes: list[str] = field(default_factory=lambda: list(DEFAULT_SCOPES))
|
|
53
|
+
require_nonce: bool = True
|
|
47
54
|
|
|
48
55
|
def redirect_uri_allowed(self, redirect_uri: str) -> bool:
|
|
49
56
|
"""Return True if *redirect_uri* exactly matches a registered URI."""
|
|
@@ -93,6 +100,7 @@ class ClientRegistry:
|
|
|
93
100
|
redirect_uris=list(entry.get("redirect_uris", [])),
|
|
94
101
|
name=entry.get("name", entry["client_id"]),
|
|
95
102
|
scopes=list(entry.get("scopes", DEFAULT_SCOPES)),
|
|
103
|
+
require_nonce=entry.get("require_nonce", True) is not False,
|
|
96
104
|
)
|
|
97
105
|
|
|
98
106
|
# ------------------------------------------------------------------
|