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.
Files changed (406) hide show
  1. {capauth-0.3.10 → capauth-0.3.12}/CHANGELOG.md +7 -0
  2. {capauth-0.3.10/src/capauth.egg-info → capauth-0.3.12}/PKG-INFO +1 -1
  3. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/app.js +75 -4
  4. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/index.html +12 -4
  5. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane_authorizer.py +5 -5
  6. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane_session.py +2 -2
  7. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/rotation.py +15 -7
  8. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/provider.py +58 -14
  9. {capauth-0.3.10 → capauth-0.3.12/src/capauth.egg-info}/PKG-INFO +1 -1
  10. {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_authorizer.py +12 -4
  11. {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_session.py +27 -1
  12. {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_idp.py +11 -3
  13. capauth-0.3.12/tests/test_passkey_enrollment_ux.py +44 -0
  14. {capauth-0.3.10 → capauth-0.3.12}/tests/test_rotation.py +1 -3
  15. capauth-0.3.10/tests/test_passkey_enrollment_ux.py +0 -24
  16. {capauth-0.3.10 → capauth-0.3.12}/.cursorrules +0 -0
  17. {capauth-0.3.10 → capauth-0.3.12}/.dockerignore +0 -0
  18. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/ci.yml +0 -0
  19. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/docs-check.yml +0 -0
  20. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/nextcloud-appstore.yml +0 -0
  21. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/publish.yml +0 -0
  22. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/pytest.yml +0 -0
  23. {capauth-0.3.10 → capauth-0.3.12}/.github/workflows/secret-scan.yml +0 -0
  24. {capauth-0.3.10 → capauth-0.3.12}/.gitignore +0 -0
  25. {capauth-0.3.10 → capauth-0.3.12}/.gitleaks-baseline.json +0 -0
  26. {capauth-0.3.10 → capauth-0.3.12}/.gitleaks.toml +0 -0
  27. {capauth-0.3.10 → capauth-0.3.12}/AGENTS.md +0 -0
  28. {capauth-0.3.10 → capauth-0.3.12}/AI-ADVOCATE.md +0 -0
  29. {capauth-0.3.10 → capauth-0.3.12}/ARCHITECTURE.md +0 -0
  30. {capauth-0.3.10 → capauth-0.3.12}/CLAUDE.md +0 -0
  31. {capauth-0.3.10 → capauth-0.3.12}/CODE_OF_CONDUCT.md +0 -0
  32. {capauth-0.3.10 → capauth-0.3.12}/CONTRIBUTING.md +0 -0
  33. {capauth-0.3.10 → capauth-0.3.12}/Dockerfile +0 -0
  34. {capauth-0.3.10 → capauth-0.3.12}/Dockerfile.authentik-capauth +0 -0
  35. {capauth-0.3.10 → capauth-0.3.12}/LICENSE +0 -0
  36. {capauth-0.3.10 → capauth-0.3.12}/MANIFEST.in +0 -0
  37. {capauth-0.3.10 → capauth-0.3.12}/MISSION.md +0 -0
  38. {capauth-0.3.10 → capauth-0.3.12}/README.md +0 -0
  39. {capauth-0.3.10 → capauth-0.3.12}/SECURITY.md +0 -0
  40. {capauth-0.3.10 → capauth-0.3.12}/SKILL.md +0 -0
  41. {capauth-0.3.10 → capauth-0.3.12}/SOP.md +0 -0
  42. {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/capauth_migrate.py +0 -0
  43. {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/imghdr.py +0 -0
  44. {capauth-0.3.10 → capauth-0.3.12}/authentik-custom/user_settings.py +0 -0
  45. {capauth-0.3.10 → capauth-0.3.12}/bin/cli.js +0 -0
  46. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/.gitignore +0 -0
  47. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/README.md +0 -0
  48. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/RELEASING.md +0 -0
  49. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/background.js +0 -0
  50. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/autofill.js +0 -0
  51. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/cross_origin_signing.js +0 -0
  52. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/detector.js +0 -0
  53. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/provider.js +0 -0
  54. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/content_scripts/provider_bridge.js +0 -0
  55. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon.svg +0 -0
  56. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon128.png +0 -0
  57. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon16.png +0 -0
  58. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/icons/icon48.png +0 -0
  59. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/bunker-e2e.js +0 -0
  60. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/keyqr.js +0 -0
  61. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/keyvault.js +0 -0
  62. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/openpgp.js +0 -0
  63. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/lib/signer-backends.js +0 -0
  64. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/manifest.firefox.json +0 -0
  65. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/manifest.json +0 -0
  66. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/capauth_signer.py +0 -0
  67. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/com.capauth.signer.json +0 -0
  68. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/native-host/install.sh +0 -0
  69. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/options/options.html +0 -0
  70. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/options/options.js +0 -0
  71. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/package-lock.json +0 -0
  72. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/package.json +0 -0
  73. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.css +0 -0
  74. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.html +0 -0
  75. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/popup/popup.js +0 -0
  76. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/scripts/build.js +0 -0
  77. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/scripts/generate-icons.js +0 -0
  78. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/AuthenticatorSelectionStage.js +0 -0
  79. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/CapAuthStage.js +0 -0
  80. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/QRDisplayStage.js +0 -0
  81. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/SignatureVerificationStage.js +0 -0
  82. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/stages/StagePipeline.js +0 -0
  83. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/submission/STORE_LISTING.md +0 -0
  84. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/submission/privacy-policy.md +0 -0
  85. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/fixtures/test_key.json +0 -0
  86. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/background.test.js +0 -0
  87. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/bunker_e2e.test.js +0 -0
  88. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/detector.test.js +0 -0
  89. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/keyqr.test.js +0 -0
  90. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/keyvault.test.js +0 -0
  91. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/openpgp.test.js +0 -0
  92. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/provider_api.test.js +0 -0
  93. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/provider_tierb.test.js +0 -0
  94. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/remote_bunker.test.js +0 -0
  95. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/tests/unit/signer_backends.test.js +0 -0
  96. {capauth-0.3.10 → capauth-0.3.12}/browser-extension/vitest.config.js +0 -0
  97. {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/.env.example +0 -0
  98. {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/deploy.sh +0 -0
  99. {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/docker-compose.yml +0 -0
  100. {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/systemd/capauth-backup.service +0 -0
  101. {capauth-0.3.10 → capauth-0.3.12}/deploy/capauth-service/systemd/capauth-backup.timer +0 -0
  102. {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/.env.example +0 -0
  103. {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/docker-compose.yml +0 -0
  104. {capauth-0.3.10 → capauth-0.3.12}/deploy/forgejo-capauth/preflight.sh +0 -0
  105. {capauth-0.3.10 → capauth-0.3.12}/docs/AIRGAPPED_SIGNING_WORKSTATION.md +0 -0
  106. {capauth-0.3.10 → capauth-0.3.12}/docs/ARCHITECTURE.md +0 -0
  107. {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_CUSTOM_STAGE.md +0 -0
  108. {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_DEPLOYMENT_SKSSO.md +0 -0
  109. {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_FORGEJO_DEPLOYMENT.md +0 -0
  110. {capauth-0.3.10 → capauth-0.3.12}/docs/AUTHENTIK_STRATEGY_DECISION.md +0 -0
  111. {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH-WEBAUTHN-05-QUALIFICATION.md +0 -0
  112. {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH_BUNKER_REMOTE_SIGNER.md +0 -0
  113. {capauth-0.3.10 → capauth-0.3.12}/docs/CAPAUTH_OIDC_IDP.md +0 -0
  114. {capauth-0.3.10 → capauth-0.3.12}/docs/CLAIMS.md +0 -0
  115. {capauth-0.3.10 → capauth-0.3.12}/docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md +0 -0
  116. {capauth-0.3.10 → capauth-0.3.12}/docs/CRYPTO_SPEC.md +0 -0
  117. {capauth-0.3.10 → capauth-0.3.12}/docs/DELEGATED_CAPABILITIES.md +0 -0
  118. {capauth-0.3.10 → capauth-0.3.12}/docs/DOWNSTREAM_DURABLE_BACKENDS.md +0 -0
  119. {capauth-0.3.10 → capauth-0.3.12}/docs/ENTERPRISE_MANAGED_KEYS.md +0 -0
  120. {capauth-0.3.10 → capauth-0.3.12}/docs/HUMAN_IDENTITY_SETUP_AND_ROTATION.md +0 -0
  121. {capauth-0.3.10 → capauth-0.3.12}/docs/IDENTITY_ESTATE.md +0 -0
  122. {capauth-0.3.10 → capauth-0.3.12}/docs/INTEGRATION_BLUEPRINT.md +0 -0
  123. {capauth-0.3.10 → capauth-0.3.12}/docs/M0-capauth-api-v1.md +0 -0
  124. {capauth-0.3.10 → capauth-0.3.12}/docs/M0-capauth-importer-inventory.md +0 -0
  125. {capauth-0.3.10 → capauth-0.3.12}/docs/PASSPHRASE_ROTATION.md +0 -0
  126. {capauth-0.3.10 → capauth-0.3.12}/docs/PQC_CAPAUTH_REVISIT.md +0 -0
  127. {capauth-0.3.10 → capauth-0.3.12}/docs/PQC_ROOT_MIGRATION.md +0 -0
  128. {capauth-0.3.10 → capauth-0.3.12}/docs/PROTOCOL.md +0 -0
  129. {capauth-0.3.10 → capauth-0.3.12}/docs/ROOT_ROTATION_CEREMONY.md +0 -0
  130. {capauth-0.3.10 → capauth-0.3.12}/docs/SKDASHBOARD_CONTROL_PLANE_POLICY.md +0 -0
  131. {capauth-0.3.10 → capauth-0.3.12}/docs/TRUST_GRAPH_COORD_PROJECTION.md +0 -0
  132. {capauth-0.3.10 → capauth-0.3.12}/docs/ZERO_KNOWLEDGE_PROFILE.md +0 -0
  133. {capauth-0.3.10 → capauth-0.3.12}/docs/audits/BLAST-RADIUS-CAPAUTH-01.md +0 -0
  134. {capauth-0.3.10 → capauth-0.3.12}/docs/audits/BLAST-RADIUS-CAPAUTH-01.sha256 +0 -0
  135. {capauth-0.3.10 → capauth-0.3.12}/docs/authentik-capauth.md +0 -0
  136. {capauth-0.3.10 → capauth-0.3.12}/docs/deploy-plan/capauth-bulletproof-deploy.md +0 -0
  137. {capauth-0.3.10 → capauth-0.3.12}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-MUTATION-ENVELOPE-2026-08-25.json +0 -0
  138. {capauth-0.3.10 → capauth-0.3.12}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-ROSTER-2026-08-25.json +0 -0
  139. {capauth-0.3.10 → capauth-0.3.12}/index.d.ts +0 -0
  140. {capauth-0.3.10 → capauth-0.3.12}/index.js +0 -0
  141. {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/package.json +0 -0
  142. {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/src/index.ts +0 -0
  143. {capauth-0.3.10 → capauth-0.3.12}/openclaw-plugin.archived-2026-04-23/src/openclaw.plugin.json +0 -0
  144. {capauth-0.3.10 → capauth-0.3.12}/package.json +0 -0
  145. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/icons/icon-192.png +0 -0
  146. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/icons/icon-512.png +0 -0
  147. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/bunker-e2e.js +0 -0
  148. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/bunker-signer.js +0 -0
  149. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/canonical.js +0 -0
  150. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/keyqr.js +0 -0
  151. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/lib/keyvault.js +0 -0
  152. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/manifest.webmanifest +0 -0
  153. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/sw.js +0 -0
  154. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/vendor/openpgp.min.js +0 -0
  155. {capauth-0.3.10 → capauth-0.3.12}/phone-signer/vendor/qrcode-generator.js +0 -0
  156. {capauth-0.3.10 → capauth-0.3.12}/pyproject.toml +0 -0
  157. {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
  158. {capauth-0.3.10 → capauth-0.3.12}/scripts/build_authentik_capauth_image.sh +0 -0
  159. {capauth-0.3.10 → capauth-0.3.12}/scripts/capauth-backup.sh +0 -0
  160. {capauth-0.3.10 → capauth-0.3.12}/scripts/capauth-restore.sh +0 -0
  161. {capauth-0.3.10 → capauth-0.3.12}/scripts/gitleaks-scope.sh +0 -0
  162. {capauth-0.3.10 → capauth-0.3.12}/scripts/migrate_canonical_subjects.py +0 -0
  163. {capauth-0.3.10 → capauth-0.3.12}/scripts/pqc_ceremony_dryrun.py +0 -0
  164. {capauth-0.3.10 → capauth-0.3.12}/scripts/provision_agent_profiles.py +0 -0
  165. {capauth-0.3.10 → capauth-0.3.12}/setup.cfg +0 -0
  166. {capauth-0.3.10 → capauth-0.3.12}/skill.yaml +0 -0
  167. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/__init__.py +0 -0
  168. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/agent_identity.py +0 -0
  169. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/apps.py +0 -0
  170. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/__init__.py +0 -0
  171. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/api.py +0 -0
  172. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/blueprints/capauth-pgp-login.yaml +0 -0
  173. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/claims_mapper.py +0 -0
  174. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/nonce_store.py +0 -0
  175. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/stage.py +0 -0
  176. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/urls.py +0 -0
  177. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authentik/verifier.py +0 -0
  178. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/authz.py +0 -0
  179. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/cli.py +0 -0
  180. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/control_plane.py +0 -0
  181. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/__init__.py +0 -0
  182. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/base.py +0 -0
  183. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/gnupg_backend.py +0 -0
  184. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/pgpy_backend.py +0 -0
  185. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/sequoia_backend.py +0 -0
  186. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/crypto/skpgp_backend.py +0 -0
  187. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/custody.py +0 -0
  188. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/delegated.py +0 -0
  189. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/did.py +0 -0
  190. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/__init__.py +0 -0
  191. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/base.py +0 -0
  192. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/file_discovery.py +0 -0
  193. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/mdns.py +0 -0
  194. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/discovery/syncthing.py +0 -0
  195. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/estate.py +0 -0
  196. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/exceptions.py +0 -0
  197. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/identity.py +0 -0
  198. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/identity_class.py +0 -0
  199. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integration.py +0 -0
  200. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/__init__.py +0 -0
  201. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/__init__.py +0 -0
  202. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
  203. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/cli.py +0 -0
  204. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/config.py +0 -0
  205. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/forgejo_api.py +0 -0
  206. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/oidc_provider.py +0 -0
  207. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
  208. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
  209. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
  210. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
  211. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
  212. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
  213. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/.gitignore +0 -0
  214. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/.nextcloudignore +0 -0
  215. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/CSR-PR.md +0 -0
  216. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/INFO-XML-CHANGES.md +0 -0
  217. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/PUBLISHING.md +0 -0
  218. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/README.md +0 -0
  219. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/appinfo/info.xml +0 -0
  220. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/appinfo/routes.php +0 -0
  221. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/build-release.sh +0 -0
  222. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/.gitignore +0 -0
  223. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/capauth.crt +0 -0
  224. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/certificates/capauth.csr +0 -0
  225. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/composer.json +0 -0
  226. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/css/login.css +0 -0
  227. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/js/login.js +0 -0
  228. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/krankerl.toml +0 -0
  229. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/AlternativeLogin/CapAuthLogin.php +0 -0
  230. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/AppInfo/Application.php +0 -0
  231. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/BackgroundJob/PruneExpiredNonces.php +0 -0
  232. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Controller/LoginController.php +0 -0
  233. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Controller/TokenController.php +0 -0
  234. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Db/KeyRegistry.php +0 -0
  235. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Exception/CapAuthUnauthorizedException.php +0 -0
  236. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Middleware/PgpVerificationMiddleware.php +0 -0
  237. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Migration/Version000300Date20260301000000.php +0 -0
  238. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Provider/CapAuthProvider.php +0 -0
  239. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/ChallengeService.php +0 -0
  240. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/GroupSyncService.php +0 -0
  241. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/UserProvisioningService.php +0 -0
  242. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/Service/VerifierService.php +0 -0
  243. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/lib/User/Backend.php +0 -0
  244. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/phpunit.xml +0 -0
  245. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/screenshots/README.md +0 -0
  246. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/screenshots/login.png +0 -0
  247. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/templates/login.php +0 -0
  248. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/bootstrap.php +0 -0
  249. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/integration/FullAuthFlowTest.php +0 -0
  250. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +0 -0
  251. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Controller/LoginControllerTest.php +0 -0
  252. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Db/KeyRegistryTest.php +0 -0
  253. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Provider/CapAuthProviderTest.php +0 -0
  254. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Service/ChallengeServiceTest.php +0 -0
  255. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/Service/VerifierServiceTest.php +0 -0
  256. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/integrations/nextcloud/tests/unit/User/BackendTest.php +0 -0
  257. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/key_transparency.py +0 -0
  258. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/login.py +0 -0
  259. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/manifest.py +0 -0
  260. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/mesh.py +0 -0
  261. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/0001_initial.py +0 -0
  262. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/0002_widen_fingerprint_to_64.py +0 -0
  263. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/migrations/__init__.py +0 -0
  264. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/models.py +0 -0
  265. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/__init__.py +0 -0
  266. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/canonicalize.py +0 -0
  267. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/kernel.py +0 -0
  268. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/operator_session.py +0 -0
  269. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/proof.py +0 -0
  270. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/records.py +0 -0
  271. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/store.py +0 -0
  272. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pairing/window.py +0 -0
  273. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pma.py +0 -0
  274. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_confidentiality.py +0 -0
  275. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_identity.py +0 -0
  276. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_root_identity.py +0 -0
  277. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/pqc_tokens.py +0 -0
  278. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/profile.py +0 -0
  279. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/provisioning.py +0 -0
  280. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/registry.py +0 -0
  281. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/seal.py +0 -0
  282. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/__init__.py +0 -0
  283. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/app.py +0 -0
  284. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/bunker.py +0 -0
  285. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/bunker_e2e.py +0 -0
  286. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/keystore.py +0 -0
  287. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/__init__.py +0 -0
  288. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/clients.example.json +0 -0
  289. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/clients.py +0 -0
  290. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/passkey.py +0 -0
  291. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/signing_key.py +0 -0
  292. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/lib/bunker-e2e.js +0 -0
  293. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/lib/keyvault.js +0 -0
  294. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/static/bunker/vendor/openpgp.min.js +0 -0
  295. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/oidc/store.py +0 -0
  296. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/push.py +0 -0
  297. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/service/server.py +0 -0
  298. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/subject.py +0 -0
  299. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/sync.py +0 -0
  300. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/testing.py +0 -0
  301. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/tokens.py +0 -0
  302. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/__init__.py +0 -0
  303. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/calibration.py +0 -0
  304. {capauth-0.3.10 → capauth-0.3.12}/src/capauth/trust/graph.py +0 -0
  305. {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/SOURCES.txt +0 -0
  306. {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/dependency_links.txt +0 -0
  307. {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/entry_points.txt +0 -0
  308. {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/requires.txt +0 -0
  309. {capauth-0.3.10 → capauth-0.3.12}/src/capauth.egg-info/top_level.txt +0 -0
  310. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/.gitignore +0 -0
  311. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/package-lock.json +0 -0
  312. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/package.json +0 -0
  313. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/CapAuthStage.test.js +0 -0
  314. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/CapAuthStage.vue +0 -0
  315. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/src/index.js +0 -0
  316. {capauth-0.3.10 → capauth-0.3.12}/stages/ak-stage-capauth/vite.config.js +0 -0
  317. {capauth-0.3.10 → capauth-0.3.12}/tests/__init__.py +0 -0
  318. {capauth-0.3.10 → capauth-0.3.12}/tests/conftest.py +0 -0
  319. {capauth-0.3.10 → capauth-0.3.12}/tests/fixtures/bunker_e2e_v1_vector.json +0 -0
  320. {capauth-0.3.10 → capauth-0.3.12}/tests/fixtures/canonical_nonce_v2_vector.json +0 -0
  321. {capauth-0.3.10 → capauth-0.3.12}/tests/test_agent_identity.py +0 -0
  322. {capauth-0.3.10 → capauth-0.3.12}/tests/test_agentrun_capabilities.py +0 -0
  323. {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_mint_helper.py +0 -0
  324. {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_tokens.py +0 -0
  325. {capauth-0.3.10 → capauth-0.3.12}/tests/test_audience_ttl_seconds.py +0 -0
  326. {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz.py +0 -0
  327. {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz_legacy_grace.py +0 -0
  328. {capauth-0.3.10 → capauth-0.3.12}/tests/test_authz_signature_gate.py +0 -0
  329. {capauth-0.3.10 → capauth-0.3.12}/tests/test_backup_restore.py +0 -0
  330. {capauth-0.3.10 → capauth-0.3.12}/tests/test_bunker.py +0 -0
  331. {capauth-0.3.10 → capauth-0.3.12}/tests/test_bunker_e2e.py +0 -0
  332. {capauth-0.3.10 → capauth-0.3.12}/tests/test_canonicalize.py +0 -0
  333. {capauth-0.3.10 → capauth-0.3.12}/tests/test_capauth_service_deploy.py +0 -0
  334. {capauth-0.3.10 → capauth-0.3.12}/tests/test_change_capabilities.py +0 -0
  335. {capauth-0.3.10 → capauth-0.3.12}/tests/test_claims_mapper.py +0 -0
  336. {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane.py +0 -0
  337. {capauth-0.3.10 → capauth-0.3.12}/tests/test_control_plane_capabilities.py +0 -0
  338. {capauth-0.3.10 → capauth-0.3.12}/tests/test_crypto.py +0 -0
  339. {capauth-0.3.10 → capauth-0.3.12}/tests/test_custody.py +0 -0
  340. {capauth-0.3.10 → capauth-0.3.12}/tests/test_delegated_capabilities.py +0 -0
  341. {capauth-0.3.10 → capauth-0.3.12}/tests/test_device_key_proof.py +0 -0
  342. {capauth-0.3.10 → capauth-0.3.12}/tests/test_did_capability_relationships.py +0 -0
  343. {capauth-0.3.10 → capauth-0.3.12}/tests/test_enrollment_proof_helper.py +0 -0
  344. {capauth-0.3.10 → capauth-0.3.12}/tests/test_estate.py +0 -0
  345. {capauth-0.3.10 → capauth-0.3.12}/tests/test_fingerprint_v6.py +0 -0
  346. {capauth-0.3.10 → capauth-0.3.12}/tests/test_forgejo_preflight.py +0 -0
  347. {capauth-0.3.10 → capauth-0.3.12}/tests/test_home_resolution.py +0 -0
  348. {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity.py +0 -0
  349. {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity_class.py +0 -0
  350. {capauth-0.3.10 → capauth-0.3.12}/tests/test_identity_ttl.py +0 -0
  351. {capauth-0.3.10 → capauth-0.3.12}/tests/test_installed_bunker_assets.py +0 -0
  352. {capauth-0.3.10 → capauth-0.3.12}/tests/test_integration.py +0 -0
  353. {capauth-0.3.10 → capauth-0.3.12}/tests/test_key_revocation_expiry.py +0 -0
  354. {capauth-0.3.10 → capauth-0.3.12}/tests/test_key_transparency.py +0 -0
  355. {capauth-0.3.10 → capauth-0.3.12}/tests/test_login.py +0 -0
  356. {capauth-0.3.10 → capauth-0.3.12}/tests/test_manifest.py +0 -0
  357. {capauth-0.3.10 → capauth-0.3.12}/tests/test_mesh.py +0 -0
  358. {capauth-0.3.10 → capauth-0.3.12}/tests/test_models_pqc_q0.py +0 -0
  359. {capauth-0.3.10 → capauth-0.3.12}/tests/test_nextcloud_app.py +0 -0
  360. {capauth-0.3.10 → capauth-0.3.12}/tests/test_no_subapp_dependency.py +0 -0
  361. {capauth-0.3.10 → capauth-0.3.12}/tests/test_nonce_store.py +0 -0
  362. {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_durable_store.py +0 -0
  363. {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_hardening.py +0 -0
  364. {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_idp_e2e.py +0 -0
  365. {capauth-0.3.10 → capauth-0.3.12}/tests/test_oidc_refresh_family.py +0 -0
  366. {capauth-0.3.10 → capauth-0.3.12}/tests/test_operator_session.py +0 -0
  367. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pairing.py +0 -0
  368. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pairing_base_dir_env.py +0 -0
  369. {capauth-0.3.10 → capauth-0.3.12}/tests/test_passkey.py +0 -0
  370. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pma.py +0 -0
  371. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_confidentiality.py +0 -0
  372. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_identity.py +0 -0
  373. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_root_integration.py +0 -0
  374. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_t3_gate.py +0 -0
  375. {capauth-0.3.10 → capauth-0.3.12}/tests/test_pqc_tokens.py +0 -0
  376. {capauth-0.3.10 → capauth-0.3.12}/tests/test_profile.py +0 -0
  377. {capauth-0.3.10 → capauth-0.3.12}/tests/test_provisioning.py +0 -0
  378. {capauth-0.3.10 → capauth-0.3.12}/tests/test_push.py +0 -0
  379. {capauth-0.3.10 → capauth-0.3.12}/tests/test_py313_compat.py +0 -0
  380. {capauth-0.3.10 → capauth-0.3.12}/tests/test_qr_login.py +0 -0
  381. {capauth-0.3.10 → capauth-0.3.12}/tests/test_real_pgp_e2e.py +0 -0
  382. {capauth-0.3.10 → capauth-0.3.12}/tests/test_register_cli.py +0 -0
  383. {capauth-0.3.10 → capauth-0.3.12}/tests/test_registry.py +0 -0
  384. {capauth-0.3.10 → capauth-0.3.12}/tests/test_seal.py +0 -0
  385. {capauth-0.3.10 → capauth-0.3.12}/tests/test_sequoia_backend.py +0 -0
  386. {capauth-0.3.10 → capauth-0.3.12}/tests/test_service.py +0 -0
  387. {capauth-0.3.10 → capauth-0.3.12}/tests/test_service_authz_decide.py +0 -0
  388. {capauth-0.3.10 → capauth-0.3.12}/tests/test_service_v6_fingerprint.py +0 -0
  389. {capauth-0.3.10 → capauth-0.3.12}/tests/test_skpgp_backend.py +0 -0
  390. {capauth-0.3.10 → capauth-0.3.12}/tests/test_subject.py +0 -0
  391. {capauth-0.3.10 → capauth-0.3.12}/tests/test_sync.py +0 -0
  392. {capauth-0.3.10 → capauth-0.3.12}/tests/test_syncthing_discovery.py +0 -0
  393. {capauth-0.3.10 → capauth-0.3.12}/tests/test_testing_helper.py +0 -0
  394. {capauth-0.3.10 → capauth-0.3.12}/tests/test_token_gc.py +0 -0
  395. {capauth-0.3.10 → capauth-0.3.12}/tests/test_token_store_flag.py +0 -0
  396. {capauth-0.3.10 → capauth-0.3.12}/tests/test_tokens.py +0 -0
  397. {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_calibration.py +0 -0
  398. {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_graph.py +0 -0
  399. {capauth-0.3.10 → capauth-0.3.12}/tests/test_trust_graph_coord_audit.py +0 -0
  400. {capauth-0.3.10 → capauth-0.3.12}/tests/test_verifier.py +0 -0
  401. {capauth-0.3.10 → capauth-0.3.12}/tests/test_version_honesty.py +0 -0
  402. {capauth-0.3.10 → capauth-0.3.12}/tests/test_zk_profile.py +0 -0
  403. {capauth-0.3.10 → capauth-0.3.12}/tools/build-sq.sh +0 -0
  404. {capauth-0.3.10 → capauth-0.3.12}/urllib.error +0 -0
  405. {capauth-0.3.10 → capauth-0.3.12}/web/README.md +0 -0
  406. {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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capauth
3
- Version: 0.3.10
3
+ Version: 0.3.12
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
@@ -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 forgetKey() {
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"), "Key forgotten.", "");
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("This is not an armored PGP private key file.");
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 armored PGP private key file</label>
111
- <input id="key-file" type="file" accept=".asc,.pgp,.gpg,text/plain" />
112
- <div class="hint">Example: <code>jarvis-private.asc</code>. The file is read only in this browser and is never uploaded.</div>
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
- first = self._owner_decision(binding, capauth)
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
- second = self._owner_decision(binding, capauth)
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=capauth,
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=8)
42
- MAX_SESSION_IDLE = timedelta(minutes=30)
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(pgpy.PGPMessage.from_blob(data).decrypt(old_passphrase).message)
270
+ bundle_plaintexts.append(
271
+ pgpy.PGPMessage.from_blob(data).decrypt(old_passphrase).message
272
+ )
273
273
  except Exception as exc:
274
- raise RotationError(f"old passphrase could not decrypt custody bundle: {bundle}") from exc
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 _validate_new_key(plan.private_key.read_bytes(), public_bytes, new_passphrase) != fingerprint:
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 not isinstance(journal, dict) or journal.get("schema") != "capauth-passphrase-rotation-rollback-v1":
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:8px;
181
- padding:.8rem;font-size:1rem;cursor:pointer;font-weight:600}}
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
- <button id="pk-btn" onclick="passkeyLogin()" style="background:#0e7490">Sign in with a passkey</button>
198
- <p class="sub" style="font-size:.74rem;margin:.4rem 0 1rem">Recommended after one PGP-proven enrollment. Passkey login then needs no fingerprint or pasted signature.
199
- <a href="{base_url}/oidc/passkey/enroll" style="color:#a78bfa">Add a passkey</a>
200
- </p>
201
-
202
- <div style="display:flex;align-items:center;gap:.6rem;margin:0 0 1rem;color:#475569;font-size:.74rem">
203
- <span style="flex:1;height:1px;background:#334155"></span>PGP FALLBACK<span style="flex:1;height:1px;background:#334155"></span>
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 &amp; 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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: capauth
3
- Version: 0.3.10
3
+ Version: 0.3.12
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
@@ -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
- result = _allow(rig, bearer=rig.bearer(ttl_seconds=60))
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.constraints == (
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, _capauth_decision):
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 ">Sign in with a passkey<" in resp.text
265
- assert "Recommended after one PGP-proven enrollment" in resp.text
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 "Sign on this device (key in your bunker)" in resp.text
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