capauth 0.3.12__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.
Files changed (412) hide show
  1. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/ci.yml +5 -0
  2. capauth-0.3.13/.github/workflows/image.yml +90 -0
  3. {capauth-0.3.12 → capauth-0.3.13}/CHANGELOG.md +31 -0
  4. {capauth-0.3.12 → capauth-0.3.13}/Dockerfile +4 -0
  5. {capauth-0.3.12/src/capauth.egg-info → capauth-0.3.13}/PKG-INFO +3 -1
  6. {capauth-0.3.12 → capauth-0.3.13}/README.md +2 -0
  7. {capauth-0.3.12 → capauth-0.3.13}/docs/CAPAUTH_OIDC_IDP.md +8 -1
  8. capauth-0.3.13/docs/SIGN_CHALLENGE.md +80 -0
  9. {capauth-0.3.12 → capauth-0.3.13}/scripts/capauth-backup.sh +10 -2
  10. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/cli.py +41 -0
  11. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/clients.py +9 -1
  12. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/provider.py +39 -11
  13. capauth-0.3.13/src/capauth/sign_challenge.py +192 -0
  14. {capauth-0.3.12 → capauth-0.3.13/src/capauth.egg-info}/PKG-INFO +3 -1
  15. {capauth-0.3.12 → capauth-0.3.13}/src/capauth.egg-info/SOURCES.txt +7 -0
  16. capauth-0.3.13/src/capauth.egg-info/scm_file_list.json +408 -0
  17. capauth-0.3.13/tests/test_backup_fallback.py +66 -0
  18. capauth-0.3.13/tests/test_dockerfile_version.py +17 -0
  19. {capauth-0.3.12 → capauth-0.3.13}/tests/test_integration.py +4 -1
  20. {capauth-0.3.12 → capauth-0.3.13}/tests/test_oidc_hardening.py +92 -0
  21. {capauth-0.3.12 → capauth-0.3.13}/tests/test_oidc_idp.py +30 -0
  22. capauth-0.3.13/tests/test_sign_challenge.py +229 -0
  23. {capauth-0.3.12 → capauth-0.3.13}/urllib.error +1 -1
  24. {capauth-0.3.12 → capauth-0.3.13}/.cursorrules +0 -0
  25. {capauth-0.3.12 → capauth-0.3.13}/.dockerignore +0 -0
  26. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/docs-check.yml +0 -0
  27. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/nextcloud-appstore.yml +0 -0
  28. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/publish.yml +0 -0
  29. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/pytest.yml +0 -0
  30. {capauth-0.3.12 → capauth-0.3.13}/.github/workflows/secret-scan.yml +0 -0
  31. {capauth-0.3.12 → capauth-0.3.13}/.gitignore +0 -0
  32. {capauth-0.3.12 → capauth-0.3.13}/.gitleaks-baseline.json +0 -0
  33. {capauth-0.3.12 → capauth-0.3.13}/.gitleaks.toml +0 -0
  34. {capauth-0.3.12 → capauth-0.3.13}/AGENTS.md +0 -0
  35. {capauth-0.3.12 → capauth-0.3.13}/AI-ADVOCATE.md +0 -0
  36. {capauth-0.3.12 → capauth-0.3.13}/ARCHITECTURE.md +0 -0
  37. {capauth-0.3.12 → capauth-0.3.13}/CLAUDE.md +0 -0
  38. {capauth-0.3.12 → capauth-0.3.13}/CODE_OF_CONDUCT.md +0 -0
  39. {capauth-0.3.12 → capauth-0.3.13}/CONTRIBUTING.md +0 -0
  40. {capauth-0.3.12 → capauth-0.3.13}/Dockerfile.authentik-capauth +0 -0
  41. {capauth-0.3.12 → capauth-0.3.13}/LICENSE +0 -0
  42. {capauth-0.3.12 → capauth-0.3.13}/MANIFEST.in +0 -0
  43. {capauth-0.3.12 → capauth-0.3.13}/MISSION.md +0 -0
  44. {capauth-0.3.12 → capauth-0.3.13}/SECURITY.md +0 -0
  45. {capauth-0.3.12 → capauth-0.3.13}/SKILL.md +0 -0
  46. {capauth-0.3.12 → capauth-0.3.13}/SOP.md +0 -0
  47. {capauth-0.3.12 → capauth-0.3.13}/authentik-custom/capauth_migrate.py +0 -0
  48. {capauth-0.3.12 → capauth-0.3.13}/authentik-custom/imghdr.py +0 -0
  49. {capauth-0.3.12 → capauth-0.3.13}/authentik-custom/user_settings.py +0 -0
  50. {capauth-0.3.12 → capauth-0.3.13}/bin/cli.js +0 -0
  51. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/.gitignore +0 -0
  52. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/README.md +0 -0
  53. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/RELEASING.md +0 -0
  54. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/background.js +0 -0
  55. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/content_scripts/autofill.js +0 -0
  56. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/content_scripts/cross_origin_signing.js +0 -0
  57. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/content_scripts/detector.js +0 -0
  58. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/content_scripts/provider.js +0 -0
  59. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/content_scripts/provider_bridge.js +0 -0
  60. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/icons/icon.svg +0 -0
  61. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/icons/icon128.png +0 -0
  62. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/icons/icon16.png +0 -0
  63. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/icons/icon48.png +0 -0
  64. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/lib/bunker-e2e.js +0 -0
  65. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/lib/keyqr.js +0 -0
  66. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/lib/keyvault.js +0 -0
  67. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/lib/openpgp.js +0 -0
  68. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/lib/signer-backends.js +0 -0
  69. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/manifest.firefox.json +0 -0
  70. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/manifest.json +0 -0
  71. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/native-host/capauth_signer.py +0 -0
  72. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/native-host/com.capauth.signer.json +0 -0
  73. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/native-host/install.sh +0 -0
  74. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/options/options.html +0 -0
  75. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/options/options.js +0 -0
  76. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/package-lock.json +0 -0
  77. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/package.json +0 -0
  78. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/popup/popup.css +0 -0
  79. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/popup/popup.html +0 -0
  80. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/popup/popup.js +0 -0
  81. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/scripts/build.js +0 -0
  82. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/scripts/generate-icons.js +0 -0
  83. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/stages/AuthenticatorSelectionStage.js +0 -0
  84. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/stages/CapAuthStage.js +0 -0
  85. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/stages/QRDisplayStage.js +0 -0
  86. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/stages/SignatureVerificationStage.js +0 -0
  87. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/stages/StagePipeline.js +0 -0
  88. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/submission/STORE_LISTING.md +0 -0
  89. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/submission/privacy-policy.md +0 -0
  90. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/fixtures/test_key.json +0 -0
  91. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/background.test.js +0 -0
  92. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/bunker_e2e.test.js +0 -0
  93. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/detector.test.js +0 -0
  94. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/keyqr.test.js +0 -0
  95. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/keyvault.test.js +0 -0
  96. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/openpgp.test.js +0 -0
  97. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/provider_api.test.js +0 -0
  98. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/provider_tierb.test.js +0 -0
  99. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/remote_bunker.test.js +0 -0
  100. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/tests/unit/signer_backends.test.js +0 -0
  101. {capauth-0.3.12 → capauth-0.3.13}/browser-extension/vitest.config.js +0 -0
  102. {capauth-0.3.12 → capauth-0.3.13}/deploy/capauth-service/.env.example +0 -0
  103. {capauth-0.3.12 → capauth-0.3.13}/deploy/capauth-service/deploy.sh +0 -0
  104. {capauth-0.3.12 → capauth-0.3.13}/deploy/capauth-service/docker-compose.yml +0 -0
  105. {capauth-0.3.12 → capauth-0.3.13}/deploy/capauth-service/systemd/capauth-backup.service +0 -0
  106. {capauth-0.3.12 → capauth-0.3.13}/deploy/capauth-service/systemd/capauth-backup.timer +0 -0
  107. {capauth-0.3.12 → capauth-0.3.13}/deploy/forgejo-capauth/.env.example +0 -0
  108. {capauth-0.3.12 → capauth-0.3.13}/deploy/forgejo-capauth/docker-compose.yml +0 -0
  109. {capauth-0.3.12 → capauth-0.3.13}/deploy/forgejo-capauth/preflight.sh +0 -0
  110. {capauth-0.3.12 → capauth-0.3.13}/docs/AIRGAPPED_SIGNING_WORKSTATION.md +0 -0
  111. {capauth-0.3.12 → capauth-0.3.13}/docs/ARCHITECTURE.md +0 -0
  112. {capauth-0.3.12 → capauth-0.3.13}/docs/AUTHENTIK_CUSTOM_STAGE.md +0 -0
  113. {capauth-0.3.12 → capauth-0.3.13}/docs/AUTHENTIK_DEPLOYMENT_SKSSO.md +0 -0
  114. {capauth-0.3.12 → capauth-0.3.13}/docs/AUTHENTIK_FORGEJO_DEPLOYMENT.md +0 -0
  115. {capauth-0.3.12 → capauth-0.3.13}/docs/AUTHENTIK_STRATEGY_DECISION.md +0 -0
  116. {capauth-0.3.12 → capauth-0.3.13}/docs/CAPAUTH-WEBAUTHN-05-QUALIFICATION.md +0 -0
  117. {capauth-0.3.12 → capauth-0.3.13}/docs/CAPAUTH_BUNKER_REMOTE_SIGNER.md +0 -0
  118. {capauth-0.3.12 → capauth-0.3.13}/docs/CLAIMS.md +0 -0
  119. {capauth-0.3.12 → capauth-0.3.13}/docs/COLD_MACHINE_BOOTSTRAP_AND_DR.md +0 -0
  120. {capauth-0.3.12 → capauth-0.3.13}/docs/CRYPTO_SPEC.md +0 -0
  121. {capauth-0.3.12 → capauth-0.3.13}/docs/DELEGATED_CAPABILITIES.md +0 -0
  122. {capauth-0.3.12 → capauth-0.3.13}/docs/DOWNSTREAM_DURABLE_BACKENDS.md +0 -0
  123. {capauth-0.3.12 → capauth-0.3.13}/docs/ENTERPRISE_MANAGED_KEYS.md +0 -0
  124. {capauth-0.3.12 → capauth-0.3.13}/docs/HUMAN_IDENTITY_SETUP_AND_ROTATION.md +0 -0
  125. {capauth-0.3.12 → capauth-0.3.13}/docs/IDENTITY_ESTATE.md +0 -0
  126. {capauth-0.3.12 → capauth-0.3.13}/docs/INTEGRATION_BLUEPRINT.md +0 -0
  127. {capauth-0.3.12 → capauth-0.3.13}/docs/M0-capauth-api-v1.md +0 -0
  128. {capauth-0.3.12 → capauth-0.3.13}/docs/M0-capauth-importer-inventory.md +0 -0
  129. {capauth-0.3.12 → capauth-0.3.13}/docs/PASSPHRASE_ROTATION.md +0 -0
  130. {capauth-0.3.12 → capauth-0.3.13}/docs/PQC_CAPAUTH_REVISIT.md +0 -0
  131. {capauth-0.3.12 → capauth-0.3.13}/docs/PQC_ROOT_MIGRATION.md +0 -0
  132. {capauth-0.3.12 → capauth-0.3.13}/docs/PROTOCOL.md +0 -0
  133. {capauth-0.3.12 → capauth-0.3.13}/docs/ROOT_ROTATION_CEREMONY.md +0 -0
  134. {capauth-0.3.12 → capauth-0.3.13}/docs/SKDASHBOARD_CONTROL_PLANE_POLICY.md +0 -0
  135. {capauth-0.3.12 → capauth-0.3.13}/docs/TRUST_GRAPH_COORD_PROJECTION.md +0 -0
  136. {capauth-0.3.12 → capauth-0.3.13}/docs/ZERO_KNOWLEDGE_PROFILE.md +0 -0
  137. {capauth-0.3.12 → capauth-0.3.13}/docs/audits/BLAST-RADIUS-CAPAUTH-01.md +0 -0
  138. {capauth-0.3.12 → capauth-0.3.13}/docs/audits/BLAST-RADIUS-CAPAUTH-01.sha256 +0 -0
  139. {capauth-0.3.12 → capauth-0.3.13}/docs/authentik-capauth.md +0 -0
  140. {capauth-0.3.12 → capauth-0.3.13}/docs/deploy-plan/capauth-bulletproof-deploy.md +0 -0
  141. {capauth-0.3.12 → capauth-0.3.13}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-MUTATION-ENVELOPE-2026-08-25.json +0 -0
  142. {capauth-0.3.12 → capauth-0.3.13}/docs/evidence/capauth/SKL-CAPAUTH-SIGNER-P3P-ROSTER-2026-08-25.json +0 -0
  143. {capauth-0.3.12 → capauth-0.3.13}/index.d.ts +0 -0
  144. {capauth-0.3.12 → capauth-0.3.13}/index.js +0 -0
  145. {capauth-0.3.12 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/package.json +0 -0
  146. {capauth-0.3.12 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/src/index.ts +0 -0
  147. {capauth-0.3.12 → capauth-0.3.13}/openclaw-plugin.archived-2026-04-23/src/openclaw.plugin.json +0 -0
  148. {capauth-0.3.12 → capauth-0.3.13}/package.json +0 -0
  149. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/app.js +0 -0
  150. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/icons/icon-192.png +0 -0
  151. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/icons/icon-512.png +0 -0
  152. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/index.html +0 -0
  153. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/lib/bunker-e2e.js +0 -0
  154. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/lib/bunker-signer.js +0 -0
  155. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/lib/canonical.js +0 -0
  156. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/lib/keyqr.js +0 -0
  157. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/lib/keyvault.js +0 -0
  158. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/manifest.webmanifest +0 -0
  159. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/sw.js +0 -0
  160. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/vendor/openpgp.min.js +0 -0
  161. {capauth-0.3.12 → capauth-0.3.13}/phone-signer/vendor/qrcode-generator.js +0 -0
  162. {capauth-0.3.12 → capauth-0.3.13}/pyproject.toml +0 -0
  163. {capauth-0.3.12 → capauth-0.3.13}/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +0 -0
  164. {capauth-0.3.12 → capauth-0.3.13}/scripts/build_authentik_capauth_image.sh +0 -0
  165. {capauth-0.3.12 → capauth-0.3.13}/scripts/capauth-restore.sh +0 -0
  166. {capauth-0.3.12 → capauth-0.3.13}/scripts/gitleaks-scope.sh +0 -0
  167. {capauth-0.3.12 → capauth-0.3.13}/scripts/migrate_canonical_subjects.py +0 -0
  168. {capauth-0.3.12 → capauth-0.3.13}/scripts/pqc_ceremony_dryrun.py +0 -0
  169. {capauth-0.3.12 → capauth-0.3.13}/scripts/provision_agent_profiles.py +0 -0
  170. {capauth-0.3.12 → capauth-0.3.13}/setup.cfg +0 -0
  171. {capauth-0.3.12 → capauth-0.3.13}/skill.yaml +0 -0
  172. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/__init__.py +0 -0
  173. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/agent_identity.py +0 -0
  174. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/apps.py +0 -0
  175. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/__init__.py +0 -0
  176. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/api.py +0 -0
  177. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/blueprints/capauth-pgp-login.yaml +0 -0
  178. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/claims_mapper.py +0 -0
  179. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/nonce_store.py +0 -0
  180. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/stage.py +0 -0
  181. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/urls.py +0 -0
  182. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authentik/verifier.py +0 -0
  183. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/authz.py +0 -0
  184. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/control_plane.py +0 -0
  185. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/control_plane_authorizer.py +0 -0
  186. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/control_plane_session.py +0 -0
  187. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/__init__.py +0 -0
  188. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/base.py +0 -0
  189. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/gnupg_backend.py +0 -0
  190. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/pgpy_backend.py +0 -0
  191. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/sequoia_backend.py +0 -0
  192. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/crypto/skpgp_backend.py +0 -0
  193. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/custody.py +0 -0
  194. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/delegated.py +0 -0
  195. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/did.py +0 -0
  196. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/discovery/__init__.py +0 -0
  197. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/discovery/base.py +0 -0
  198. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/discovery/file_discovery.py +0 -0
  199. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/discovery/mdns.py +0 -0
  200. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/discovery/syncthing.py +0 -0
  201. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/estate.py +0 -0
  202. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/exceptions.py +0 -0
  203. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/identity.py +0 -0
  204. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/identity_class.py +0 -0
  205. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integration.py +0 -0
  206. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/__init__.py +0 -0
  207. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/__init__.py +0 -0
  208. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/auth_flow.py +0 -0
  209. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/cli.py +0 -0
  210. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/config.py +0 -0
  211. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/forgejo_api.py +0 -0
  212. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/oidc_provider.py +0 -0
  213. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/__init__.py +0 -0
  214. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_auth_flow.py +0 -0
  215. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_cli.py +0 -0
  216. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_config.py +0 -0
  217. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_forgejo_api.py +0 -0
  218. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/forgejo/tests/test_oidc_provider.py +0 -0
  219. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/.gitignore +0 -0
  220. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/.nextcloudignore +0 -0
  221. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/CSR-PR.md +0 -0
  222. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/INFO-XML-CHANGES.md +0 -0
  223. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/PUBLISHING.md +0 -0
  224. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/README.md +0 -0
  225. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/appinfo/info.xml +0 -0
  226. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/appinfo/routes.php +0 -0
  227. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/build-release.sh +0 -0
  228. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/.gitignore +0 -0
  229. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/capauth.crt +0 -0
  230. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/certificates/capauth.csr +0 -0
  231. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/composer.json +0 -0
  232. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/css/login.css +0 -0
  233. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/js/login.js +0 -0
  234. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/krankerl.toml +0 -0
  235. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/AlternativeLogin/CapAuthLogin.php +0 -0
  236. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/AppInfo/Application.php +0 -0
  237. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/BackgroundJob/PruneExpiredNonces.php +0 -0
  238. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Controller/LoginController.php +0 -0
  239. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Controller/TokenController.php +0 -0
  240. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Db/KeyRegistry.php +0 -0
  241. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Exception/CapAuthUnauthorizedException.php +0 -0
  242. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Middleware/PgpVerificationMiddleware.php +0 -0
  243. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Migration/Version000300Date20260301000000.php +0 -0
  244. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Provider/CapAuthProvider.php +0 -0
  245. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/ChallengeService.php +0 -0
  246. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/GroupSyncService.php +0 -0
  247. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/UserProvisioningService.php +0 -0
  248. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/Service/VerifierService.php +0 -0
  249. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/lib/User/Backend.php +0 -0
  250. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/phpunit.xml +0 -0
  251. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/screenshots/README.md +0 -0
  252. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/screenshots/login.png +0 -0
  253. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/templates/login.php +0 -0
  254. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/bootstrap.php +0 -0
  255. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/integration/FullAuthFlowTest.php +0 -0
  256. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/oidc/oidc_e2e_login_test.py +0 -0
  257. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Controller/LoginControllerTest.php +0 -0
  258. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Db/KeyRegistryTest.php +0 -0
  259. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Provider/CapAuthProviderTest.php +0 -0
  260. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Service/ChallengeServiceTest.php +0 -0
  261. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/Service/VerifierServiceTest.php +0 -0
  262. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/integrations/nextcloud/tests/unit/User/BackendTest.php +0 -0
  263. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/key_transparency.py +0 -0
  264. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/login.py +0 -0
  265. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/manifest.py +0 -0
  266. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/mesh.py +0 -0
  267. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/migrations/0001_initial.py +0 -0
  268. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/migrations/0002_widen_fingerprint_to_64.py +0 -0
  269. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/migrations/__init__.py +0 -0
  270. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/models.py +0 -0
  271. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/__init__.py +0 -0
  272. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/canonicalize.py +0 -0
  273. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/kernel.py +0 -0
  274. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/operator_session.py +0 -0
  275. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/proof.py +0 -0
  276. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/records.py +0 -0
  277. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/store.py +0 -0
  278. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pairing/window.py +0 -0
  279. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pma.py +0 -0
  280. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pqc_confidentiality.py +0 -0
  281. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pqc_identity.py +0 -0
  282. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pqc_root_identity.py +0 -0
  283. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/pqc_tokens.py +0 -0
  284. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/profile.py +0 -0
  285. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/provisioning.py +0 -0
  286. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/registry.py +0 -0
  287. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/rotation.py +0 -0
  288. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/seal.py +0 -0
  289. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/__init__.py +0 -0
  290. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/app.py +0 -0
  291. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/bunker.py +0 -0
  292. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/bunker_e2e.py +0 -0
  293. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/keystore.py +0 -0
  294. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/__init__.py +0 -0
  295. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/clients.example.json +0 -0
  296. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/passkey.py +0 -0
  297. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/signing_key.py +0 -0
  298. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/lib/bunker-e2e.js +0 -0
  299. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/lib/keyvault.js +0 -0
  300. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/static/bunker/vendor/openpgp.min.js +0 -0
  301. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/oidc/store.py +0 -0
  302. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/push.py +0 -0
  303. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/service/server.py +0 -0
  304. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/subject.py +0 -0
  305. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/sync.py +0 -0
  306. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/testing.py +0 -0
  307. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/tokens.py +0 -0
  308. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/trust/__init__.py +0 -0
  309. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/trust/calibration.py +0 -0
  310. {capauth-0.3.12 → capauth-0.3.13}/src/capauth/trust/graph.py +0 -0
  311. {capauth-0.3.12 → capauth-0.3.13}/src/capauth.egg-info/dependency_links.txt +0 -0
  312. {capauth-0.3.12 → capauth-0.3.13}/src/capauth.egg-info/entry_points.txt +0 -0
  313. {capauth-0.3.12 → capauth-0.3.13}/src/capauth.egg-info/requires.txt +0 -0
  314. {capauth-0.3.12 → capauth-0.3.13}/src/capauth.egg-info/top_level.txt +0 -0
  315. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/.gitignore +0 -0
  316. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/package-lock.json +0 -0
  317. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/package.json +0 -0
  318. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/src/CapAuthStage.test.js +0 -0
  319. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/src/CapAuthStage.vue +0 -0
  320. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/src/index.js +0 -0
  321. {capauth-0.3.12 → capauth-0.3.13}/stages/ak-stage-capauth/vite.config.js +0 -0
  322. {capauth-0.3.12 → capauth-0.3.13}/tests/__init__.py +0 -0
  323. {capauth-0.3.12 → capauth-0.3.13}/tests/conftest.py +0 -0
  324. {capauth-0.3.12 → capauth-0.3.13}/tests/fixtures/bunker_e2e_v1_vector.json +0 -0
  325. {capauth-0.3.12 → capauth-0.3.13}/tests/fixtures/canonical_nonce_v2_vector.json +0 -0
  326. {capauth-0.3.12 → capauth-0.3.13}/tests/test_agent_identity.py +0 -0
  327. {capauth-0.3.12 → capauth-0.3.13}/tests/test_agentrun_capabilities.py +0 -0
  328. {capauth-0.3.12 → capauth-0.3.13}/tests/test_audience_mint_helper.py +0 -0
  329. {capauth-0.3.12 → capauth-0.3.13}/tests/test_audience_tokens.py +0 -0
  330. {capauth-0.3.12 → capauth-0.3.13}/tests/test_audience_ttl_seconds.py +0 -0
  331. {capauth-0.3.12 → capauth-0.3.13}/tests/test_authz.py +0 -0
  332. {capauth-0.3.12 → capauth-0.3.13}/tests/test_authz_legacy_grace.py +0 -0
  333. {capauth-0.3.12 → capauth-0.3.13}/tests/test_authz_signature_gate.py +0 -0
  334. {capauth-0.3.12 → capauth-0.3.13}/tests/test_backup_restore.py +0 -0
  335. {capauth-0.3.12 → capauth-0.3.13}/tests/test_bunker.py +0 -0
  336. {capauth-0.3.12 → capauth-0.3.13}/tests/test_bunker_e2e.py +0 -0
  337. {capauth-0.3.12 → capauth-0.3.13}/tests/test_canonicalize.py +0 -0
  338. {capauth-0.3.12 → capauth-0.3.13}/tests/test_capauth_service_deploy.py +0 -0
  339. {capauth-0.3.12 → capauth-0.3.13}/tests/test_change_capabilities.py +0 -0
  340. {capauth-0.3.12 → capauth-0.3.13}/tests/test_claims_mapper.py +0 -0
  341. {capauth-0.3.12 → capauth-0.3.13}/tests/test_control_plane.py +0 -0
  342. {capauth-0.3.12 → capauth-0.3.13}/tests/test_control_plane_authorizer.py +0 -0
  343. {capauth-0.3.12 → capauth-0.3.13}/tests/test_control_plane_capabilities.py +0 -0
  344. {capauth-0.3.12 → capauth-0.3.13}/tests/test_control_plane_session.py +0 -0
  345. {capauth-0.3.12 → capauth-0.3.13}/tests/test_crypto.py +0 -0
  346. {capauth-0.3.12 → capauth-0.3.13}/tests/test_custody.py +0 -0
  347. {capauth-0.3.12 → capauth-0.3.13}/tests/test_delegated_capabilities.py +0 -0
  348. {capauth-0.3.12 → capauth-0.3.13}/tests/test_device_key_proof.py +0 -0
  349. {capauth-0.3.12 → capauth-0.3.13}/tests/test_did_capability_relationships.py +0 -0
  350. {capauth-0.3.12 → capauth-0.3.13}/tests/test_enrollment_proof_helper.py +0 -0
  351. {capauth-0.3.12 → capauth-0.3.13}/tests/test_estate.py +0 -0
  352. {capauth-0.3.12 → capauth-0.3.13}/tests/test_fingerprint_v6.py +0 -0
  353. {capauth-0.3.12 → capauth-0.3.13}/tests/test_forgejo_preflight.py +0 -0
  354. {capauth-0.3.12 → capauth-0.3.13}/tests/test_home_resolution.py +0 -0
  355. {capauth-0.3.12 → capauth-0.3.13}/tests/test_identity.py +0 -0
  356. {capauth-0.3.12 → capauth-0.3.13}/tests/test_identity_class.py +0 -0
  357. {capauth-0.3.12 → capauth-0.3.13}/tests/test_identity_ttl.py +0 -0
  358. {capauth-0.3.12 → capauth-0.3.13}/tests/test_installed_bunker_assets.py +0 -0
  359. {capauth-0.3.12 → capauth-0.3.13}/tests/test_key_revocation_expiry.py +0 -0
  360. {capauth-0.3.12 → capauth-0.3.13}/tests/test_key_transparency.py +0 -0
  361. {capauth-0.3.12 → capauth-0.3.13}/tests/test_login.py +0 -0
  362. {capauth-0.3.12 → capauth-0.3.13}/tests/test_manifest.py +0 -0
  363. {capauth-0.3.12 → capauth-0.3.13}/tests/test_mesh.py +0 -0
  364. {capauth-0.3.12 → capauth-0.3.13}/tests/test_models_pqc_q0.py +0 -0
  365. {capauth-0.3.12 → capauth-0.3.13}/tests/test_nextcloud_app.py +0 -0
  366. {capauth-0.3.12 → capauth-0.3.13}/tests/test_no_subapp_dependency.py +0 -0
  367. {capauth-0.3.12 → capauth-0.3.13}/tests/test_nonce_store.py +0 -0
  368. {capauth-0.3.12 → capauth-0.3.13}/tests/test_oidc_durable_store.py +0 -0
  369. {capauth-0.3.12 → capauth-0.3.13}/tests/test_oidc_idp_e2e.py +0 -0
  370. {capauth-0.3.12 → capauth-0.3.13}/tests/test_oidc_refresh_family.py +0 -0
  371. {capauth-0.3.12 → capauth-0.3.13}/tests/test_operator_session.py +0 -0
  372. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pairing.py +0 -0
  373. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pairing_base_dir_env.py +0 -0
  374. {capauth-0.3.12 → capauth-0.3.13}/tests/test_passkey.py +0 -0
  375. {capauth-0.3.12 → capauth-0.3.13}/tests/test_passkey_enrollment_ux.py +0 -0
  376. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pma.py +0 -0
  377. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pqc_confidentiality.py +0 -0
  378. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pqc_identity.py +0 -0
  379. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pqc_root_integration.py +0 -0
  380. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pqc_t3_gate.py +0 -0
  381. {capauth-0.3.12 → capauth-0.3.13}/tests/test_pqc_tokens.py +0 -0
  382. {capauth-0.3.12 → capauth-0.3.13}/tests/test_profile.py +0 -0
  383. {capauth-0.3.12 → capauth-0.3.13}/tests/test_provisioning.py +0 -0
  384. {capauth-0.3.12 → capauth-0.3.13}/tests/test_push.py +0 -0
  385. {capauth-0.3.12 → capauth-0.3.13}/tests/test_py313_compat.py +0 -0
  386. {capauth-0.3.12 → capauth-0.3.13}/tests/test_qr_login.py +0 -0
  387. {capauth-0.3.12 → capauth-0.3.13}/tests/test_real_pgp_e2e.py +0 -0
  388. {capauth-0.3.12 → capauth-0.3.13}/tests/test_register_cli.py +0 -0
  389. {capauth-0.3.12 → capauth-0.3.13}/tests/test_registry.py +0 -0
  390. {capauth-0.3.12 → capauth-0.3.13}/tests/test_rotation.py +0 -0
  391. {capauth-0.3.12 → capauth-0.3.13}/tests/test_seal.py +0 -0
  392. {capauth-0.3.12 → capauth-0.3.13}/tests/test_sequoia_backend.py +0 -0
  393. {capauth-0.3.12 → capauth-0.3.13}/tests/test_service.py +0 -0
  394. {capauth-0.3.12 → capauth-0.3.13}/tests/test_service_authz_decide.py +0 -0
  395. {capauth-0.3.12 → capauth-0.3.13}/tests/test_service_v6_fingerprint.py +0 -0
  396. {capauth-0.3.12 → capauth-0.3.13}/tests/test_skpgp_backend.py +0 -0
  397. {capauth-0.3.12 → capauth-0.3.13}/tests/test_subject.py +0 -0
  398. {capauth-0.3.12 → capauth-0.3.13}/tests/test_sync.py +0 -0
  399. {capauth-0.3.12 → capauth-0.3.13}/tests/test_syncthing_discovery.py +0 -0
  400. {capauth-0.3.12 → capauth-0.3.13}/tests/test_testing_helper.py +0 -0
  401. {capauth-0.3.12 → capauth-0.3.13}/tests/test_token_gc.py +0 -0
  402. {capauth-0.3.12 → capauth-0.3.13}/tests/test_token_store_flag.py +0 -0
  403. {capauth-0.3.12 → capauth-0.3.13}/tests/test_tokens.py +0 -0
  404. {capauth-0.3.12 → capauth-0.3.13}/tests/test_trust_calibration.py +0 -0
  405. {capauth-0.3.12 → capauth-0.3.13}/tests/test_trust_graph.py +0 -0
  406. {capauth-0.3.12 → capauth-0.3.13}/tests/test_trust_graph_coord_audit.py +0 -0
  407. {capauth-0.3.12 → capauth-0.3.13}/tests/test_verifier.py +0 -0
  408. {capauth-0.3.12 → capauth-0.3.13}/tests/test_version_honesty.py +0 -0
  409. {capauth-0.3.12 → capauth-0.3.13}/tests/test_zk_profile.py +0 -0
  410. {capauth-0.3.12 → capauth-0.3.13}/tools/build-sq.sh +0 -0
  411. {capauth-0.3.12 → capauth-0.3.13}/web/README.md +0 -0
  412. {capauth-0.3.12 → 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,37 @@ 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
+
9
40
  - Raise operator proof absolute and idle ceilings to 24 hours while keeping
10
41
  access capabilities bounded to five minutes (card `4af0ae7f`).
11
42
 
@@ -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.12
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.
@@ -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
- warn "sqlite3 not found; falling back to cp (best-effort consistency)"
156
- cp -p "$DB_PATH" "$dst"
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",
@@ -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
  # ------------------------------------------------------------------
@@ -162,7 +162,7 @@ _LOGIN_PAGE = """<!DOCTYPE html>
162
162
  <head>
163
163
  <meta charset="UTF-8"/>
164
164
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
165
- <title>CapAuth — Sign in with PGP</title>
165
+ <title>CapAuth: Sign in with PGP</title>
166
166
  <style>
167
167
  *{{box-sizing:border-box;margin:0;padding:0}}
168
168
  body{{font-family:'Segoe UI',system-ui,sans-serif;background:#0f0f1a;color:#e2e8f0;
@@ -212,7 +212,7 @@ _LOGIN_PAGE = """<!DOCTYPE html>
212
212
  <details id="manual-pgp">
213
213
  <summary>Other sign-in and recovery options</summary>
214
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>
215
- <div class="step">1 — Your PGP fingerprint</div>
215
+ <div class="step">1 - Your PGP fingerprint (remembered after the first login)</div>
216
216
  <label for="fp">Fingerprint (40- or 64-hex chars)</label>
217
217
  <input id="fp" type="text" maxlength="64" placeholder="ABCDEF0123..." autocomplete="off"/>
218
218
 
@@ -220,9 +220,13 @@ _LOGIN_PAGE = """<!DOCTYPE html>
220
220
  <div class="nonce-box" id="nonce">Enter your fingerprint to load the complete signed payload.</div>
221
221
  <button class="copy" type="button" onclick="copyPayload()">Copy message</button>
222
222
 
223
- <div class="step">3 - Paste your signed message or detached signature</div>
224
- <label for="sig">Signed message / signature (ASCII armor)</label>
225
- <textarea id="sig" placeholder="-----BEGIN PGP MESSAGE-----&#10;...&#10;-----END PGP MESSAGE-----"></textarea>
223
+ <div class="step">3 - Sign it: run <code>capauth sign-challenge</code> on your computer</div>
224
+ <p class="sub" style="font-size:.74rem;margin:.2rem 0 .8rem">It signs the copied message with your key (gpg asks for your passphrase in its own window) and puts the signature on the clipboard.</p>
225
+
226
+ <div class="step">4 - Paste the signature</div>
227
+ <button class="copy" type="button" onclick="pasteAndSubmit()">Paste signature and continue</button>
228
+ <label for="sig">or paste it here yourself (ASCII armor)</label>
229
+ <textarea id="sig" placeholder="-----BEGIN PGP SIGNATURE-----&#10;...&#10;-----END PGP SIGNATURE-----"></textarea>
226
230
 
227
231
  <p class="sub">This fingerprint must already have an approved CapAuth enrollment.</p>
228
232
 
@@ -236,7 +240,7 @@ _LOGIN_PAGE = """<!DOCTYPE html>
236
240
  </div>
237
241
 
238
242
  <p style="margin-top:1rem;font-size:.74rem;color:#475569">
239
- Manual GPG: copy the complete message, then use <code>gpg --armor --sign</code> for an inline signed message
243
+ Without the capauth tool: copy the complete message, then use <code>gpg --armor --sign</code> for an inline signed message
240
244
  or <code>gpg --armor --detach-sign</code> for a detached signature. The browser extension can sign automatically.
241
245
  </p>
242
246
  </details>
@@ -282,7 +286,7 @@ refreshBrowserAuth();
282
286
  async function passkeyLogin(){{
283
287
  document.getElementById("err").style.display="none";
284
288
  if(!window.capauthWebAuthn || !window.capauthWebAuthn.available()){{
285
- return setErr("This browser has no passkey support — use PGP above.");
289
+ return setErr("This browser has no passkey support. Use PGP below.");
286
290
  }}
287
291
  try{{
288
292
  const redirect = await window.capauthWebAuthn.login(BASE, REQUEST_ID);
@@ -321,14 +325,14 @@ document.getElementById("fp").addEventListener("blur", async function(){{
321
325
  "service="+ch.service, "expires="+ch.expires].join("\\n");
322
326
  document.getElementById("nonce").textContent=currentPayload;
323
327
  // window.capauth provider: auto-sign with Tier B origin-binding. The
324
- // extension injects origin=window.location.origin and signs in-extension —
328
+ // extension injects origin=window.location.origin and signs in-extension -
325
329
  // the private key never reaches this page. Falls back to manual paste.
326
330
  if(window.capauth && window.capauth.isCapAuth){{
327
331
  try{{
328
332
  const res=await window.capauth.signChallenge(ch);
329
333
  document.getElementById("sig").value=res.signature;
330
334
  submitSig();
331
- }}catch(e){{ /* denied/locked — leave the paste flow available */ }}
335
+ }}catch(e){{ /* denied/locked: leave the paste flow available */ }}
332
336
  }}
333
337
  }}catch(e){{ document.getElementById("nonce").textContent="Error: "+e.message; }}
334
338
  }});
@@ -338,7 +342,7 @@ async function submitSig(){{
338
342
  const fp=document.getElementById("fp").value.trim().toUpperCase().replace(/\\s/g,"");
339
343
  const sig=document.getElementById("sig").value.trim();
340
344
  if(![40,64].includes(fp.length)) return setErr("Fingerprint must be 40- or 64-hex characters.");
341
- if(!currentNonce) return setErr("No challenge loaded — tab out of the fingerprint field first.");
345
+ if(!currentNonce) return setErr("No challenge loaded. Enter your fingerprint first.");
342
346
  if(!sig) return setErr("Paste your PGP signature.");
343
347
 
344
348
  const body={{request_id:REQUEST_ID, fingerprint:fp, nonce:currentNonce,
@@ -347,9 +351,33 @@ async function submitSig(){{
347
351
  method:"POST", headers:{{"Content-Type":"application/json"}}, body: JSON.stringify(body)
348
352
  }});
349
353
  if(!r.ok){{ const b=await r.json().catch(()=>({{}})); return setErr(b.detail || b.error || "Login failed."); }}
354
+ try{{ localStorage.setItem("capauth_manual_fp",fp); }}catch(e){{}}
350
355
  const d=await r.json();
351
356
  window.location.href=d.redirect_to;
352
357
  }}
358
+
359
+ async function pasteAndSubmit(){{
360
+ document.getElementById("err").style.display="none";
361
+ let text="";
362
+ try{{ text=await navigator.clipboard.readText(); }}
363
+ catch(e){{ return setErr("This browser did not allow reading the clipboard. Paste the signature into the box (Ctrl+V), then click Verify & Continue."); }}
364
+ if(!/BEGIN PGP (SIGNATURE|MESSAGE)/.test(text)){{
365
+ return setErr("The clipboard holds no signature yet. Click Copy message, run capauth sign-challenge, then try again.");
366
+ }}
367
+ document.getElementById("sig").value=text.trim();
368
+ submitSig();
369
+ }}
370
+
371
+ // the fingerprint of the last successful manual login: open this section
372
+ // with a fresh challenge loaded, so the next login is copy, sign, paste
373
+ (function(){{
374
+ let fp="";
375
+ try{{ fp=(localStorage.getItem("capauth_manual_fp")||"").trim(); }}catch(e){{}}
376
+ if(![40,64].includes(fp.length)||!/^[0-9A-F]+$/.test(fp)) return;
377
+ document.getElementById("fp").value=fp;
378
+ document.getElementById("manual-pgp").open=true;
379
+ document.getElementById("fp").dispatchEvent(new Event("blur"));
380
+ }})();
353
381
  </script>
354
382
  </body>
355
383
  </html>
@@ -1067,7 +1095,7 @@ def build_oidc_router(
1067
1095
  raise HTTPException(status_code=400, detail="invalid_code_challenge")
1068
1096
  if not (_OPAQUE_MIN_LENGTH <= len(state) <= 512):
1069
1097
  raise HTTPException(status_code=400, detail="invalid_state")
1070
- if not (_OPAQUE_MIN_LENGTH <= len(nonce) <= 512):
1098
+ if (nonce or client.require_nonce) and not (_OPAQUE_MIN_LENGTH <= len(nonce) <= 512):
1071
1099
  raise HTTPException(status_code=400, detail="invalid_nonce")
1072
1100
  scopes = scope.split()
1073
1101
  if (