create-workframe 0.1.0 → 0.1.1

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 (206) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +12 -12
  3. package/README.md +8 -92
  4. package/SECURITY.md +40 -40
  5. package/bin/workframe.js +329 -329
  6. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +1 -1
  7. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +8 -8
  8. package/package.json +3 -6
  9. package/profiles/architect/AGENTS.md +29 -29
  10. package/profiles/architect/SOUL.md +2 -2
  11. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  12. package/profiles/designer/AGENTS.md +26 -26
  13. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  14. package/profiles/dev/AGENTS.md +28 -28
  15. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  16. package/profiles/docs/AGENTS.md +27 -27
  17. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  18. package/profiles/research/AGENTS.md +26 -26
  19. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/visionary/AGENTS.md +25 -25
  21. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  22. package/profiles/workframe-agent/AGENTS.md +37 -37
  23. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  24. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  25. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  26. package/rules/workspace-README.md +5 -5
  27. package/scripts/apply-update-hermes.sh +17 -17
  28. package/scripts/apply-update-workframe.sh +77 -77
  29. package/scripts/bootstrap-workspace-link.sh +8 -8
  30. package/scripts/bundle-workframe-ui.mjs +3 -3
  31. package/scripts/compose-docker-host.sh +37 -37
  32. package/scripts/ensure-compose-host-paths.mjs +51 -51
  33. package/scripts/fix-zk-encryption-key.sh +35 -35
  34. package/scripts/lib/install-identity.mjs +212 -212
  35. package/scripts/restart-gateway-hermes.sh +12 -12
  36. package/scripts/set-compose-public-url.mjs +92 -92
  37. package/scripts/setup-stack-secrets.sh +50 -50
  38. package/scripts/sync-canonical-to-package.mjs +8 -7
  39. package/scripts/verify-public-deploy.sh +105 -105
  40. package/shared/WORKFRAME_AGENT_LIBRARY.md +17 -17
  41. package/shared/WORKFRAME_AGENT_OPERATIONS.md +15 -15
  42. package/shared/WORKFRAME_AGENT_PACKS.json +18 -18
  43. package/shared/WORKFRAME_AGENT_PACKS.yaml +8 -8
  44. package/shared/WORKFRAME_SKILL_CURATION.md +4 -4
  45. package/workframe-api/README.md +28 -28
  46. package/workframe-api/action_proxy.py +131 -131
  47. package/workframe-api/auth_rate_limit.py +49 -49
  48. package/workframe-api/credential_vault.py +445 -445
  49. package/workframe-api/data/avatar-catalog.json +41 -41
  50. package/workframe-api/email_sender.py +220 -220
  51. package/workframe-api/google_auth.py +90 -90
  52. package/workframe-api/install_api.py +359 -359
  53. package/workframe-api/internal_proxy_auth.py +150 -150
  54. package/workframe-api/llm_proxy.py +277 -277
  55. package/workframe-api/oidc_jwt.py +108 -108
  56. package/workframe-api/package.json +12 -13
  57. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  58. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  59. package/workframe-api/requirements.txt +2 -2
  60. package/workframe-api/site_meta.py +271 -271
  61. package/workframe-api/stack_config.py +427 -427
  62. package/workframe-api/time-bind-chat.py +99 -99
  63. package/workframe-api/turn_credentials.py +226 -226
  64. package/workframe-api/updates.py +417 -417
  65. package/workframe-api/vault_kek.py +159 -159
  66. package/workframe-api/zk_auth.py +633 -633
  67. package/workframe-supervisor/Dockerfile +11 -11
  68. package/workframe-supervisor/server.py +787 -787
  69. package/workframe-ui/docker/nginx.conf +85 -85
  70. package/workframe-ui/public/assets/{arc-CBDYvkAF.js → arc-COAT3laO.js} +1 -1
  71. package/workframe-ui/public/assets/architecture-7EHR7CIX-DUyH3hWG.js +1 -0
  72. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-XnBRKeW0.js → architectureDiagram-3BPJPVTR-BFjWV24l.js} +1 -1
  73. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-VYHUfVhd.js → blockDiagram-GPEHLZMM-DSQLPfrj.js} +1 -1
  74. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BTjUcJpm.js → c4Diagram-AAUBKEIU-DKEHv1t2.js} +1 -1
  75. package/workframe-ui/public/assets/channel-g7r_RGaY.js +1 -0
  76. package/workframe-ui/public/assets/{chunk-2J33WTMH-w7uu7R-b.js → chunk-2J33WTMH-DHZg-DUi.js} +1 -1
  77. package/workframe-ui/public/assets/{chunk-3OPIFGDE-Cb9LtnDX.js → chunk-3OPIFGDE-BB-OYTfp.js} +1 -1
  78. package/workframe-ui/public/assets/{chunk-4BX2VUAB-DiQ-qCwH.js → chunk-4BX2VUAB-C93q0YIm.js} +1 -1
  79. package/workframe-ui/public/assets/{chunk-55IACEB6-C-mLFr7z.js → chunk-55IACEB6-MAYniqik.js} +1 -1
  80. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DOesfiCI.js → chunk-5ZQYHXKU-ChgN6YJs.js} +1 -1
  81. package/workframe-ui/public/assets/{chunk-727SXJPM-BJ3oBZuz.js → chunk-727SXJPM-B_FYwdAv.js} +1 -1
  82. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-CCA6xpGs.js → chunk-AQP2D5EJ-1_Hw_h1A.js} +1 -1
  83. package/workframe-ui/public/assets/{chunk-BSJP7CBP-a0cMNFb2.js → chunk-BSJP7CBP-CFiDQ1Rv.js} +1 -1
  84. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-kuqN8EIY.js → chunk-CSCIHK7Q-DZ9UMTlB.js} +1 -1
  85. package/workframe-ui/public/assets/{chunk-FMBD7UC4-DyPgYHCg.js → chunk-FMBD7UC4-DlMlyFgw.js} +1 -1
  86. package/workframe-ui/public/assets/{chunk-KSCS5N6A-CdUuvR0V.js → chunk-KSCS5N6A-DHXtQ_Hf.js} +1 -1
  87. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-Dq9NoWmK.js → chunk-L5ZTLDWV-CuQzg-QG.js} +1 -1
  88. package/workframe-ui/public/assets/{chunk-LZXEDZCA-p74rddlO.js → chunk-LZXEDZCA-BHzjzCGg.js} +2 -2
  89. package/workframe-ui/public/assets/{chunk-ND2GUHAM-DBD2u1Gz.js → chunk-ND2GUHAM-DHXx05n2.js} +1 -1
  90. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BeIeYFnd.js → chunk-NZK2D7GU-CV5pmDM_.js} +1 -1
  91. package/workframe-ui/public/assets/{chunk-O5CBEL6O-ClHc56ib.js → chunk-O5CBEL6O-6tkCHxsV.js} +1 -1
  92. package/workframe-ui/public/assets/chunk-QZHKN3VN-C5UQehWY.js +1 -0
  93. package/workframe-ui/public/assets/chunk-WU5MYG2G-DhWllrI8.js +1 -0
  94. package/workframe-ui/public/assets/{chunk-XPW4576I-EFr8R_1p.js → chunk-XPW4576I-BClwIiCp.js} +1 -1
  95. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BBM_8T8E.js +1 -0
  96. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BBM_8T8E.js +1 -0
  97. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-C7aPBODd.js → cose-bilkent-S5V4N54A-DOrGV6DQ.js} +1 -1
  98. package/workframe-ui/public/assets/{dagre-BM42HDAG-BdU1Rv-H.js → dagre-BM42HDAG-DXTPvJkX.js} +1 -1
  99. package/workframe-ui/public/assets/{diagram-2AECGRRQ-DWowSo85.js → diagram-2AECGRRQ-xX_v-pbf.js} +1 -1
  100. package/workframe-ui/public/assets/{diagram-5GNKFQAL-MnxBbceO.js → diagram-5GNKFQAL-Cd2pXbBe.js} +1 -1
  101. package/workframe-ui/public/assets/{diagram-KO2AKTUF-DQaLRXFf.js → diagram-KO2AKTUF-Df3XvUtk.js} +1 -1
  102. package/workframe-ui/public/assets/{diagram-LMA3HP47-CQaBud9k.js → diagram-LMA3HP47-CsijIPaD.js} +1 -1
  103. package/workframe-ui/public/assets/{diagram-OG6HWLK6-D8bAXbY9.js → diagram-OG6HWLK6-aq5fmfHd.js} +1 -1
  104. package/workframe-ui/public/assets/{dist-DGpTLHr_.js → dist-D1c0mkbB.js} +1 -1
  105. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-1E-xSvBK.js → erDiagram-TEJ5UH35-DnFysVRY.js} +1 -1
  106. package/workframe-ui/public/assets/eventmodeling-FCH6USID-Ci8mdb44.js +1 -0
  107. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CgOVD5hu.js → flowDiagram-I6XJVG4X-C6Ebi3su.js} +1 -1
  108. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-JFYAIauo.js → ganttDiagram-6RSMTGT7-BQXQtUpa.js} +1 -1
  109. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-B9REenIl.js → gitGraph-WXDBUCRP-Dt0zIs_M.js} +1 -1
  110. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-BQ7NcMSn.js → gitGraphDiagram-PVQCEYII-BF8gHzRn.js} +1 -1
  111. package/workframe-ui/public/assets/index-DpoUZAxh.css +1 -0
  112. package/workframe-ui/public/assets/{index-Dnw6vjqb.js → index-lRpzpNPT.js} +2 -2
  113. package/workframe-ui/public/assets/{info-J43DQDTF-CL6-eTjH.js → info-J43DQDTF-CSmszQJT.js} +1 -1
  114. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-LJTODW4W.js → infoDiagram-5YYISTIA-CVTKGW6p.js} +1 -1
  115. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-bchrQVuo.js → ishikawaDiagram-YF4QCWOH-Z8pT09Lv.js} +1 -1
  116. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-DkrvYuxP.js → journeyDiagram-JHISSGLW-r3wD68_T.js} +1 -1
  117. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-DFRbj0IG.js → kanban-definition-UN3LZRKU-Il8VglqN.js} +1 -1
  118. package/workframe-ui/public/assets/{line-Vd48P7-O.js → line-oyjpfz2A.js} +1 -1
  119. package/workframe-ui/public/assets/{linear-Ckizh2G7.js → linear-Cf7p5tVp.js} +1 -1
  120. package/workframe-ui/public/assets/{mermaid-parser.core-Bkimsnqj.js → mermaid-parser.core-YmbZ-AfY.js} +2 -2
  121. package/workframe-ui/public/assets/{mermaid.core-x0TvVuPo.js → mermaid.core-BFdCAqCo.js} +3 -3
  122. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-6ykAFPEz.js → mindmap-definition-RKZ34NQL-Cy2iCtEl.js} +1 -1
  123. package/workframe-ui/public/assets/{packet-YPE3B663-Dw3xgMDt.js → packet-YPE3B663-DwOBZL6K.js} +1 -1
  124. package/workframe-ui/public/assets/{pie-LRSECV5Y-DATysawG.js → pie-LRSECV5Y-04PPhnKK.js} +1 -1
  125. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-SJKD1S0S.js → pieDiagram-4H26LBE5-LxIpgHqi.js} +1 -1
  126. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-BrYDZX8q.js → quadrantDiagram-W4KKPZXB-0nBYfYm4.js} +1 -1
  127. package/workframe-ui/public/assets/{radar-GUYGQ44K-BmWYPCds.js → radar-GUYGQ44K-D2-vBqps.js} +1 -1
  128. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-DwL9Mc8e.js → requirementDiagram-4Y6WPE33-DbuU0nlu.js} +1 -1
  129. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-DYIFsL8h.js → sankeyDiagram-5OEKKPKP-B2hQ6B2x.js} +1 -1
  130. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-0-FPkFk8.js → sequenceDiagram-3UESZ5HK-BBrU30e1.js} +1 -1
  131. package/workframe-ui/public/assets/{src-B_od6b6h.js → src-BJEDmV70.js} +1 -1
  132. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-BQCiBk6u.js → stateDiagram-AJRCARHV-7FGO4kkH.js} +1 -1
  133. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-DLTSizMg.js +1 -0
  134. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DS3tFcXj.js → timeline-definition-PNZ67QCA-ptDm4rCN.js} +1 -1
  135. package/workframe-ui/public/assets/{treeView-BLDUP644-DSyUCKLY.js → treeView-BLDUP644-CS6Z-0q8.js} +1 -1
  136. package/workframe-ui/public/assets/{treemap-LRROVOQU-CEZaNh5Y.js → treemap-LRROVOQU-DqV4Y2VA.js} +1 -1
  137. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-CD-Vc9NF.js → vennDiagram-CIIHVFJN-C0UrZJYt.js} +1 -1
  138. package/workframe-ui/public/assets/{wardley-L42UT6IY-Drq5w1Mc.js → wardley-L42UT6IY-bNDN3_Sa.js} +1 -1
  139. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DouXDJoF.js → wardleyDiagram-YWT4CUSO-jWiJsefM.js} +1 -1
  140. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-DDf_Lol5.js → xychartDiagram-2RQKCTM6-Dsh_fLCy.js} +1 -1
  141. package/workframe-ui/public/favicon.svg +7 -7
  142. package/workframe-ui/public/index.html +50 -50
  143. package/workframe-ui/public/workframe-config.json +3 -3
  144. package/scripts/security_audit.py +0 -156
  145. package/scripts/test-scaffold.mjs +0 -390
  146. package/workframe-api/tests/__init__.py +0 -0
  147. package/workframe-api/tests/db_setup.py +0 -13
  148. package/workframe-api/tests/test_admin_updates_gated.py +0 -30
  149. package/workframe-api/tests/test_agent_dm_bootstrap.py +0 -196
  150. package/workframe-api/tests/test_agent_profile_sync.py +0 -76
  151. package/workframe-api/tests/test_auth_email.py +0 -222
  152. package/workframe-api/tests/test_auth_hole_fix_selfcheck.py +0 -99
  153. package/workframe-api/tests/test_auth_rate_limit.py +0 -19
  154. package/workframe-api/tests/test_avatar_resolve.py +0 -77
  155. package/workframe-api/tests/test_child_soul_template.py +0 -71
  156. package/workframe-api/tests/test_credential_canary.py +0 -135
  157. package/workframe-api/tests/test_credential_isolation.py +0 -448
  158. package/workframe-api/tests/test_credential_resolution.py +0 -206
  159. package/workframe-api/tests/test_device_oauth.py +0 -108
  160. package/workframe-api/tests/test_doctor_repair.py +0 -103
  161. package/workframe-api/tests/test_ensure_profile_api.py +0 -77
  162. package/workframe-api/tests/test_gateway_compose_security.py +0 -136
  163. package/workframe-api/tests/test_install_secure_host.py +0 -39
  164. package/workframe-api/tests/test_internal_proxy_auth.py +0 -125
  165. package/workframe-api/tests/test_invite_runtime_bootstrap.py +0 -72
  166. package/workframe-api/tests/test_kanban_delegation.py +0 -185
  167. package/workframe-api/tests/test_llm_proxy.py +0 -155
  168. package/workframe-api/tests/test_login_access_policy.py +0 -183
  169. package/workframe-api/tests/test_mvp_model_bootstrap.py +0 -75
  170. package/workframe-api/tests/test_onboarding_bootstrap.py +0 -248
  171. package/workframe-api/tests/test_platform_auth.py +0 -47
  172. package/workframe-api/tests/test_profile_config_path.py +0 -56
  173. package/workframe-api/tests/test_profile_config_yaml_repair.py +0 -63
  174. package/workframe-api/tests/test_profile_create.py +0 -72
  175. package/workframe-api/tests/test_profile_identity_overlay.py +0 -61
  176. package/workframe-api/tests/test_profile_install_health.py +0 -45
  177. package/workframe-api/tests/test_profile_secret_policy.py +0 -57
  178. package/workframe-api/tests/test_profile_workspace_cwd.py +0 -34
  179. package/workframe-api/tests/test_provider_bootstrap.py +0 -75
  180. package/workframe-api/tests/test_provider_connect.py +0 -54
  181. package/workframe-api/tests/test_room_crud.py +0 -192
  182. package/workframe-api/tests/test_room_tenancy.py +0 -701
  183. package/workframe-api/tests/test_runtime_identity_backfill.py +0 -34
  184. package/workframe-api/tests/test_site_meta.py +0 -81
  185. package/workframe-api/tests/test_soul_stub.py +0 -42
  186. package/workframe-api/tests/test_space_member_sync.py +0 -99
  187. package/workframe-api/tests/test_stripe_stack_config.py +0 -37
  188. package/workframe-api/tests/test_supervisor_lifecycle.py +0 -52
  189. package/workframe-api/tests/test_turn_credential_vault.py +0 -125
  190. package/workframe-api/tests/test_updates.py +0 -176
  191. package/workframe-api/tests/test_user_cohort.py +0 -113
  192. package/workframe-api/tests/test_vault_envelope.py +0 -110
  193. package/workframe-api/tests/test_workspace_members.py +0 -183
  194. package/workframe-api/tests/test_workspace_messaging_sync.py +0 -125
  195. package/workframe-api/tests/test_workspace_provider_list.py +0 -57
  196. package/workframe-supervisor/tests/test_exec_guard.py +0 -42
  197. package/workframe-supervisor/tests/test_server_import.py +0 -21
  198. package/workframe-ui/public/assets/architecture-7EHR7CIX-CtbQKTuT.js +0 -1
  199. package/workframe-ui/public/assets/channel-Dy4Z4-jn.js +0 -1
  200. package/workframe-ui/public/assets/chunk-QZHKN3VN-CtBEchFK.js +0 -1
  201. package/workframe-ui/public/assets/chunk-WU5MYG2G-B9pBtriN.js +0 -1
  202. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BMAEA8jI.js +0 -1
  203. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BMAEA8jI.js +0 -1
  204. package/workframe-ui/public/assets/eventmodeling-FCH6USID-D75cstNT.js +0 -1
  205. package/workframe-ui/public/assets/index-DpAGxump.css +0 -1
  206. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B89jAMFF.js +0 -1
@@ -1,108 +1,108 @@
1
- """Minimal OIDC id_token verification (RS256 + JWKS) — no extra dependencies."""
2
-
3
- from __future__ import annotations
4
-
5
- import base64
6
- import json
7
- import time
8
- import urllib.error
9
- import urllib.request
10
- from typing import Any
11
-
12
- from cryptography.hazmat.primitives import hashes
13
- from cryptography.hazmat.primitives.asymmetric import padding, rsa
14
- from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
15
-
16
- _JWKS_CACHE: dict[str, tuple[float, dict[str, Any]]] = {}
17
- _JWKS_TTL = 3600.0
18
-
19
-
20
- def _b64url_decode(raw: str) -> bytes:
21
- padded = raw + "=" * (-len(raw) % 4)
22
- return base64.urlsafe_b64decode(padded.encode("ascii"))
23
-
24
-
25
- def _jwk_to_rsa(key: dict[str, Any]) -> RSAPublicKey:
26
- n = int.from_bytes(_b64url_decode(str(key["n"])), "big")
27
- e = int.from_bytes(_b64url_decode(str(key["e"])), "big")
28
- return rsa.RSAPublicNumbers(e, n).public_key()
29
-
30
-
31
- def _fetch_jwks(jwks_url: str) -> dict[str, Any]:
32
- now = time.time()
33
- cached = _JWKS_CACHE.get(jwks_url)
34
- if cached and cached[0] > now:
35
- return cached[1]
36
- req = urllib.request.Request(jwks_url, headers={"Accept": "application/json"})
37
- with urllib.request.urlopen(req, timeout=15) as resp:
38
- payload = json.loads(resp.read().decode("utf-8"))
39
- if not isinstance(payload, dict):
40
- raise ValueError("invalid jwks")
41
- _JWKS_CACHE[jwks_url] = (now + _JWKS_TTL, payload)
42
- return payload
43
-
44
-
45
- def verify_rs256_id_token(
46
- token: str,
47
- *,
48
- audience: str,
49
- issuer: str | tuple[str, ...] = (),
50
- jwks_url: str,
51
- clock_skew: int = 60,
52
- ) -> dict[str, Any]:
53
- parts = str(token or "").split(".")
54
- if len(parts) != 3:
55
- raise ValueError("invalid id_token")
56
- header = json.loads(_b64url_decode(parts[0]))
57
- payload = json.loads(_b64url_decode(parts[1]))
58
- if str(header.get("alg") or "") != "RS256":
59
- raise ValueError("unsupported jwt alg")
60
- kid = str(header.get("kid") or "")
61
- jwks = _fetch_jwks(jwks_url)
62
- keys = jwks.get("keys") if isinstance(jwks.get("keys"), list) else []
63
- pub: RSAPublicKey | None = None
64
- for entry in keys:
65
- if not isinstance(entry, dict):
66
- continue
67
- if kid and str(entry.get("kid") or "") != kid:
68
- continue
69
- if str(entry.get("kty") or "") != "RSA":
70
- continue
71
- pub = _jwk_to_rsa(entry)
72
- break
73
- if pub is None:
74
- raise ValueError("jwks key not found")
75
- signing_input = f"{parts[0]}.{parts[1]}".encode("ascii")
76
- signature = _b64url_decode(parts[2])
77
- pub.verify(signature, signing_input, padding.PKCS1v15(), hashes.SHA256())
78
-
79
- now = int(time.time())
80
- exp = int(payload.get("exp") or 0)
81
- if exp and now > exp + clock_skew:
82
- raise ValueError("id_token expired")
83
- iat = int(payload.get("iat") or 0)
84
- if iat and now + clock_skew < iat:
85
- raise ValueError("id_token not yet valid")
86
-
87
- iss = str(payload.get("iss") or "")
88
- allowed_iss = issuer or ()
89
- if allowed_iss and iss not in allowed_iss:
90
- raise ValueError("issuer mismatch")
91
-
92
- aud = payload.get("aud")
93
- aud_ok = audience in (aud if isinstance(aud, list) else [aud])
94
- if not aud_ok:
95
- raise ValueError("audience mismatch")
96
-
97
- if not isinstance(payload, dict):
98
- raise ValueError("invalid claims")
99
- return payload
100
-
101
-
102
- def verify_google_id_token(token: str, client_id: str) -> dict[str, Any]:
103
- return verify_rs256_id_token(
104
- token,
105
- audience=str(client_id or "").strip(),
106
- issuer=("https://accounts.google.com", "accounts.google.com"),
107
- jwks_url="https://www.googleapis.com/oauth2/v3/certs",
108
- )
1
+ """Minimal OIDC id_token verification (RS256 + JWKS) — no extra dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import base64
6
+ import json
7
+ import time
8
+ import urllib.error
9
+ import urllib.request
10
+ from typing import Any
11
+
12
+ from cryptography.hazmat.primitives import hashes
13
+ from cryptography.hazmat.primitives.asymmetric import padding, rsa
14
+ from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey
15
+
16
+ _JWKS_CACHE: dict[str, tuple[float, dict[str, Any]]] = {}
17
+ _JWKS_TTL = 3600.0
18
+
19
+
20
+ def _b64url_decode(raw: str) -> bytes:
21
+ padded = raw + "=" * (-len(raw) % 4)
22
+ return base64.urlsafe_b64decode(padded.encode("ascii"))
23
+
24
+
25
+ def _jwk_to_rsa(key: dict[str, Any]) -> RSAPublicKey:
26
+ n = int.from_bytes(_b64url_decode(str(key["n"])), "big")
27
+ e = int.from_bytes(_b64url_decode(str(key["e"])), "big")
28
+ return rsa.RSAPublicNumbers(e, n).public_key()
29
+
30
+
31
+ def _fetch_jwks(jwks_url: str) -> dict[str, Any]:
32
+ now = time.time()
33
+ cached = _JWKS_CACHE.get(jwks_url)
34
+ if cached and cached[0] > now:
35
+ return cached[1]
36
+ req = urllib.request.Request(jwks_url, headers={"Accept": "application/json"})
37
+ with urllib.request.urlopen(req, timeout=15) as resp:
38
+ payload = json.loads(resp.read().decode("utf-8"))
39
+ if not isinstance(payload, dict):
40
+ raise ValueError("invalid jwks")
41
+ _JWKS_CACHE[jwks_url] = (now + _JWKS_TTL, payload)
42
+ return payload
43
+
44
+
45
+ def verify_rs256_id_token(
46
+ token: str,
47
+ *,
48
+ audience: str,
49
+ issuer: str | tuple[str, ...] = (),
50
+ jwks_url: str,
51
+ clock_skew: int = 60,
52
+ ) -> dict[str, Any]:
53
+ parts = str(token or "").split(".")
54
+ if len(parts) != 3:
55
+ raise ValueError("invalid id_token")
56
+ header = json.loads(_b64url_decode(parts[0]))
57
+ payload = json.loads(_b64url_decode(parts[1]))
58
+ if str(header.get("alg") or "") != "RS256":
59
+ raise ValueError("unsupported jwt alg")
60
+ kid = str(header.get("kid") or "")
61
+ jwks = _fetch_jwks(jwks_url)
62
+ keys = jwks.get("keys") if isinstance(jwks.get("keys"), list) else []
63
+ pub: RSAPublicKey | None = None
64
+ for entry in keys:
65
+ if not isinstance(entry, dict):
66
+ continue
67
+ if kid and str(entry.get("kid") or "") != kid:
68
+ continue
69
+ if str(entry.get("kty") or "") != "RSA":
70
+ continue
71
+ pub = _jwk_to_rsa(entry)
72
+ break
73
+ if pub is None:
74
+ raise ValueError("jwks key not found")
75
+ signing_input = f"{parts[0]}.{parts[1]}".encode("ascii")
76
+ signature = _b64url_decode(parts[2])
77
+ pub.verify(signature, signing_input, padding.PKCS1v15(), hashes.SHA256())
78
+
79
+ now = int(time.time())
80
+ exp = int(payload.get("exp") or 0)
81
+ if exp and now > exp + clock_skew:
82
+ raise ValueError("id_token expired")
83
+ iat = int(payload.get("iat") or 0)
84
+ if iat and now + clock_skew < iat:
85
+ raise ValueError("id_token not yet valid")
86
+
87
+ iss = str(payload.get("iss") or "")
88
+ allowed_iss = issuer or ()
89
+ if allowed_iss and iss not in allowed_iss:
90
+ raise ValueError("issuer mismatch")
91
+
92
+ aud = payload.get("aud")
93
+ aud_ok = audience in (aud if isinstance(aud, list) else [aud])
94
+ if not aud_ok:
95
+ raise ValueError("audience mismatch")
96
+
97
+ if not isinstance(payload, dict):
98
+ raise ValueError("invalid claims")
99
+ return payload
100
+
101
+
102
+ def verify_google_id_token(token: str, client_id: str) -> dict[str, Any]:
103
+ return verify_rs256_id_token(
104
+ token,
105
+ audience=str(client_id or "").strip(),
106
+ issuer=("https://accounts.google.com", "accounts.google.com"),
107
+ jwks_url="https://www.googleapis.com/oauth2/v3/certs",
108
+ )
@@ -1,13 +1,12 @@
1
- {
2
- "name": "@workframe/workframe-api",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "dev": "python3 server.py",
8
- "start": "python3 server.py",
9
- "test": "python3 -m unittest discover -s tests",
10
- "typecheck": "python3 -m py_compile server.py zk_auth.py email_sender.py",
11
- "build": "python3 -m py_compile server.py zk_auth.py email_sender.py"
12
- }
13
- }
1
+ {
2
+ "name": "@workframe/workframe-api",
3
+ "version": "0.1.1",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "python3 server.py",
8
+ "start": "python3 server.py",
9
+ "typecheck": "python3 -m py_compile server.py zk_auth.py email_sender.py",
10
+ "build": "python3 -m py_compile server.py zk_auth.py email_sender.py"
11
+ }
12
+ }
@@ -1 +1 @@
1
- :root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--wf-font-sans:"Inter Tight", ui-sans-serif, system-ui, sans-serif;--wf-font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--wf-blur:34px;--wf-header-height:56px;--wf-footer-height:28px}[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--wf-bg:#0a0a0f;--wf-text:#e8e8ee;--wf-muted:#727280;--wf-border:#ffffff12;--wf-border-strong:#ffffff1c;--wf-surface:#12121a94;--wf-surface-soft:#12121a61;--wf-primary:#ffffffe6;--wf-primary-foreground:#0a0a0f;--wf-violet:#6d28d9;--wf-violet-glow:#7c3aed;--wf-ring:#6d28d961}[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--wf-bg:#f2f2f7;--wf-text:#1c1c1e;--wf-muted:#8e8e93;--wf-border:#00000014;--wf-border-strong:#0000001f;--wf-surface:#fffc;--wf-surface-soft:#ffffff80;--wf-primary:#000;--wf-primary-foreground:#fff;--wf-violet:#6d28d9;--wf-violet-glow:#7c3aed;--wf-ring:#6d28d961}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--wf-font-sans);background:var(--wf-bg);color:var(--wf-text);-webkit-font-smoothing:antialiased;line-height:1.5}.wf-shell{flex-direction:column;min-height:100vh;display:flex}.wf-shell--centered{justify-content:center;align-items:center}.wf-header{height:var(--wf-header-height);border-bottom:1px solid var(--wf-border);background:var(--wf-surface);-webkit-backdrop-filter:blur(var(--wf-blur));backdrop-filter:blur(var(--wf-blur));justify-content:space-between;align-items:center;padding:0 20px;display:flex}.wf-header__brand{letter-spacing:-.02em;color:var(--wf-text);font-size:18px;font-weight:700}.wf-header__actions{gap:8px;display:flex}.wf-auth{background:var(--wf-surface);border:1px solid var(--wf-border);width:100%;max-width:400px;-webkit-backdrop-filter:blur(var(--wf-blur));backdrop-filter:blur(var(--wf-blur));border-radius:16px;padding:32px}.wf-auth__title{letter-spacing:-.02em;margin:0 0 8px;font-size:24px;font-weight:700}.wf-auth__subtitle{color:var(--wf-muted);margin:0 0 24px;font-size:14px}.wf-field{margin-bottom:16px}.wf-field__label{color:var(--wf-muted);margin-bottom:6px;font-size:13px;font-weight:500;display:block}.wf-input{background:var(--wf-surface-soft);border:1px solid var(--wf-border);width:100%;height:40px;color:var(--wf-text);font-size:14px;font-family:var(--wf-font-sans);border-radius:8px;outline:none;padding:0 12px;transition:border-color .15s}.wf-input:focus{border-color:var(--wf-violet);box-shadow:0 0 0 3px var(--wf-ring)}.wf-input::placeholder{color:var(--wf-muted)}.wf-btn{height:40px;font-size:14px;font-weight:500;font-family:var(--wf-font-sans);cursor:pointer;border:none;border-radius:8px;justify-content:center;align-items:center;padding:0 16px;transition:all .15s;display:inline-flex}.wf-btn--primary{background:var(--wf-primary);color:var(--wf-primary-foreground)}.wf-btn--primary:hover{opacity:.9}.wf-btn--secondary{background:var(--wf-surface-soft);color:var(--wf-text);border:1px solid var(--wf-border)}.wf-btn--secondary:hover{background:var(--wf-border)}.wf-btn--ghost{color:var(--wf-text);background:0 0}.wf-btn--ghost:hover{background:var(--wf-surface-soft)}.wf-btn--sm{height:32px;padding:0 12px;font-size:13px}.wf-btn:disabled{opacity:.5;cursor:not-allowed}.wf-error{color:#ef4444;background:#ef44441a;border:1px solid #ef444433;border-radius:8px;margin-bottom:16px;padding:10px 12px;font-size:13px}.wf-success{color:#22c55e;background:#22c55e1a;border:1px solid #22c55e33;border-radius:8px;margin-bottom:16px;padding:10px 12px;font-size:13px}.wf-otp{justify-content:center;gap:8px;margin-bottom:16px;display:flex}.wf-otp__digit{text-align:center;width:44px;height:48px;font-size:20px;font-weight:600;font-family:var(--wf-font-mono);background:var(--wf-surface-soft);border:1px solid var(--wf-border);color:var(--wf-text);border-radius:8px;outline:none;padding:0;transition:border-color .15s}.wf-otp__digit:focus{border-color:var(--wf-violet);box-shadow:0 0 0 3px var(--wf-ring)}.wf-layout{flex:1;display:flex;overflow:hidden}.wf-sidebar{border-right:1px solid var(--wf-border);background:var(--wf-surface);width:200px;padding:16px 0}.wf-sidebar__link{color:var(--wf-muted);cursor:pointer;padding:8px 20px;font-size:14px;text-decoration:none;transition:all .15s;display:block}.wf-sidebar__link:hover{color:var(--wf-text);background:var(--wf-surface-soft)}.wf-sidebar__link--active{color:var(--wf-text);background:var(--wf-surface-soft);font-weight:500}.wf-main{flex:1;padding:24px;overflow-y:auto}.wf-profile{max-width:480px}.wf-profile__avatar{background:var(--wf-violet);color:#fff;border-radius:50%;justify-content:center;align-items:center;width:64px;height:64px;margin-bottom:24px;font-size:24px;font-weight:600;display:flex}.wf-profile__name{margin:0 0 4px;font-size:20px;font-weight:600}.wf-profile__email{color:var(--wf-muted);margin:0 0 24px;font-size:14px}.wf-otp{gap:8px;margin-bottom:16px;display:flex}.wf-otp .wf-input{text-align:center;letter-spacing:4px;font-size:20px;font-weight:600}
1
+ :root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--wf-font-sans:"Inter Tight", ui-sans-serif, system-ui, sans-serif;--wf-font-mono:"JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;--wf-blur:34px;--wf-header-height:56px;--wf-footer-height:28px}[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--wf-bg:#0a0a0f;--wf-text:#e8e8ee;--wf-muted:#727280;--wf-border:#ffffff12;--wf-border-strong:#ffffff1c;--wf-surface:#12121a94;--wf-surface-soft:#12121a61;--wf-primary:#ffffffe6;--wf-primary-foreground:#0a0a0f;--wf-violet:#6d28d9;--wf-violet-glow:#7c3aed;--wf-ring:#6d28d961}[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--wf-bg:#f2f2f7;--wf-text:#1c1c1e;--wf-muted:#8e8e93;--wf-border:#00000014;--wf-border-strong:#0000001f;--wf-surface:#fffc;--wf-surface-soft:#ffffff80;--wf-primary:#000;--wf-primary-foreground:#fff;--wf-violet:#6d28d9;--wf-violet-glow:#7c3aed;--wf-ring:#6d28d961}*{box-sizing:border-box;margin:0;padding:0}body{font-family:var(--wf-font-sans);background:var(--wf-bg);color:var(--wf-text);-webkit-font-smoothing:antialiased;line-height:1.5}.wf-shell{flex-direction:column;min-height:100vh;display:flex}.wf-shell--centered{justify-content:center;align-items:center}.wf-header{height:var(--wf-header-height);border-bottom:1px solid var(--wf-border);background:var(--wf-surface);-webkit-backdrop-filter:blur(var(--wf-blur));backdrop-filter:blur(var(--wf-blur));justify-content:space-between;align-items:center;padding:0 20px;display:flex}.wf-header__brand{letter-spacing:-.02em;color:var(--wf-text);font-size:18px;font-weight:700}.wf-header__actions{gap:8px;display:flex}.wf-auth{background:var(--wf-surface);border:1px solid var(--wf-border);width:100%;max-width:400px;-webkit-backdrop-filter:blur(var(--wf-blur));backdrop-filter:blur(var(--wf-blur));border-radius:16px;padding:32px}.wf-auth__title{letter-spacing:-.02em;margin:0 0 8px;font-size:24px;font-weight:700}.wf-auth__subtitle{color:var(--wf-muted);margin:0 0 24px;font-size:14px}.wf-field{margin-bottom:16px}.wf-field__label{color:var(--wf-muted);margin-bottom:6px;font-size:13px;font-weight:500;display:block}.wf-input{background:var(--wf-surface-soft);border:1px solid var(--wf-border);width:100%;height:40px;color:var(--wf-text);font-size:14px;font-family:var(--wf-font-sans);border-radius:8px;outline:none;padding:0 12px;transition:border-color .15s}.wf-input:focus{border-color:var(--wf-violet);box-shadow:0 0 0 3px var(--wf-ring)}.wf-input::placeholder{color:var(--wf-muted)}.wf-btn{height:40px;font-size:14px;font-weight:500;font-family:var(--wf-font-sans);cursor:pointer;border:none;border-radius:8px;justify-content:center;align-items:center;padding:0 16px;transition:all .15s;display:inline-flex}.wf-btn--primary{background:var(--wf-primary);color:var(--wf-primary-foreground)}.wf-btn--primary:hover{opacity:.9}.wf-btn--secondary{background:var(--wf-surface-soft);color:var(--wf-text);border:1px solid var(--wf-border)}.wf-btn--secondary:hover{background:var(--wf-border)}.wf-btn--ghost{color:var(--wf-text);background:0 0}.wf-btn--ghost:hover{background:var(--wf-surface-soft)}.wf-btn--sm{height:32px;padding:0 12px;font-size:13px}.wf-btn:disabled{opacity:.5;cursor:not-allowed}.wf-error{color:#ef4444;background:#ef44441a;border:1px solid #ef444433;border-radius:8px;margin-bottom:16px;padding:10px 12px;font-size:13px}.wf-success{color:#22c55e;background:#22c55e1a;border:1px solid #22c55e33;border-radius:8px;margin-bottom:16px;padding:10px 12px;font-size:13px}.wf-otp{justify-content:center;gap:8px;margin-bottom:16px;display:flex}.wf-otp__digit{text-align:center;width:44px;height:48px;font-size:20px;font-weight:600;font-family:var(--wf-font-mono);background:var(--wf-surface-soft);border:1px solid var(--wf-border);color:var(--wf-text);border-radius:8px;outline:none;padding:0;transition:border-color .15s}.wf-otp__digit:focus{border-color:var(--wf-violet);box-shadow:0 0 0 3px var(--wf-ring)}.wf-layout{flex:1;display:flex;overflow:hidden}.wf-sidebar{border-right:1px solid var(--wf-border);background:var(--wf-surface);width:200px;padding:16px 0}.wf-sidebar__link{color:var(--wf-muted);cursor:pointer;padding:8px 20px;font-size:14px;text-decoration:none;transition:all .15s;display:block}.wf-sidebar__link:hover{color:var(--wf-text);background:var(--wf-surface-soft)}.wf-sidebar__link--active{color:var(--wf-text);background:var(--wf-surface-soft);font-weight:500}.wf-main{flex:1;padding:24px;overflow-y:auto}.wf-profile{max-width:480px}.wf-profile__avatar{background:var(--wf-violet);color:#fff;border-radius:50%;justify-content:center;align-items:center;width:64px;height:64px;margin-bottom:24px;font-size:24px;font-weight:600;display:flex}.wf-profile__name{margin:0 0 4px;font-size:20px;font-weight:600}.wf-profile__email{color:var(--wf-muted);margin:0 0 24px;font-size:14px}.wf-otp{gap:8px;margin-bottom:16px;display:flex}.wf-otp .wf-input{text-align:center;letter-spacing:4px;font-size:20px;font-weight:600}