paskia 0.9.1__py3-none-any.whl → 0.10.2__py3-none-any.whl

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 (71) hide show
  1. paskia/_version.py +2 -2
  2. paskia/bootstrap.py +8 -7
  3. paskia/db/__init__.py +2 -0
  4. paskia/db/background.py +5 -8
  5. paskia/db/jsonl.py +2 -2
  6. paskia/db/logging.py +130 -45
  7. paskia/db/operations.py +25 -4
  8. paskia/db/structs.py +3 -2
  9. paskia/fastapi/__main__.py +33 -19
  10. paskia/fastapi/admin.py +2 -2
  11. paskia/fastapi/api.py +7 -3
  12. paskia/fastapi/authz.py +11 -9
  13. paskia/fastapi/logging.py +64 -21
  14. paskia/fastapi/mainapp.py +8 -5
  15. paskia/fastapi/remote.py +11 -37
  16. paskia/fastapi/user.py +22 -0
  17. paskia/fastapi/ws.py +12 -35
  18. paskia/fastapi/wschat.py +55 -2
  19. paskia/fastapi/wsutil.py +2 -7
  20. paskia/frontend-build/auth/admin/index.html +7 -6
  21. paskia/frontend-build/auth/assets/{AccessDenied-DPkUS8LZ.css → AccessDenied-CVQZxSIL.css} +1 -1
  22. paskia/frontend-build/auth/assets/AccessDenied-Licr0tqA.js +8 -0
  23. paskia/frontend-build/auth/assets/{RestrictedAuth-CvR33_Z0.css → RestrictedAuth-0MFeNWS2.css} +1 -1
  24. paskia/frontend-build/auth/assets/{RestrictedAuth-DsJXicIw.js → RestrictedAuth-DWKMTEV3.js} +1 -1
  25. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js +33 -0
  26. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css +1 -0
  27. paskia/frontend-build/auth/assets/{admin-DzzjSg72.css → admin-B1H4YqM_.css} +1 -1
  28. paskia/frontend-build/auth/assets/admin-CZKsX1OI.js +1 -0
  29. paskia/frontend-build/auth/assets/{auth-C7k64Wad.css → auth-B4EpDxom.css} +1 -1
  30. paskia/frontend-build/auth/assets/auth-Pe-PKe8b.js +1 -0
  31. paskia/frontend-build/auth/assets/forward-BC0p23CH.js +1 -0
  32. paskia/frontend-build/auth/assets/{pow-2N9bxgAo.js → pow-DUr-T9XX.js} +1 -1
  33. paskia/frontend-build/auth/assets/reset-B8PlNXuP.css +1 -0
  34. paskia/frontend-build/auth/assets/reset-CkY9h28U.js +1 -0
  35. paskia/frontend-build/auth/assets/restricted-C9cJlHkd.js +1 -0
  36. paskia/frontend-build/auth/assets/theme-C2WysaSw.js +1 -0
  37. paskia/frontend-build/auth/index.html +8 -7
  38. paskia/frontend-build/auth/restricted/index.html +7 -6
  39. paskia/frontend-build/int/forward/index.html +6 -6
  40. paskia/frontend-build/int/reset/index.html +4 -4
  41. paskia/frontend-build/paskia.webp +0 -0
  42. paskia/util/__init__.py +0 -0
  43. paskia/util/apistructs.py +110 -0
  44. paskia/util/frontend.py +75 -0
  45. paskia/util/hostutil.py +75 -0
  46. paskia/util/htmlutil.py +47 -0
  47. paskia/util/passphrase.py +20 -0
  48. paskia/util/permutil.py +43 -0
  49. paskia/util/pow.py +45 -0
  50. paskia/util/querysafe.py +11 -0
  51. paskia/util/sessionutil.py +38 -0
  52. paskia/util/startupbox.py +103 -0
  53. paskia/util/timeutil.py +47 -0
  54. paskia/util/useragent.py +10 -0
  55. paskia/util/userinfo.py +63 -0
  56. paskia/util/vitedev.py +71 -0
  57. paskia/util/wordlist.py +54 -0
  58. {paskia-0.9.1.dist-info → paskia-0.10.2.dist-info}/METADATA +14 -11
  59. paskia-0.10.2.dist-info/RECORD +78 -0
  60. paskia/frontend-build/auth/assets/AccessDenied-Fmeb6EtF.js +0 -8
  61. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css +0 -1
  62. paskia/frontend-build/auth/assets/_plugin-vue_export-helper-nhjnO_bd.js +0 -2
  63. paskia/frontend-build/auth/assets/admin-CPE1pLMm.js +0 -1
  64. paskia/frontend-build/auth/assets/auth-YIZvPlW_.js +0 -1
  65. paskia/frontend-build/auth/assets/forward-DmqVHZ7e.js +0 -1
  66. paskia/frontend-build/auth/assets/reset-Chtv69AT.css +0 -1
  67. paskia/frontend-build/auth/assets/reset-s20PATTN.js +0 -1
  68. paskia/frontend-build/auth/assets/restricted-D3AJx3_6.js +0 -1
  69. paskia-0.9.1.dist-info/RECORD +0 -60
  70. {paskia-0.9.1.dist-info → paskia-0.10.2.dist-info}/WHEEL +0 -0
  71. {paskia-0.9.1.dist-info → paskia-0.10.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1 @@
1
+ import{_ as re,b as m,c as p,d as o,t as A,g as T,F as C,r as X,f as Re,B as te,m as I,A as J,E as ie,k as O,j as Ce,L as De,x as G,q as ae,G as ke,z as P,y as _e,h as z,i as ne,v as oe,u as ge,o as Se,a as Oe,N as et,w as tt,C as st,D as nt,J as ot,K as at}from"./_plugin-vue_export-helper-DJsHCwvl.js";import{i as it}from"./theme-C2WysaSw.js";import{u as Ae,U as rt,_ as lt,a as ut,R as dt,N as me,M as ct,b as mt,L as ft,A as fe,B as gt,c as vt}from"./AccessDenied-Licr0tqA.js";import"./helpers-DzjFIx78.js";const yt={key:0},pt=["onClick"],ht=["onClick"],bt={class:"role-names"},wt={class:"center"},$t={key:0,class:"center"},Dt=["onClick"],kt={key:0,class:"permissions-section"},St={class:"matrix-scroll"},Ot=["title"],Rt=["title"],Ct={class:"display-text"},At=["checked","onChange"],Ut={class:"perm-name-cell"},Et={class:"perm-title"},Mt={class:"display-text"},Pt=["onClick"],xt={class:"perm-id-info"},qt={class:"id-text"},Nt={class:"perm-domain"},Tt={class:"perm-members center"},Lt={class:"perm-actions center"},Ft=["onClick"],It={__name:"AdminOverview",props:{info:Object,orgs:Array,permissions:Array,permissionSummary:Object,navigationDisabled:{type:Boolean,default:!1}},emits:["createOrg","openOrg","updateOrg","deleteOrg","toggleOrgPermission","openDialog","deletePermission","renamePermissionDisplay","navigateOut"],setup(n,{expose:V,emit:Y}){const k=n,L=Y,E=O(null),c=O(null),y=O(null),x=O(null),N=O(null),B=O(null),q=I(()=>[...k.orgs].sort((r,t)=>{const s=r.display_name.localeCompare(t.display_name);return s!==0?s:r.uuid.localeCompare(t.uuid)})),w=I(()=>[...k.permissions].sort((r,t)=>r.scope.localeCompare(t.scope))),F=I(()=>k.info?.ctx.permissions.includes("auth:admin")),$=I(()=>k.info?.ctx.permissions.includes("auth:org:admin"));function _(r){return r.roles.slice().sort((t,s)=>t.display_name.localeCompare(s.display_name)).map(t=>t.display_name).join(", ")}function Z(r,t){if(k.navigationDisabled)return;const s=J(r);if(!s)return;const D=r.target,l=D.closest("tr");if(!l)return;const d=l.closest("tbody");if(!d)return;const h=Array.from(d.querySelectorAll("tr")),R=h.indexOf(l);if(R===-1)return;if(s==="left"||s==="right"){r.preventDefault();const M=Array.from(l.querySelectorAll("a, button:not([disabled])")),K=M.indexOf(D);if(K===-1)return;s==="left"&&K>0?M[K-1].focus():s==="right"&&K<M.length-1&&M[K+1].focus();return}let j=R;if(s==="up"&&R>0)j=R-1;else if(s==="down"&&R<h.length-1)j=R+1;else if(s==="up"&&R===0){r.preventDefault(),t==="org"?te(c.value,{itemSelector:"button"}):t==="perm"&&te(N.value,{itemSelector:"button"});return}else if(s==="down"&&R===h.length-1){if(r.preventDefault(),t==="org"&&F.value&&x.value){const M=x.value.querySelector('input[type="checkbox"]');M?M.focus():te(N.value,{itemSelector:"button"})}return}if(j!==R){r.preventDefault();const K=h[j].querySelector("a, button:not([disabled])");K&&K.focus()}}function ee(r){if(k.navigationDisabled)return;const t=J(r);if(t){if(r.preventDefault(),t==="left"||t==="right")ie(c.value,r.target,t,{itemSelector:"button"});else if(t==="up")L("navigateOut","up");else if(t==="down"){const s=y.value?.querySelector("tbody tr a, tbody tr button:not([disabled])");s&&s.focus()}}}function g(r){if(k.navigationDisabled)return;const t=J(r);if(!t)return;const s=r.target;if(s.tagName!=="INPUT")return;r.preventDefault();const D=Array.from(x.value.querySelectorAll('input[type="checkbox"]')),l=D.indexOf(s);if(l===-1)return;const d=q.value.length,h=w.value.length;if(d===0||h===0)return;const R=Math.floor(l/d),j=l%d;let M=l;if(t==="left")j>0&&(M=l-1);else if(t==="right")j<d-1&&(M=l+1);else if(t==="up")if(R>0)M=l-d;else{const le=y.value?.querySelector("tbody tr:last-child")?.querySelector("a, button:not([disabled])");le&&le.focus();return}else if(t==="down")if(R<h-1)M=l+d;else{te(N.value,{itemSelector:"button"});return}M!==l&&D[M]&&D[M].focus()}function u(r){if(k.navigationDisabled)return;const t=J(r);if(t){if(r.preventDefault(),t==="left"||t==="right")ie(N.value,r.target,t,{itemSelector:"button"});else if(t==="up"){const s=x.value?.querySelectorAll('input[type="checkbox"]');if(s?.length){const D=q.value.length,d=(w.value.length-1)*D;s[d]?s[d].focus():s[0].focus()}else{const l=y.value?.querySelector("tbody tr:last-child")?.querySelector("a, button:not([disabled])");l&&l.focus()}}else if(t==="down"){const s=B.value?.querySelector("tbody tr button:not([disabled])");s&&s.focus()}}}function f(){if(F.value)te(c.value,{itemSelector:"button"});else{const r=y.value?.querySelector("tbody tr a, tbody tr button:not([disabled])");r&&r.focus()}}return V({focusFirstElement:f}),(r,t)=>(m(),p(C,null,[o("div",{class:"permissions-section",ref_key:"orgSection",ref:E},[o("h2",null,A(F.value?"Organizations":"Your Organizations"),1),o("div",{class:"actions",ref_key:"orgActionsRef",ref:c,onKeydown:ee},[F.value?(m(),p("button",{key:0,onClick:t[0]||(t[0]=s=>r.$emit("createOrg"))},"+ Create Org")):T("",!0)],544),o("table",{class:"org-table",ref_key:"orgTableRef",ref:y,onKeydown:t[1]||(t[1]=s=>Z(s,"org"))},[o("thead",null,[o("tr",null,[t[4]||(t[4]=o("th",null,"Name",-1)),t[5]||(t[5]=o("th",null,"Roles",-1)),t[6]||(t[6]=o("th",null,"Members",-1)),F.value?(m(),p("th",yt,"Actions")):T("",!0)])]),o("tbody",null,[(m(!0),p(C,null,X(q.value,s=>(m(),p("tr",{key:s.uuid},[o("td",null,[o("a",{href:"#org/{{o.uuid}}",onClick:Ce(D=>r.$emit("openOrg",s),["prevent"])},A(s.display_name),9,pt),F.value||$.value?(m(),p("button",{key:0,onClick:D=>r.$emit("updateOrg",s),class:"icon-btn edit-org-btn","aria-label":"Rename organization",title:"Rename organization"},"✏️",8,ht)):T("",!0)]),o("td",bt,A(_(s)),1),o("td",wt,A(s.roles.reduce((D,l)=>D+l.users.length,0)),1),F.value?(m(),p("td",$t,[o("button",{onClick:D=>r.$emit("deleteOrg",s),class:"icon-btn delete-icon","aria-label":"Delete organization",title:"Delete organization"},"❌",8,Dt)])):T("",!0)]))),128))])],544)],512),F.value?(m(),p("div",kt,[t[10]||(t[10]=o("h2",null,"Permissions",-1)),o("div",{class:"matrix-wrapper",ref_key:"permMatrixRef",ref:x,onKeydown:g},[o("div",St,[o("div",{class:"perm-matrix-grid",style:Re({gridTemplateColumns:"minmax(180px, 1fr) "+q.value.map(()=>"2.2rem").join(" ")})},[t[7]||(t[7]=o("div",{class:"grid-head perm-head"},"Permission",-1)),(m(!0),p(C,null,X(q.value,s=>(m(),p("div",{key:"head-"+s.uuid,class:"grid-head org-head",title:s.display_name},[o("span",null,A(s.display_name),1)],8,Ot))),128)),(m(!0),p(C,null,X(w.value,s=>(m(),p(C,{key:s.uuid},[o("div",{class:"perm-name",title:s.scope},[o("span",Ct,A(s.display_name),1)],8,Rt),(m(!0),p(C,null,X(q.value,D=>(m(),p("div",{key:D.uuid+"-"+s.uuid,class:"matrix-cell"},[o("input",{type:"checkbox",checked:D.permissions.includes(s.uuid),onChange:l=>r.$emit("toggleOrgPermission",D,s.uuid,l.target.checked)},null,40,At)]))),128))],64))),128))],4)]),t[8]||(t[8]=o("p",{class:"matrix-hint muted"},"Toggle which permissions each organization can grant to its members.",-1))],544),o("div",{class:"actions",ref_key:"permActionsRef",ref:N,onKeydown:u},[F.value?(m(),p("button",{key:0,onClick:t[2]||(t[2]=s=>r.$emit("openDialog","perm-create",{display_name:"",scope:"",domain:""}))},"+ Create Permission")):T("",!0)],544),o("table",{class:"org-table",ref_key:"permTableRef",ref:B,onKeydown:t[3]||(t[3]=s=>Z(s,"perm"))},[t[9]||(t[9]=o("thead",null,[o("tr",null,[o("th",{scope:"col"},"Permission"),o("th",{scope:"col"},"Domain"),o("th",{scope:"col",class:"center"},"Members"),o("th",{scope:"col",class:"center"},"Actions")])],-1)),o("tbody",null,[(m(!0),p(C,null,X(w.value,s=>(m(),p("tr",{key:s.uuid},[o("td",Ut,[o("div",Et,[o("span",Mt,A(s.display_name),1),o("button",{onClick:D=>r.$emit("renamePermissionDisplay",s),class:"icon-btn edit-display-btn","aria-label":"Edit permission",title:"Edit permission"},"✏️",8,Pt)]),o("div",xt,[o("span",qt,A(s.scope),1)])]),o("td",Nt,A(s.domain||"—"),1),o("td",Tt,A(n.permissionSummary[s.uuid]?.userCount||0),1),o("td",Lt,[o("button",{onClick:D=>r.$emit("deletePermission",s),class:"icon-btn delete-icon","aria-label":"Delete permission",title:"Delete permission"},"❌",8,Ft)])]))),128))])],544)])):T("",!0)],64))}},Kt=re(It,[["__scopeId","data-v-56228c4a"]]),Bt=["title"],zt={class:"org-name"},Vt={class:"matrix-scroll"},jt=["title"],Ht=["title"],Gt=["checked","onChange"],Jt=["onDrop"],Yt=["onKeydown"],Wt=["title"],Zt=["onClick"],Qt={class:"role-actions"},Xt=["onClick"],_t=["onDragstart","onClick","onKeydown","title"],es={class:"name"},ts={class:"meta"},ss=["onKeydown"],ns=["onClick"],os={__name:"AdminOrgDetail",props:{selectedOrg:Object,permissions:Array,navigationDisabled:{type:Boolean,default:!1}},emits:["updateOrg","createRole","updateRole","deleteRole","createUserInRole","openUser","toggleRolePermission","onRoleDragOver","onRoleDrop","onUserDragStart","navigateOut"],setup(n,{expose:V,emit:Y}){const k=n,L=Y,E=O(null),c=O(null),y=O(null),x=I(()=>[...k.selectedOrg.roles].sort((g,u)=>{const f=g.display_name.toLowerCase(),r=u.display_name.toLowerCase();return f!==r?f.localeCompare(r):g.uuid.localeCompare(u.uuid)})),N=I(()=>{const g=new Set(k.selectedOrg.permissions||[]);return k.permissions.filter(u=>g.has(u.uuid))});function B(g,u,f){L("toggleRolePermission",g,u,f)}function q(g){if(k.navigationDisabled)return;const u=J(g);if(u){if(g.preventDefault(),u==="left"||u==="right")ie(E.value,g.target,u,{itemSelector:"button"});else if(u==="up")L("navigateOut","up");else if(u==="down"){const f=c.value?.querySelector('input[type="checkbox"]');f?f.focus():Z()}}}function w(g){if(k.navigationDisabled)return;const u=J(g);if(!u)return;const f=g.target;if(f.tagName!=="INPUT")return;g.preventDefault();const r=Array.from(c.value.querySelectorAll('input[type="checkbox"]')),t=r.indexOf(f);if(t===-1)return;const s=x.value.length,D=k.selectedOrg.permissions.length,l=Math.floor(t/s),d=t%s;let h=t;if(u==="left"&&d>0)h=t-1;else if(u==="right"&&d<s-1)h=t+1;else if(u==="up"&&l>0)h=t-s;else if(u==="down"&&l<D-1)h=t+s;else if(u==="up"&&l===0){const R=E.value?.querySelector("button");R&&R.focus();return}else if(u==="down"&&l===D-1){Z();return}h!==t&&r[h]&&r[h].focus()}function F(g){if(k.navigationDisabled)return;const u=J(g);if(!u)return;const f=g.target;if(!f.classList.contains("user-chip"))return;const r=f.closest(".user-list");if(!r)return;const t=Array.from(r.querySelectorAll(".user-chip")),s=t.indexOf(f);if(s!==-1){if(u==="up"&&s>0){g.preventDefault(),t[s-1].focus();return}else if(u==="down"&&s<t.length-1){g.preventDefault(),t[s+1].focus();return}if(u==="up"&&s===0){g.preventDefault();const l=r.closest(".role-column")?.querySelector(".role-header button");l&&l.focus();return}if(!(u==="down"&&s===t.length-1)&&(u==="left"||u==="right")){g.preventDefault();const D=Array.from(y.value?.querySelectorAll(".role-column")||[]),l=r.closest(".role-column"),d=D.indexOf(l);let h=u==="left"?d-1:d+1;if(h>=0&&h<D.length){const R=D[h],j=R.querySelectorAll(".user-chip"),M=Math.min(s,j.length-1);if(j[M])j[M].focus();else{const K=R.querySelector(".plus-btn");K&&K.focus()}}else if(u==="left"&&d===0){const R=c.value?.querySelector('input[type="checkbox"]:last-of-type');R&&R.focus()}}}}function $(g,u){if(k.navigationDisabled)return;const f=J(g);if(!f)return;const r=Array.from(y.value?.querySelectorAll(".role-column")||[]);if(f==="left"||f==="right"){g.preventDefault();const t=g.currentTarget.querySelectorAll("button:not([disabled])"),s=Array.from(t).indexOf(g.target);if(f==="left"&&s>0)t[s-1].focus();else if(f==="right"&&s<t.length-1)t[s+1].focus();else if(f==="left"&&s===0&&u>0){const l=r[u-1]?.querySelectorAll(".role-header button");l?.length&&l[l.length-1].focus()}else if(f==="right"&&s===t.length-1&&u<r.length-1){const l=r[u+1]?.querySelector(".role-header button");l&&l.focus()}}else if(f==="up"){g.preventDefault();const t=c.value?.querySelectorAll('input[type="checkbox"]');if(t?.length){const s=x.value.length,l=(k.selectedOrg.permissions.length-1)*s+u;t[l]?t[l].focus():t[t.length-1].focus()}else{const s=E.value?.querySelector("button");s&&s.focus()}}else if(f==="down"){g.preventDefault();const s=r[u]?.querySelector(".user-chip");s&&s.focus()}}function _(g,u){if(k.navigationDisabled)return;const f=J(g);if(!f)return;const r=Array.from(y.value?.querySelectorAll(".role-column")||[]);if(f==="up"){g.preventDefault();const s=r[u]?.querySelector(".role-header button");s&&s.focus()}else if(f==="left"&&u>0){g.preventDefault();const t=r[u-1],s=t?.querySelector(".empty-role button"),D=t?.querySelector(".user-chip:last-child");s?s.focus():D&&D.focus()}else if(f==="right"&&u<r.length-1){g.preventDefault();const t=r[u+1],s=t?.querySelector(".empty-role button"),D=t?.querySelector(".user-chip");s?s.focus():D&&D.focus()}}function Z(){const u=y.value?.querySelector(".role-column")?.querySelector(".role-header button");u&&u.focus()}function ee(){const g=E.value?.querySelector("button");g&&g.focus()}return V({focusFirstElement:ee}),(g,u)=>(m(),p(C,null,[o("h2",{class:"org-title",ref_key:"orgTitleRef",ref:E,onKeydown:q,title:n.selectedOrg.uuid},[o("span",zt,A(n.selectedOrg.display_name),1),o("button",{onClick:u[0]||(u[0]=f=>g.$emit("updateOrg",n.selectedOrg)),class:"icon-btn","aria-label":"Rename organization",title:"Rename organization"},"✏️")],40,Bt),o("div",{class:"matrix-wrapper",ref_key:"permMatrixRef",ref:c,onKeydown:w},[o("div",Vt,[o("div",{class:"perm-matrix-grid",style:Re({gridTemplateColumns:"minmax(180px, 1fr) "+x.value.map(()=>"2.2rem").join(" ")+" 2.2rem"})},[u[5]||(u[5]=o("div",{class:"grid-head perm-head"},"Permission",-1)),(m(!0),p(C,null,X(x.value,f=>(m(),p("div",{key:"head-"+f.uuid,class:"grid-head role-head",title:f.display_name},[o("span",null,A(f.display_name),1)],8,jt))),128)),o("div",{class:"grid-head role-head add-role-head",title:"Add role",onClick:u[1]||(u[1]=f=>g.$emit("createRole",n.selectedOrg)),role:"button",tabindex:"0",onKeydown:u[2]||(u[2]=De(f=>g.$emit("createRole",n.selectedOrg),["enter"]))},"➕",32),(m(!0),p(C,null,X(N.value,f=>(m(),p(C,{key:f.uuid},[o("div",{class:"perm-name",title:f.scope},A(f.display_name),9,Ht),(m(!0),p(C,null,X(x.value,r=>(m(),p("div",{key:r.uuid+"-"+f.uuid,class:"matrix-cell"},[o("input",{type:"checkbox",checked:r.permissions.includes(f.uuid),onChange:t=>B(r,f.uuid,t.target.checked)},null,40,Gt)]))),128)),u[4]||(u[4]=o("div",{class:"matrix-cell add-role-cell"},null,-1))],64))),128))],4)]),u[6]||(u[6]=o("p",{class:"matrix-hint muted"},"Toggle which permissions each role grants.",-1))],544),o("div",{class:"roles-grid",ref_key:"rolesGridRef",ref:y},[(m(!0),p(C,null,X(x.value,(f,r)=>(m(),p("div",{key:f.uuid,class:"role-column",onDragover:u[3]||(u[3]=t=>g.$emit("onRoleDragOver",t)),onDrop:t=>g.$emit("onRoleDrop",t,n.selectedOrg,f)},[o("div",{class:"role-header",onKeydown:t=>$(t,r)},[o("strong",{class:"role-name",title:f.uuid},[o("span",null,A(f.display_name),1),o("button",{onClick:t=>g.$emit("updateRole",f),class:"icon-btn","aria-label":"Edit role",title:"Edit role"},"✏️",8,Zt)],8,Wt),o("div",Qt,[o("button",{onClick:t=>g.$emit("createUserInRole",n.selectedOrg,f),class:"plus-btn","aria-label":"Add user",title:"Add user"},"➕",8,Xt)])],40,Yt),f.users.length>0?(m(),p("ul",{key:0,class:"user-list",onKeydown:F},[(m(!0),p(C,null,X(f.users.slice().sort((t,s)=>{const D=t.display_name.toLowerCase(),l=s.display_name.toLowerCase();return D!==l?D.localeCompare(l):t.uuid.localeCompare(s.uuid)}),t=>(m(),p("li",{key:t.uuid,class:"user-chip",tabindex:"0",draggable:"true",onDragstart:s=>g.$emit("onUserDragStart",s,t,n.selectedOrg.uuid),onClick:s=>g.$emit("openUser",t),onKeydown:De(s=>g.$emit("openUser",t),["enter"]),title:t.uuid},[o("span",es,A(t.display_name),1),o("span",ts,A(t.last_seen?new Date(t.last_seen).toLocaleDateString():"—"),1)],40,_t))),128))],32)):(m(),p("div",{key:1,class:"empty-role",onKeydown:t=>_(t,r)},[u[7]||(u[7]=o("p",{class:"empty-text muted"},"No members",-1)),o("button",{onClick:t=>g.$emit("deleteRole",f),class:"icon-btn delete-icon","aria-label":"Delete empty role",title:"Delete role"},"❌",8,ns)],40,ss))],40,Jt))),128))],512)],64))}},as=re(os,[["__scopeId","data-v-f76fbbc3"]]),is={class:"user-detail"},rs={key:0,class:"error small"},ls=["disabled"],us={class:"section-block","data-section":"registered-passkeys"},ds={class:"section-body"},cs={__name:"AdminUserDetail",props:{selectedUser:Object,userDetail:Object,selectedOrg:Object,loading:Boolean,showRegModal:Boolean,navigationDisabled:{type:Boolean,default:!1}},emits:["generateUserRegistrationLink","goOverview","openOrg","onUserNameSaved","closeRegModal","editUserName","refreshUserDetail","navigateOut"],setup(n,{expose:V,emit:Y}){const k=n,L=Y,E=Ae(),c=O({}),y=O(null),x=O(null),N=O(null),B=O(null),q=O(null),w=O(null),F=O(null),$=I(()=>k.showRegModal);function _(){E.showMessage(`📋 Link copied! Send it to ${k.selectedUser.display_name}.`),L("closeRegModal")}function Z(){L("editUserName",k.selectedUser)}async function ee(l){try{const d=await P(`/auth/api/admin/orgs/${k.selectedUser.org}/users/${k.selectedUser.uuid}/credentials/${l.credential}`,{method:"DELETE"});d.status==="ok"?L("onUserNameSaved"):console.error("Failed to delete credential",d)}catch(d){console.error("Delete credential error",d)}}async function g(l){const d=l?.id;if(d){c.value={...c.value,[d]:!0};try{const h=await P(`/auth/api/admin/orgs/${k.selectedUser.org}/users/${k.selectedUser.uuid}/sessions/${d}`,{method:"DELETE"});if(h.status==="ok"){if(h.current_session_terminated){sessionStorage.clear(),location.reload();return}L("refreshUserDetail"),E.showMessage("Session terminated","success",2500)}else E.showMessage(h.detail||"Failed to terminate session","error")}catch(h){console.error("Terminate session error",h),E.showMessage(h.message||"Failed to terminate session","error")}finally{const h={...c.value};delete h[d],c.value=h}}}function u(l){if($.value||k.navigationDisabled)return;const d=J(l);d&&(l.preventDefault(),d==="left"||d==="right"?ie(N.value,l.target,d,{itemSelector:".mini-btn"}):d==="up"?L("navigateOut","up"):d==="down"&&te(B.value,{itemSelector:"button"}))}function f(l){if($.value||k.navigationDisabled)return;const d=J(l);d&&(l.preventDefault(),d==="left"||d==="right"?ie(B.value,l.target,d,{itemSelector:"button"}):d==="up"?te(N.value,{itemSelector:".mini-btn"}):d==="down"&&q.value?.$el?.focus())}function r(l){$.value||k.navigationDisabled||(l==="up"?te(B.value,{itemSelector:"button"}):l==="down"&&ke(w.value?.$el,0,{itemSelector:".session-group"}))}function t(l){if(!($.value||k.navigationDisabled)){if(l==="up")q.value?.$el?.focus();else if(l==="down"){const d=F.value?.querySelector("button");d&&d.focus()}}}function s(l){if($.value||k.navigationDisabled)return;const d=J(l);d&&(l.preventDefault(),d==="up"&&ke(w.value?.$el,-1,{itemSelector:".session-group"}))}function D(){te(N.value,{itemSelector:".mini-btn"})}return V({focusFirstElement:D}),(l,d)=>(m(),p("div",is,[o("div",{ref_key:"userInfoRef",ref:N,onKeydown:u},[n.userDetail&&!n.userDetail.error?(m(),G(rt,{key:0,name:n.userDetail.display_name||n.selectedUser.display_name,visits:n.userDetail.visits,"created-at":n.userDetail.created_at,"last-seen":n.userDetail.last_seen,loading:n.loading,"org-display-name":n.userDetail.org.display_name,"role-name":n.userDetail.role,"update-endpoint":`/auth/api/admin/orgs/${n.selectedUser.org}/users/${n.selectedUser.uuid}/display-name`,onSaved:d[0]||(d[0]=h=>l.$emit("onUserNameSaved")),onEditName:Z},null,8,["name","visits","created-at","last-seen","loading","org-display-name","role-name","update-endpoint"])):T("",!0)],544),n.userDetail?.error?(m(),p("div",rs,A(n.userDetail.error),1)):T("",!0),n.userDetail&&!n.userDetail.error?(m(),p(C,{key:1},[o("div",{class:"registration-actions",ref_key:"regActionsRef",ref:B,onKeydown:f},[o("button",{class:"btn-secondary reg-token-btn",onClick:d[1]||(d[1]=h=>l.$emit("generateUserRegistrationLink",n.selectedUser)),disabled:n.loading},"Generate Registration Token",8,ls),d[6]||(d[6]=o("p",{class:"matrix-hint muted"}," Generate a one-time registration link so this user can register or add another passkey. Copy the link from the dialog and send it to the user, or have the user scan the QR code on their device. ",-1))],544),o("section",us,[d[7]||(d[7]=o("div",{class:"section-header"},[o("h2",null,"Registered Passkeys")],-1)),o("div",ds,[ae(lt,{ref_key:"credentialListRef",ref:q,credentials:n.userDetail.credentials,"aaguid-info":n.userDetail.aaguid_info,"allow-delete":!0,"hovered-credential-uuid":y.value,"hovered-session-credential-uuid":x.value?.credential,"navigation-disabled":$.value,onDelete:ee,onCredentialHover:d[2]||(d[2]=h=>y.value=h),onNavigateOut:r},null,8,["credentials","aaguid-info","hovered-credential-uuid","hovered-session-credential-uuid","navigation-disabled"])])]),ae(ut,{ref_key:"sessionListRef",ref:w,sessions:n.userDetail.sessions||[],"terminating-sessions":c.value,"hovered-credential-uuid":y.value,"navigation-disabled":$.value,"empty-message":"This user has no active sessions.","section-description":"View and manage the active sessions for this user.",onTerminate:g,onSessionHover:d[3]||(d[3]=h=>x.value=h),onNavigateOut:t},null,8,["sessions","terminating-sessions","hovered-credential-uuid","navigation-disabled"])],64)):T("",!0),o("div",{class:"actions ancillary-actions",ref_key:"backButtonRef",ref:F,onKeydown:s},[n.selectedOrg?(m(),p("button",{key:0,onClick:d[4]||(d[4]=h=>l.$emit("openOrg",n.selectedOrg)),class:"icon-btn",title:"Back to Org"},"↩️")):T("",!0)],544),n.showRegModal?(m(),G(dt,{key:2,endpoint:`/auth/api/admin/orgs/${n.selectedUser.org}/users/${n.selectedUser.uuid}/create-link`,"user-name":n.userDetail?.display_name||n.selectedUser.display_name,onClose:d[5]||(d[5]=h=>l.$emit("closeRegModal")),onCopied:_},null,8,["endpoint","user-name"])):T("",!0)]))}},ms=re(cs,[["__scopeId","data-v-738bccea"]]),fs={class:"modal-title"},gs={key:0},vs={key:2},ys={class:"small muted"},ps=["placeholder","pattern"],hs={class:"small muted"},bs={key:7},ws={key:8,class:"error small"},$s={key:9,class:"modal-actions"},Ds=["disabled"],ks=["disabled"],Ss={__name:"AdminDialogs",props:{dialog:Object,PERMISSION_ID_PATTERN:String,settings:Object},emits:["submitDialog","closeDialog"],setup(n,{emit:V}){const Y=n,k=new Set(["org-update","role-update","user-update-name"]),L=I(()=>Y.settings?.rp_id||"the configured domain");return(E,c)=>n.dialog.type?(m(),G(ct,{key:0,onClose:c[14]||(c[14]=y=>E.$emit("closeDialog"))},{default:_e(()=>[o("h3",fs,[n.dialog.type==="org-create"?(m(),p(C,{key:0},[z("Create Organization")],64)):n.dialog.type==="org-update"?(m(),p(C,{key:1},[z("Rename Organization")],64)):n.dialog.type==="role-create"?(m(),p(C,{key:2},[z("Create Role")],64)):n.dialog.type==="role-update"?(m(),p(C,{key:3},[z("Edit Role")],64)):n.dialog.type==="user-create"?(m(),p(C,{key:4},[z("Add User To Role")],64)):n.dialog.type==="user-update-name"?(m(),p(C,{key:5},[z("Edit User Name")],64)):n.dialog.type==="perm-create"||n.dialog.type==="perm-display"?(m(),p(C,{key:6},[z(A(n.dialog.type==="perm-create"?"Create Permission":"Edit Permission"),1)],64)):n.dialog.type==="confirm"?(m(),p(C,{key:7},[z("Confirm")],64)):T("",!0)]),o("form",{onSubmit:c[13]||(c[13]=Ce(y=>E.$emit("submitDialog"),["prevent"])),class:"modal-form"},[n.dialog.type==="org-create"?(m(),p("label",gs,[c[15]||(c[15]=z("Name ",-1)),ne(o("input",{ref:"nameInput","onUpdate:modelValue":c[0]||(c[0]=y=>n.dialog.data.name=y),required:""},null,512),[[oe,n.dialog.data.name]])])):n.dialog.type==="org-update"?(m(),G(me,{key:1,label:"Organization Name",modelValue:n.dialog.data.name,"onUpdate:modelValue":c[1]||(c[1]=y=>n.dialog.data.name=y),busy:n.dialog.busy,error:n.dialog.error,onCancel:c[2]||(c[2]=y=>E.$emit("closeDialog"))},null,8,["modelValue","busy","error"])):n.dialog.type==="role-create"?(m(),p("label",vs,[c[16]||(c[16]=z("Role Name ",-1)),ne(o("input",{"onUpdate:modelValue":c[3]||(c[3]=y=>n.dialog.data.name=y),placeholder:"Role name",required:""},null,512),[[oe,n.dialog.data.name]])])):n.dialog.type==="role-update"?(m(),G(me,{key:3,label:"Role Name",modelValue:n.dialog.data.name,"onUpdate:modelValue":c[4]||(c[4]=y=>n.dialog.data.name=y),busy:n.dialog.busy,error:n.dialog.error,onCancel:c[5]||(c[5]=y=>E.$emit("closeDialog"))},null,8,["modelValue","busy","error"])):n.dialog.type==="user-create"?(m(),p(C,{key:4},[o("p",ys,"Role: "+A(n.dialog.data.role.display_name),1),o("label",null,[c[17]||(c[17]=z("Display Name ",-1)),ne(o("input",{"onUpdate:modelValue":c[6]||(c[6]=y=>n.dialog.data.name=y),placeholder:"User display name",required:""},null,512),[[oe,n.dialog.data.name]])])],64)):n.dialog.type==="user-update-name"?(m(),G(me,{key:5,label:"Display Name",modelValue:n.dialog.data.name,"onUpdate:modelValue":c[7]||(c[7]=y=>n.dialog.data.name=y),busy:n.dialog.busy,error:n.dialog.error,onCancel:c[8]||(c[8]=y=>E.$emit("closeDialog"))},null,8,["modelValue","busy","error"])):n.dialog.type==="perm-create"||n.dialog.type==="perm-display"?(m(),p(C,{key:6},[o("label",null,[c[18]||(c[18]=z("Display Name ",-1)),ne(o("input",{ref:"displayNameInput","onUpdate:modelValue":c[9]||(c[9]=y=>n.dialog.data.display_name=y),required:""},null,512),[[oe,n.dialog.data.display_name]])]),o("label",null,[c[19]||(c[19]=z("Permission Scope ",-1)),ne(o("input",{"onUpdate:modelValue":c[10]||(c[10]=y=>n.dialog.data.scope=y),placeholder:n.dialog.type==="perm-create"?"yourapp:permission":n.dialog.data.permission.scope,required:"",pattern:n.PERMISSION_ID_PATTERN,title:"Allowed: A-Za-z0-9:._~-","data-form-type":"other"},null,8,ps),[[oe,n.dialog.data.scope]])]),c[21]||(c[21]=o("p",{class:"small muted"},"E.g. yourapp:reports. Changing the scope name may break deployed applications.",-1)),o("label",null,[c[20]||(c[20]=z("Domain Scope ",-1)),ne(o("input",{"onUpdate:modelValue":c[11]||(c[11]=y=>n.dialog.data.domain=y),placeholder:"e.g. app.example.com","data-form-type":"other"},null,512),[[oe,n.dialog.data.domain]])]),o("p",hs,"If set, this permission is effective only on the specified domain, which can be "+A(L.value)+" or its subdomain.",1)],64)):n.dialog.type==="confirm"?(m(),p("p",bs,A(n.dialog.data.message),1)):T("",!0),n.dialog.error&&!ge(k).has(n.dialog.type)?(m(),p("div",ws,A(n.dialog.error),1)):T("",!0),ge(k).has(n.dialog.type)?T("",!0):(m(),p("div",$s,[o("button",{type:"button",class:"btn-secondary",onClick:c[12]||(c[12]=y=>E.$emit("closeDialog")),disabled:n.dialog.busy}," Cancel ",8,Ds),o("button",{type:"submit",class:"btn-primary",disabled:n.dialog.busy},A(n.dialog.type==="confirm"?"OK":"Save"),9,ks)]))],32)]),_:1})):T("",!0)}},Os=re(Ss,[["__scopeId","data-v-8d16c6cc"]]),Rs={class:"app-shell admin-shell"},Cs={class:"app-main"},As={key:4,class:"view-root view-root--wide view-admin"},Us={class:"view-header"},Es={class:"section-block admin-section"},Ms={class:"section-body admin-section-body"},Ps={class:"admin-panels"},xs="^[A-Za-z0-9:._~-]+$",qs={__name:"AdminApp",setup(n){const V=O(null),Y=O(!0),k=O("Loading..."),L=O(!1),E=O(!1),c=O(null),y=O([]),x=O([]),N=O(null),B=O(null),q=O(null),w=Ae(),F=O(null),$=O({type:null,data:null,busy:!1,error:""}),_=O(null),Z=O(null),ee=O(null),g=O(null),u=O(null),f=I(()=>$.value.type!==null||ue.value),r=I(()=>V.value?.ctx.permissions.includes("auth:admin")),t=I(()=>V.value?.ctx.permissions.includes("auth:org:admin"));function s(e){if(!F.value)return;const a=e.target.closest(".org-add-menu"),i=e.target.closest(".add-org-btn");!a&&!i&&(F.value=null)}Se(async()=>{document.addEventListener("click",s),window.addEventListener("hashchange",d),await w.loadSettings(),w.settings?.rp_name&&(document.title=w.settings.rp_name+" Admin"),await Ee()}),Oe(()=>{document.removeEventListener("click",s),window.removeEventListener("hashchange",d)});const D=I(()=>{const e={};for(const i of y.value){const v={uuid:i.uuid,display_name:i.display_name},b=new Set(i.permissions||[]);for(const S of i.permissions||[])e[S]||(e[S]={orgs:[],orgSet:new Set,userCount:0}),e[S].orgSet.has(i.uuid)||(e[S].orgs.push(v),e[S].orgSet.add(i.uuid));for(const S of i.roles)for(const U of S.permissions)b.has(U)&&(e[U]||(e[U]={orgs:[],orgSet:new Set,userCount:0}),e[U].orgSet.has(i.uuid)||(e[U].orgs.push(v),e[U].orgSet.add(i.uuid)),e[U].userCount+=S.users.length)}const a={};for(const[i,v]of Object.entries(e))a[i]={orgs:v.orgs.sort((b,S)=>b.display_name.localeCompare(S.display_name)),userCount:v.userCount};return a});function l(e){Q("perm-display",{permission:e,scope:e.scope,display_name:e.display_name,domain:e.domain||""})}function d(){const e=window.location.hash||"";N.value=null,B.value=null,e.startsWith("#org/")?N.value=e.slice(5):e.startsWith("#user/")&&(B.value=e.slice(6))}async function h(){const e=await P("/auth/api/admin/orgs");y.value=e.map(a=>{const i=a.roles.map(b=>({...b,org:a.uuid,users:[]})),v=Object.fromEntries(i.map(b=>[b.display_name,b]));for(const b of a.users||[])v[b.role]&&v[b.role].users.push(b);return{...a,roles:i}})}async function R(){x.value=await P("/auth/api/admin/permissions")}async function j(){const e=await P("/auth/api/validate",{method:"POST"});V.value=e,L.value=!0}function M(){V.value=null,y.value=[],x.value=[],q.value=null,L.value=!1}function K(e){M(),e.name==="AuthCancelledError"?E.value=!0:c.value=e.message}const le=()=>V.value?.ctx.user.uuid,ve=new et(le,K);Se(()=>ve.start()),Oe(()=>ve.stop());async function Ee(){Y.value=!0,k.value="Loading...",c.value=null;try{await Promise.all([h(),R()]),await j(),!r.value&&t.value&&y.value.length===1&&(!window.location.hash||window.location.hash==="#overview")?(N.value=y.value[0].uuid,window.location.hash=`#org/${N.value}`,w.showMessage(`Navigating to ${y.value[0].display_name} Administration`,"info",3e3)):d()}catch(e){K(e)}finally{Y.value=!1}}function Me(){Q("org-create",{})}function ye(e){Q("org-update",{org:e,name:e.display_name})}function Pe(e){Q("user-update-name",{user:e,name:e.display_name})}async function pe(e){await P(`/auth/api/admin/orgs/${e}`,{method:"DELETE"}),await Promise.all([h(),R()])}function xe(e){if(e.roles.reduce((b,S)=>b+S.users.length,0)===0){pe(e.uuid).then(()=>{w.showMessage(`Organization "${e.display_name}" deleted.`,"success",2500)}).catch(b=>{w.showMessage(b.message||"Failed to delete organization","error")});return}const v=e.roles.filter(b=>b.users.length>0).map(b=>`${b.users.length} ${b.display_name}`).join(", ");Q("confirm",{message:`Delete organization "${e.display_name}", including accounts of ${v})?`,action:async()=>{await pe(e.uuid)}})}function qe(e,a){Q("user-create",{org:e,role:a})}async function Ne(e,a,i){if(a.role!==i)try{await P(`/auth/api/admin/orgs/${e.uuid}/users/${a.uuid}/role`,{method:"PATCH",body:{role:i}}),await h()}catch(v){w.showMessage(v.message||"Failed to update user role")}}function Te(e,a,i){e.dataTransfer.effectAllowed="move",e.dataTransfer.setData("text/plain",JSON.stringify({user_uuid:a.uuid,org:i}))}function Le(e){e.preventDefault(),e.dataTransfer.dropEffect="move"}function Fe(e,a,i){e.preventDefault();try{const v=JSON.parse(e.dataTransfer.getData("text/plain"));if(v.org!==a.uuid)return;const b=a.roles.flatMap(S=>S.users).find(S=>S.uuid===v.user_uuid);b&&Ne(a,b,i.display_name)}catch{}}function Ie(e){Q("role-create",{org:e})}function Ke(e){Q("role-update",{role:e,name:e.display_name})}function Be(e){P(`/auth/api/admin/orgs/${e.org}/roles/${e.uuid}`,{method:"DELETE"}).then(()=>{w.showMessage(`Role "${e.display_name}" deleted.`,"success",2500),h()}).catch(a=>{w.showMessage(a.message||"Failed to delete role","error")})}async function ze(e,a,i){const v=[...e.permissions],b=i?[...e.permissions,a]:e.permissions.filter(S=>S!==a);e.permissions=b;try{const S=i?"POST":"DELETE";await P(`/auth/api/admin/orgs/${e.org}/roles/${e.uuid}/permissions/${a}`,{method:S}),await h()}catch(S){w.showMessage(S.message||"Failed to update role permission"),e.permissions=v}}async function he(e){const a=new URLSearchParams({permission_uuid:e});await P(`/auth/api/admin/permission?${a.toString()}`,{method:"DELETE"}),await R()}function Ve(e){const a=D.value[e.uuid]?.userCount||0;let i=0;for(const S of y.value)for(const U of S.roles)U.permissions.includes(e.uuid)&&i++;if(i===0){he(e.uuid).then(()=>{w.showMessage(`Permission "${e.display_name}" deleted.`,"success",2500)}).catch(S=>{w.showMessage(S.message||"Failed to delete permission","error")});return}const v=[];i>0&&v.push(`${i} role${i!==1?"s":""}`),a>0&&v.push(`${a} user${a!==1?"s":""}`);const b=v.join(", ");Q("confirm",{message:`Delete permission "${e.display_name}" (${b})?`,action:async()=>{await he(e.uuid)}})}const se=I(()=>y.value.find(e=>e.uuid===N.value)||null);function be(e){window.location.hash=`#org/${e.uuid}`}function je(){window.location.hash="#overview"}function He(e){window.location.hash=`#user/${e.uuid}`}const H=I(()=>{if(!B.value)return null;for(const e of y.value)for(const a of e.roles){const i=a.users.find(v=>v.uuid===B.value);if(i)return{...i,org:e.uuid,role_display_name:a.display_name}}return null}),Ge=I(()=>H.value?"Admin: User":se.value?"Admin: Org":(w.settings?.rp_name||"Master")+" Admin"),Je=I(()=>{const e=[{label:"Auth",href:st()},{label:"Admin",href:nt()}];let a=null;H.value&&(a=y.value.find(v=>v.uuid===H.value.org)||null);const i=se.value||a;return i&&e.push({label:i.display_name,href:`#org/${i.uuid}`}),H.value&&e.push({label:H.value.display_name,href:`#user/${H.value.uuid}`}),e});tt(H,async e=>{if(!e){q.value=null;return}try{q.value=await P(`/auth/api/admin/orgs/${e.org}/users/${e.uuid}`)}catch(a){q.value={error:a.message}}});const ue=O(!1);function Ye(e){ue.value=!0}async function We(e,a,i){const v=e.permissions.includes(a);if(i&&v||!i&&!v)return;const b=i?[...e.permissions,a]:e.permissions.filter(U=>U!==a),S=[...e.permissions];e.permissions=b;try{const U=new URLSearchParams({permission_uuid:a});await P(`/auth/api/admin/orgs/${e.uuid}/permission?${U.toString()}`,{method:i?"POST":"DELETE"}),await h()}catch(U){w.showMessage(U.message||"Failed to update organization permission","error"),e.permissions=S}}function Q(e,a){const i=document.activeElement;if(_.value=i,e==="confirm"&&i){const v=i.closest("tr");if(v){const b=v.closest("tbody");if(b){const S=Array.from(b.querySelectorAll("tr")),U=S.indexOf(v);$.value.focusContext={tbody:b,index:U,total:S.length,selector:"button:not([disabled]), a"}}}}$.value={...$.value,type:e,data:a,busy:!1,error:""}}function W(){const e=_.value,a=$.value.focusContext;$.value={type:null,data:null,busy:!1,error:""},Ze(e,a),_.value=null}function Ze(e,a){if(!e)return;if(document.body.contains(e)&&!e.disabled){e.focus();return}if(a?.tbody&&a.selector){const b=Array.from(a.tbody.querySelectorAll("tr"));if(b.length>0){const S=Math.min(a.index,b.length-1),de=b[S]?.querySelector(a.selector);if(de){de.focus();return}}}const i=document.querySelector(".admin-panels");if(!i)return;const v=i.querySelector('button:not([disabled]), a, input:not([disabled]), [tabindex="0"]');v&&v.focus()}function Qe(e){if(f.value)return;const a=J(e);a&&a==="down"&&(e.preventDefault(),ee.value?ee.value.focusFirstElement?.():g.value?g.value.focusFirstElement?.():u.value&&u.value.focusFirstElement?.())}function ce(e){f.value||e==="up"&&Z.value?.focusCurrent?.()}async function we(){if(await h(),H.value)try{q.value=await P(`/auth/api/admin/orgs/${H.value.org}/users/${H.value.uuid}`)}catch(e){w.showMessage(e.message||"Failed to reload user","error")}}async function $e(){await we(),w.showMessage("User renamed","success",1500)}async function Xe(){if(!(!$.value.type||$.value.busy)){$.value.busy=!0,$.value.error="";try{const e=$.value.type;if(e==="org-create"){const a=$.value.data.name?.trim();if(!a)throw new Error("Name required");W(),P("/auth/api/admin/orgs",{method:"POST",body:{display_name:a,permissions:[]}}).then(()=>{w.showMessage(`Organization "${a}" created.`,"success",2500),Promise.all([h(),R()])}).catch(i=>{w.showMessage(i.message||"Failed to create organization","error")});return}else if(e==="org-update"){const{org:a}=$.value.data,i=$.value.data.name?.trim();if(!i)throw new Error("Name required");W(),P(`/auth/api/admin/orgs/${a.uuid}`,{method:"PATCH",body:{display_name:i}}).then(()=>{w.showMessage(`Organization renamed to "${i}".`,"success",2500),h()}).catch(v=>{w.showMessage(v.message||"Failed to update organization","error")});return}else if(e==="role-create"){const{org:a}=$.value.data,i=$.value.data.name?.trim();if(!i)throw new Error("Name required");W(),P(`/auth/api/admin/orgs/${a.uuid}/roles`,{method:"POST",body:{display_name:i,permissions:[]}}).then(()=>{w.showMessage(`Role "${i}" created.`,"success",2500),h()}).catch(v=>{w.showMessage(v.message||"Failed to create role","error")});return}else if(e==="role-update"){const{role:a}=$.value.data,i=$.value.data.name?.trim();if(!i)throw new Error("Name required");W(),P(`/auth/api/admin/orgs/${a.org}/roles/${a.uuid}`,{method:"PATCH",body:{display_name:i}}).then(()=>{w.showMessage(`Role renamed to "${i}".`,"success",2500),h()}).catch(v=>{w.showMessage(v.message||"Failed to update role","error")});return}else if(e==="user-create"){const{org:a,role:i}=$.value.data,v=$.value.data.name?.trim();if(!v)throw new Error("Name required");W(),P(`/auth/api/admin/orgs/${a.uuid}/users`,{method:"POST",body:{display_name:v,role:i.display_name}}).then(()=>{w.showMessage(`User "${v}" added to ${i.display_name} role.`,"success",2500),h()}).catch(b=>{w.showMessage(b.message||"Failed to add user","error")});return}else if(e==="user-update-name"){const{user:a}=$.value.data,i=$.value.data.name?.trim();if(!i)throw new Error("Name required");W(),P(`/auth/api/admin/orgs/${a.org}/users/${a.uuid}/display-name`,{method:"PATCH",body:{display_name:i}}).then(()=>{w.showMessage(`User renamed to "${i}".`,"success",2500),$e()}).catch(v=>{w.showMessage(v.message||"Failed to update user name","error")});return}else if(e==="perm-display"){const{permission:a}=$.value.data,i=$.value.data.scope?.trim(),v=$.value.data.display_name?.trim(),b=$.value.data.domain?.trim()||"";if(!v)throw new Error("Display name required");if(!i)throw new Error("Scope required");W();const S=a.domain||"";if(i===a.scope&&v===a.display_name&&b===S)return;const U=new URLSearchParams({permission_uuid:a.uuid});i!==a.scope&&U.set("scope",i),v!==a.display_name&&U.set("display_name",v),b!==S&&U.set("domain",b||""),P(`/auth/api/admin/permission?${U.toString()}`,{method:"PATCH"}).then(()=>{w.showMessage(`Permission "${v}" updated.`,"success",2500),R()}).catch(de=>{w.showMessage(de.message||"Failed to update permission","error")});return}else if(e==="perm-create"){const a=$.value.data.scope?.trim();if(!a)throw new Error("Scope required");const i=$.value.data.display_name?.trim();if(!i)throw new Error("Display name required");const v=$.value.data.domain?.trim()||"";W(),P("/auth/api/admin/permissions",{method:"POST",body:{scope:a,display_name:i,domain:v||void 0}}).then(()=>{w.showMessage(`Permission "${i}" created.`,"success",2500),R()}).catch(b=>{w.showMessage(b.message||"Failed to create permission","error")});return}else if(e==="confirm"){const a=$.value.data.action;if(W(),a)try{await a()}catch(i){w.showMessage(i.message||"Action failed","error")}return}W()}catch(e){$.value.error=e.message||"Error"}finally{$.value.busy=!1}}}return(e,a)=>(m(),p("div",Rs,[ae(mt),o("main",Cs,[Y.value?(m(),G(ft,{key:0,message:k.value},null,8,["message"])):E.value?(m(),G(fe,{key:1})):c.value?(m(),G(fe,{key:2,icon:"⚠️",title:"Error",message:c.value},null,8,["message"])):L.value&&!r.value&&!t.value?(m(),G(fe,{key:3,icon:"⛔",message:"You do not have admin permissions for this application."})):L.value&&(r.value||t.value)?(m(),p("section",As,[o("header",Us,[o("h1",null,A(Ge.value),1),ae(gt,{ref_key:"breadcrumbsRef",ref:Z,entries:Je.value,onKeydown:Qe},null,8,["entries"])]),o("section",Es,[o("div",Ms,[o("div",Ps,[!H.value&&!se.value&&(r.value||t.value)?(m(),G(Kt,{key:0,ref_key:"adminOverviewRef",ref:ee,info:V.value,orgs:y.value,permissions:x.value,"navigation-disabled":f.value,"permission-summary":D.value,onCreateOrg:Me,onOpenOrg:be,onUpdateOrg:ye,onDeleteOrg:xe,onToggleOrgPermission:We,onOpenDialog:Q,onDeletePermission:Ve,onRenamePermissionDisplay:l,onNavigateOut:ce},null,8,["info","orgs","permissions","navigation-disabled","permission-summary"])):H.value?(m(),G(ms,{key:1,ref_key:"adminUserDetailRef",ref:u,"selected-user":H.value,"user-detail":q.value,"selected-org":se.value,loading:Y.value,"show-reg-modal":ue.value,"navigation-disabled":f.value,onGenerateUserRegistrationLink:Ye,onGoOverview:je,onOpenOrg:be,onOnUserNameSaved:$e,onRefreshUserDetail:we,onEditUserName:Pe,onCloseRegModal:a[0]||(a[0]=i=>ue.value=!1),onNavigateOut:ce},null,8,["selected-user","user-detail","selected-org","loading","show-reg-modal","navigation-disabled"])):se.value?(m(),G(as,{key:2,ref_key:"adminOrgDetailRef",ref:g,"selected-org":se.value,permissions:x.value,"navigation-disabled":f.value,onUpdateOrg:ye,onCreateRole:Ie,onUpdateRole:Ke,onDeleteRole:Be,onCreateUserInRole:qe,onOpenUser:He,onToggleRolePermission:ze,onOnRoleDragOver:Le,onNavigateOut:ce,onOnRoleDrop:Fe,onOnUserDragStart:Te},null,8,["selected-org","permissions","navigation-disabled"])):T("",!0)])])])])):T("",!0)]),ae(Os,{dialog:$.value,"permission-id-pattern":xs,settings:ge(w).settings,onSubmitDialog:Xe,onCloseDialog:W},null,8,["dialog","settings"])]))}},Ns=re(qs,[["__scopeId","data-v-6d820553"]]);it();const Ue=ot(Ns);Ue.use(vt());Ue.mount("#admin-app");at();
@@ -1 +1 @@
1
- .pairing-entry[data-v-77a073d9]{display:flex;flex-direction:column;gap:1rem}.pairing-form[data-v-77a073d9]{display:flex;flex-direction:column;gap:.5rem}.input-row[data-v-77a073d9]{display:flex;align-items:center;gap:.5rem}.input-wrapper[data-v-77a073d9]{position:relative;display:flex;width:280px;max-width:100%}.slot-machine[data-v-77a073d9]{position:absolute;left:0;top:0;width:100%;height:100%;gap:0;box-sizing:border-box;z-index:1;pointer-events:none}.input-wrapper.focused.has-error .slot-machine[data-v-77a073d9]{background:var(--color-error-bg, rgba(239, 68, 68, .05))}.slot-reel[data-v-77a073d9]{flex:1 1 33.333%;overflow:visible}.slot-reel[data-v-77a073d9]:not(:last-child){margin-right:.5rem}.slot-word[data-v-77a073d9]{font-weight:600;letter-spacing:.05em;text-align:center;width:100%;color:var(--color-text);display:flex;align-items:center;justify-content:center;position:relative}.slot-word .typed-prefix[data-v-77a073d9]{color:var(--color-text)}.slot-word .hint-suffix[data-v-77a073d9]{color:var(--color-text-muted);opacity:.6}.cursor-overlay[data-v-77a073d9]{position:absolute;width:2px;height:1.2em;background:var(--color-text);animation:none;pointer-events:none;left:calc(50% + (var(--cursor-pos) - var(--word-len, 0) / 2) * .65em);transform:translate(-1px);opacity:0}.input-wrapper.focused .cursor-overlay[data-v-77a073d9]{opacity:1;animation:cursorBlink-77a073d9 .25s alternate infinite}.input-wrapper.focused.has-selection .cursor-overlay[data-v-77a073d9]{animation:none}.selection-overlay[data-v-77a073d9]{position:absolute;height:1.2em;background:var(--color-primary, #3b82f6);opacity:.3;pointer-events:none;left:calc(50% + (var(--sel-start) - var(--word-len, 0) / 2) * .65em);width:calc((var(--sel-end) - var(--sel-start)) * .65em)}@keyframes cursorBlink-77a073d9{0%,50%{opacity:1}80%,to{opacity:0}}.slot-reel.invalid-word .slot-word[data-v-77a073d9],.slot-reel.invalid-word .slot-word .typed-prefix[data-v-77a073d9]{color:var(--color-error, #ef4444)}.slot-reel.invalid-word .cursor-overlay[data-v-77a073d9]{background:var(--color-error, #ef4444)}.slot-reel.empty .slot-word[data-v-77a073d9]{color:var(--color-text-muted)}.pairing-input[data-v-77a073d9]{flex:1;width:100%;height:100%;border-radius:var(--radius-sm, 6px);position:relative;z-index:0}.pairing-input.hidden-input[data-v-77a073d9]{opacity:0}.pairing-input[data-v-77a073d9]:disabled{cursor:not-allowed}.pairing-input[data-v-77a073d9]::placeholder{color:transparent}.processing-status[data-v-77a073d9]{display:flex;align-items:center;gap:.25rem;font-size:.875rem;color:var(--color-text-muted)}.processing-icon[data-v-77a073d9]{font-size:.875rem}.processing-spinner-small[data-v-77a073d9]{width:12px;height:12px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin-77a073d9 .8s linear infinite}@keyframes spin-77a073d9{to{transform:rotate(360deg)}}.device-info[data-v-77a073d9]{display:flex;flex-direction:column;gap:.5rem}.device-permit-text[data-v-77a073d9]{margin:0;font-size:.95rem;color:var(--color-text)}.device-meta[data-v-77a073d9]{margin:0;font-size:.8rem;color:var(--color-text-muted);font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,Courier New,monospace}.error-message[data-v-77a073d9]{margin:0;font-size:.875rem;color:var(--color-error, #ef4444);margin-bottom:1rem}.view-lede[data-v-89d5d936]{margin:0;color:var(--color-text-muted);font-size:1rem}.section-header[data-v-89d5d936]{display:flex;flex-direction:column;gap:.4rem}.empty-state[data-v-89d5d936]{margin:0;color:var(--color-text-muted);text-align:center;padding:1rem 0}.logout-note[data-v-89d5d936]{margin:.75rem 0 0;color:var(--color-text-muted);font-size:.875rem}.remote-auth-inline[data-v-89d5d936]{display:flex;flex-direction:column;gap:.5rem}.remote-auth-label[data-v-89d5d936]{display:block;margin:0;font-size:.875rem;color:var(--color-text-muted);font-weight:500}.remote-auth-description[data-v-89d5d936]{font-size:.75rem;color:var(--color-text-muted)}.host-view[data-v-237999a2]{padding:3rem 1.5rem 4rem}.host-actions[data-v-237999a2]{display:flex;flex-direction:column;gap:.75rem}.host-actions .button-row[data-v-237999a2]{gap:.75rem;flex-wrap:wrap}.host-actions .button-row button[data-v-237999a2]{flex:1 1 0}.note[data-v-237999a2],.empty-state[data-v-237999a2]{margin:0;color:var(--color-text-muted)}
1
+ .pairing-entry[data-v-77a073d9]{display:flex;flex-direction:column;gap:1rem}.pairing-form[data-v-77a073d9]{display:flex;flex-direction:column;gap:.5rem}.input-row[data-v-77a073d9]{display:flex;align-items:center;gap:.5rem}.input-wrapper[data-v-77a073d9]{position:relative;display:flex;width:280px;max-width:100%}.slot-machine[data-v-77a073d9]{position:absolute;left:0;top:0;width:100%;height:100%;gap:0;box-sizing:border-box;z-index:1;pointer-events:none}.input-wrapper.focused.has-error .slot-machine[data-v-77a073d9]{background:var(--color-error-bg, rgba(239, 68, 68, .05))}.slot-reel[data-v-77a073d9]{flex:1 1 33.333%;overflow:visible}.slot-reel[data-v-77a073d9]:not(:last-child){margin-right:.5rem}.slot-word[data-v-77a073d9]{font-weight:600;letter-spacing:.05em;text-align:center;width:100%;color:var(--color-text);display:flex;align-items:center;justify-content:center;position:relative}.slot-word .typed-prefix[data-v-77a073d9]{color:var(--color-text)}.slot-word .hint-suffix[data-v-77a073d9]{color:var(--color-text-muted);opacity:.6}.cursor-overlay[data-v-77a073d9]{position:absolute;width:2px;height:1.2em;background:var(--color-text);animation:none;pointer-events:none;left:calc(50% + (var(--cursor-pos) - var(--word-len, 0) / 2) * .65em);transform:translate(-1px);opacity:0}.input-wrapper.focused .cursor-overlay[data-v-77a073d9]{opacity:1;animation:cursorBlink-77a073d9 .25s alternate infinite}.input-wrapper.focused.has-selection .cursor-overlay[data-v-77a073d9]{animation:none}.selection-overlay[data-v-77a073d9]{position:absolute;height:1.2em;background:var(--color-primary, #3b82f6);opacity:.3;pointer-events:none;left:calc(50% + (var(--sel-start) - var(--word-len, 0) / 2) * .65em);width:calc((var(--sel-end) - var(--sel-start)) * .65em)}@keyframes cursorBlink-77a073d9{0%,50%{opacity:1}80%,to{opacity:0}}.slot-reel.invalid-word .slot-word[data-v-77a073d9],.slot-reel.invalid-word .slot-word .typed-prefix[data-v-77a073d9]{color:var(--color-error, #ef4444)}.slot-reel.invalid-word .cursor-overlay[data-v-77a073d9]{background:var(--color-error, #ef4444)}.slot-reel.empty .slot-word[data-v-77a073d9]{color:var(--color-text-muted)}.pairing-input[data-v-77a073d9]{flex:1;width:100%;height:100%;border-radius:var(--radius-sm, 6px);position:relative;z-index:0}.pairing-input.hidden-input[data-v-77a073d9]{opacity:0}.pairing-input[data-v-77a073d9]:disabled{cursor:not-allowed}.pairing-input[data-v-77a073d9]::placeholder{color:transparent}.processing-status[data-v-77a073d9]{display:flex;align-items:center;gap:.25rem;font-size:.875rem;color:var(--color-text-muted)}.processing-icon[data-v-77a073d9]{font-size:.875rem}.processing-spinner-small[data-v-77a073d9]{width:12px;height:12px;border:2px solid var(--color-border);border-top-color:var(--color-primary);border-radius:50%;animation:spin-77a073d9 .8s linear infinite}@keyframes spin-77a073d9{to{transform:rotate(360deg)}}.device-info[data-v-77a073d9]{display:flex;flex-direction:column;gap:.5rem}.device-permit-text[data-v-77a073d9]{margin:0;font-size:.95rem;color:var(--color-text)}.device-meta[data-v-77a073d9]{margin:0;font-size:.8rem;color:var(--color-text-muted);font-family:SF Mono,Monaco,Cascadia Code,Roboto Mono,Consolas,Courier New,monospace}.error-message[data-v-77a073d9]{margin:0;font-size:.875rem;color:var(--color-error, #ef4444);margin-bottom:1rem}.view-lede[data-v-2cc35e77]{margin:0;color:var(--color-text-muted);font-size:1rem}.section-header[data-v-2cc35e77]{display:flex;flex-direction:column;gap:.4rem}.empty-state[data-v-2cc35e77]{margin:0;color:var(--color-text-muted);text-align:center;padding:1rem 0}.logout-note[data-v-2cc35e77]{margin:.75rem 0 0;color:var(--color-text-muted);font-size:.875rem}.remote-auth-inline[data-v-2cc35e77]{display:flex;flex-direction:column;gap:.5rem}.remote-auth-label[data-v-2cc35e77]{display:block;margin:0;font-size:.875rem;color:var(--color-text-muted);font-weight:500}.remote-auth-description[data-v-2cc35e77]{font-size:.75rem;color:var(--color-text-muted)}.theme-toggle[data-v-2cc35e77]{position:absolute;top:var(--layout-padding);right:var(--layout-padding)}.theme-btn[data-v-2cc35e77]{background:none;border:none;padding:.25rem;font-size:1.25rem;cursor:pointer;opacity:.5;transition:opacity .15s}.theme-btn[data-v-2cc35e77]:hover{opacity:.8}.theme-menu[data-v-2cc35e77]{position:absolute;top:100%;right:0;width:5rem;height:4rem;margin-top:.25rem}.theme-option[data-v-2cc35e77]{position:absolute;background:none;border:none;font-size:1.25rem;cursor:pointer;opacity:.5;padding:.25rem;border-radius:var(--radius-sm);transition:opacity .15s,transform .15s}.theme-option[data-v-2cc35e77]:hover{opacity:1;transform:scale(1.2)}.theme-option.active[data-v-2cc35e77]{opacity:1}.theme-option.top[data-v-2cc35e77]{top:0;left:50%;transform:translate(-50%)}.theme-option.top[data-v-2cc35e77]:hover{transform:translate(-50%) scale(1.2)}.theme-option.left[data-v-2cc35e77]{bottom:0;left:0}.theme-option.right[data-v-2cc35e77]{bottom:0;right:0}.host-view[data-v-237999a2]{padding:3rem 1.5rem 4rem}.host-actions[data-v-237999a2]{display:flex;flex-direction:column;gap:.75rem}.host-actions .button-row[data-v-237999a2]{gap:.75rem;flex-wrap:wrap}.host-actions .button-row button[data-v-237999a2]{flex:1 1 0}.note[data-v-237999a2],.empty-state[data-v-237999a2]{margin:0;color:var(--color-text-muted)}
@@ -0,0 +1 @@
1
+ import{_ as Ke,w as He,o as Se,n as Ie,a as $e,b as f,c as m,d as a,e as ie,F as he,r as it,f as Ne,g as B,t as W,h as F,i as ut,v as ct,j as pe,k as c,l as dt,m as A,s as ft,p as vt,q as re,u as k,x as Q,y as Ye,z as Re,A as ge,B as Te,C as ht,D as pt,E as Ce,G as Ue,H as gt,N as mt,O as xe,I as yt,J as wt,K as bt}from"./_plugin-vue_export-helper-DJsHCwvl.js";import{u as kt,i as _t}from"./theme-C2WysaSw.js";import{u as Ae,B as It,U as et,_ as xt,a as Ct,N as St,M as $t,R as Lt,L as At,b as Wt,A as Pt,c as Mt}from"./AccessDenied-Licr0tqA.js";import{g as Ze,i as te,a as Ge,b as Je,c as Qe,s as Xe}from"./pow-DUr-T9XX.js";import{g as Le}from"./helpers-DzjFIx78.js";const Ve={};async function Et(T="login"){if(Ve[T])return Ve[T];const o=await fetch("/auth/api/forward");if(o.status===401||o.status===403){const $=await o.json();if($.auth?.iframe){let _=$.auth.iframe;return T!==$.auth.mode&&(_=_.replace(/mode=[^&]*/,`mode=${T}`)),Ve[T]=_,_}}throw new Error("Unable to fetch auth iframe URL")}const Dt={class:"pairing-entry"},Bt={key:0,class:"input-row"},Nt={class:"slot-word"},Tt={class:"typed-prefix"},Ut={class:"hint-suffix"},Vt={key:0,class:"cursor-overlay",style:{"--cursor-pos":0,"--word-len":0}},Ht=["placeholder"],Rt={key:0,class:"processing-status"},Kt={class:"processing-icon"},zt={key:1,class:"device-info"},Ft={class:"device-permit-text"},jt={class:"device-meta"},Ot={key:0,class:"error-message",style:{"margin-top":"0.5rem"}},qt={class:"button-row",style:{"margin-top":"0.75rem",display:"flex",gap:"0.5rem"}},Yt=["disabled"],Zt=["disabled"],Gt={__name:"RemoteAuthPermit",props:{title:{type:String,default:"Help Another Device Sign In"},description:{type:String,default:"Enter the code shown on the device that needs to sign in."},placeholder:{type:String,default:"Enter three words"},action:{type:String,default:"login"}},emits:["completed","error","cancelled","back","register","deviceInfoVisible"],setup(T,{expose:o,emit:$}){const _=$,I=c(!1),C=c(null),U=c(null);let d=null,V=null;try{V=Ae()}catch{}const E=c(null),j=c(null),u=c(""),g=c(!1),y=c(""),h=c(null),p=c("");He(h,e=>{_("deviceInfoVisible",!!e)});const w=c(!1),P=c(!1),L=c(0),H=c(0),X=c(0),ue=c(!1),se=c(!1);let R=0,ne=!1,G=null,oe=null,q=null,N=null,Y=null,J=null;function ae(e,n="info",s=3e3){V&&V.showMessage(e,n,s)}async function me(){try{const e=await dt();U.value=e}catch(e){console.warn("Unable to load settings",e)}}function ye(e,n){if(!e||n<0)return{word:"",start:0,end:0};let s=n,i=n;for(;s>0&&/[a-zA-Z]/.test(e[s-1]);)s--;for(;i<e.length&&/[a-zA-Z]/.test(e[i]);)i++;return{word:e.slice(s,i),start:s,end:i}}function le(e){return e.trim().split(/[.\s]+/).filter(n=>n.length>0)}function D(e){const n=le(e),s=[];for(const i of n){let r=i.toLowerCase();for(;r.length>0&&s.length<3;){let S=null;for(let b=Math.min(r.length,6);b>=3;b--){const x=r.slice(0,b);if(te(x)){S=x;break}}if(S)s.push(S),r=r.slice(S.length);else{s.push(r);break}}if(s.length>=3)break}return s}function We(e){const n=/[.\s]$/.test(e),s=D(e);return n?s.length:Math.max(0,s.length-1)}function ce(e){if(!e)return{valid:!0,segments:[]};const n=[],s=/[.\s]$/.test(e);let i,r=/([a-zA-Z]+)|([.\s]+)/g;for(;(i=r.exec(e))!==null;)i[1]?n.push({text:i[1],isWord:!0,start:i.index}):i[2]&&n.push({text:i[2],isWord:!1,start:i.index});const S=n.filter(x=>x.isWord);let b=!0;return S.forEach((x,K)=>{const M=K===S.length-1,Z=x.text.toLowerCase();M&&!s?x.invalid=!Ge(Z):x.invalid=!te(Z),x.invalid&&(b=!1)}),{valid:b,segments:n}}function we(e){return ce(e).valid}function ee(e){return D(e).length>0&&D(e).every(n=>te(n))}function Pe(e){if(/[.\s]$/.test(e))return"";const s=e.match(/[a-zA-Z]+$/);return s?s[0].toLowerCase():""}function be(e,n){if(!e||n===0)return{wordIndex:0,charIndex:0};const s=e.slice(0,n),i=/[.\s]$/.test(s),r=D(s);if(r.length===0)return{wordIndex:0,charIndex:0};if(i)return{wordIndex:Math.min(r.length,2),charIndex:0};const S=r[r.length-1],b=r.length-1;D(e);const x=S.length;return b<2&&!se.value&&te(S)?{wordIndex:b+1,charIndex:0}:{wordIndex:Math.min(b,2),charIndex:x}}function de(e,n){if(!e||n===0)return{wordIndex:0,charIndex:0};const s=e.slice(0,n),i=/[.\s]$/.test(s),r=D(s);if(r.length===0)return{wordIndex:0,charIndex:0};if(i)return{wordIndex:Math.min(r.length,2),charIndex:0};const S=r[r.length-1],b=r.length-1;return{wordIndex:Math.min(b,2),charIndex:S.length}}const Me=A(()=>{const e=D(u.value),n=[],s=Pe(u.value),i=p.value,r=/[.\s]$/.test(u.value),S=H.value!==X.value,b=de(u.value,Math.min(H.value,X.value)),x=de(u.value,Math.max(H.value,X.value)),K=S?de(u.value,L.value):be(u.value,L.value);for(let M=0;M<3;M++){const Z=K.wordIndex===M;let O=-1,z=-1;if(S&&(M>b.wordIndex&&M<x.wordIndex?(O=0,z=e[M]?.length??0):M===b.wordIndex&&M===x.wordIndex?(O=b.charIndex,z=x.charIndex):M===b.wordIndex?(O=b.charIndex,z=e[M]?.length??0):M===x.wordIndex&&(O=0,z=x.charIndex)),M<e.length){const _e=e[M].toLowerCase(),Oe=M===e.length-1,qe=Oe&&!r?!Ge(_e):!te(_e);if(Oe&&!r&&i&&s){const rt=i.length;n.push({text:"",typedPrefix:s,hintSuffix:i.slice(s.length),invalid:qe,hasCursor:Z,cursorCharIndex:Z?K.charIndex:-1,wordLen:rt,selectionStartChar:O,selectionEndChar:z})}else n.push({text:_e,invalid:qe,hasCursor:Z,cursorCharIndex:Z?K.charIndex:-1,wordLen:_e.length,selectionStartChar:O,selectionEndChar:z})}else n.push({text:"",invalid:!1,hasCursor:Z,cursorCharIndex:0,wordLen:0,selectionStartChar:O,selectionEndChar:z})}return n}),Ee=A(()=>H.value!==X.value),fe=A(()=>{const e=D(u.value);return e.length===3&&e.every(n=>te(n))});function ke(e){return D(e).join(".")}function De(){if(!G||q)return;const e=Qe(G);q=Xe(e,oe).then(n=>{N=n,q=null})}async function l(){if(N){const n=N;return N=null,n}if(q){await q;const n=N;return N=null,n}if(!G)throw new Error("No PoW challenge available");const e=Qe(G);return await Xe(e,oe)}function t(e){e?.challenge&&(G=e.challenge,oe=e.work,N=null,q=null,De())}async function v(){if(!(d||ne)){ne=!0;try{const e=U.value?.auth_host,n="/auth/ws/remote-auth/permit",s=e&&location.host!==e?`//${e}${n}`:n;d=await vt(s);const i=await d.receive_json();if(i.status&&i.detail)throw new Error(i.detail);if(!i.pow?.challenge)throw new Error("Server did not send PoW challenge");t(i.pow)}catch(e){throw console.error("WebSocket connection error:",e),d=null,e}finally{ne=!1}}}function st(e){if(e.key==="Tab"||e.key===" "||e.key==="Escape"){at(e);return}setTimeout(ze,0)}function ze(){const e=E.value,n=e?.selectionStart??u.value.length,s=e?.selectionEnd??n,r=(e?.selectionDirection??"none")==="backward"?n:s;se.value=r<R,R=r,L.value=r,X.value=s,H.value=n}function nt(){L.value=E.value?.selectionStart??u.value.length;const{word:e,end:n}=ye(u.value,L.value);if(We(u.value)>=3||!e||e.length<1||L.value!==n){p.value="";return}const i=Ze(e.toLowerCase());i&&i!==e.toLowerCase()?p.value=i:p.value=""}function Fe(){if(!p.value)return!1;const{word:e,start:n,end:s}=ye(u.value,L.value);if(!e)return!1;const i=u.value.slice(0,n),b=D(i).length===2?"":" ",x=u.value.slice(s);u.value=i+p.value+b+x.trimStart();const K=n+p.value.length+b.length;return Ie(()=>{E.value?.setSelectionRange(K,K),L.value=K}),p.value="",!0}function ve(){L.value=E.value?.selectionStart??u.value.length;const e=L.value,s=u.value.slice(0,e).match(/([a-zA-Z]+) $/);if(s){const r=s[1].toLowerCase(),S=Ze(r);if(S&&S!==r&&!te(r)){const b=e-s[0].length,x=u.value.slice(0,b),K=u.value.slice(e),O=D(x).length===2?"":" ";u.value=x+S+O+K;const z=b+S.length+O.length;Ie(()=>{E.value?.setSelectionRange(z,z),L.value=z})}}nt(),Y&&(clearTimeout(Y),Y=null),h.value=null,C.value=null,P.value=!1,w.value=!we(u.value);const i=D(u.value);if(i.length>=1&&!d&&!ne&&v(),i.length===3){if(!ee(u.value))return;Y=setTimeout(()=>{ot()},150)}}async function ot(){if(!(g.value||I.value||!fe.value||ke(u.value)===J&&h.value)){g.value=!0,y.value="pow",C.value=null,P.value=!1;try{if(await v(),!d)throw new Error("Failed to connect");const n=await l(),s=Je(n),i=ke(u.value);if(!fe.value)return;y.value="server",d.send_json({code:i,pow:s});const r=await d.receive_json();if(t(r.pow),typeof r.status=="number"&&r.status>=400){ae(r.detail||"Request failed","error"),P.value=!0,h.value=null,J=null;return}r.status==="found"&&r.host?(h.value={host:r.host,user_agent_pretty:r.user_agent_pretty,client_ip:r.client_ip,action:r.action||"login"},J=i,Ie(()=>{j.value?.focus()})):(ae("Unexpected response from server","error"),P.value=!0,h.value=null,J=null)}catch(n){console.error("Lookup error:",n),ae(n.message||"Lookup failed","error"),P.value=!0,h.value=null,J=null,d&&(d.close(),d=null)}finally{g.value=!1,y.value=""}}}function at(e){if(e.key==="Escape"){u.value="",ve(),e.preventDefault();return}if(e.key==="Tab"){if(p.value&&Fe()){e.preventDefault(),ve();return}u.value.trim()&&e.preventDefault();return}e.key===" "&&p.value&&Fe()&&(e.preventDefault(),ve())}async function lt(){if(!(!h.value||I.value)){I.value=!0,C.value=null;try{if(d||await v(),!d)throw new Error("Failed to connect");const e=await l(),n=Je(e);d.send_json({authenticate:!0,pow:n});const s=await d.receive_json();if(typeof s.status=="number"&&s.status>=400)throw new Error(s.detail||"Authentication failed");if(!s.optionsJSON)throw new Error(s.detail||"Failed to get authentication options");const i=await ft(s);d.send_json(i);const r=await d.receive_json();if(typeof r.status=="number"&&r.status>=400)throw new Error(r.detail||"Authentication failed");if(r.status==="success")ae("Device authenticated successfully!","success",3e3),_("completed"),Be();else throw new Error(r.detail||"Authentication failed")}catch(e){console.error("Pairing error:",e);const n=e.name==="NotAllowedError"?"Passkey authentication was cancelled":e.message||"Authentication failed";C.value=n,_("error",n)}finally{I.value=!1,d&&(d.close(),d=null)}}}async function je(){if(d){try{d.send_json({deny:!0}),await new Promise(e=>setTimeout(e,100))}catch(e){console.error("Error sending deny message:",e)}d.close(),d=null}Be()}function Be(){u.value="",C.value=null,P.value=!1,h.value=null,g.value=!1,y.value="",p.value="",w.value=!1,J=null,d&&(d.close(),d=null),G=null,oe=null,q=null,N=null}return Se(async()=>{await me(),Ie(()=>{L.value=E.value?.selectionStart??0})}),$e(()=>{Y&&(clearTimeout(Y),Y=null),d&&(d.close(),d=null)}),o({reset:Be,deny:je,code:u,handleInput:ve,loading:I,error:C}),(e,n)=>(f(),m("div",Dt,[a("form",{onSubmit:pe(lt,["prevent"]),class:"pairing-form"},[h.value?h.value?(f(),m("div",zt,[a("p",Ft,[F("Permit "+W(h.value.action==="register"?"registration":"login")+" to ",1),a("strong",null,W(h.value.host),1)]),a("p",jt,W(h.value.user_agent_pretty),1),C.value?(f(),m("p",Ot,W(C.value),1)):B("",!0),a("div",qt,[a("button",{type:"button",class:"btn-secondary",disabled:I.value,onClick:je,style:{flex:"1"}}," Deny ",8,Yt),a("button",{ref_key:"submitBtnRef",ref:j,type:"submit",disabled:I.value,class:"btn-primary",style:{flex:"1"}},W(I.value?"Authenticating…":"Authorize"),9,Zt)])])):B("",!0):(f(),m("div",Bt,[a("div",{class:ie(["input-wrapper",{"has-error":P.value,"is-complete":h.value&&!P.value,focused:ue.value,"has-selection":Ee.value}])},[a("div",{class:ie(["slot-machine",{"has-error":P.value,"is-complete":h.value&&!P.value}]),"aria-hidden":"true"},[(f(!0),m(he,null,it(Me.value,(s,i)=>(f(),m("div",{key:i,class:ie(["slot-reel",{"invalid-word":s.invalid,empty:!s.text&&!s.typedPrefix}])},[a("div",Nt,[s.selectionStartChar>=0&&s.selectionEndChar>s.selectionStartChar?(f(),m("span",{key:0,class:"selection-overlay",style:Ne({"--sel-start":s.selectionStartChar,"--sel-end":s.selectionEndChar,"--word-len":s.wordLen})},null,4)):B("",!0),s.typedPrefix?(f(),m(he,{key:1},[a("span",Tt,W(s.typedPrefix),1),a("span",Ut,W(s.hintSuffix),1),s.hasCursor?(f(),m("span",{key:0,class:"cursor-overlay",style:Ne({"--cursor-pos":s.cursorCharIndex,"--word-len":s.wordLen})},null,4)):B("",!0)],64)):s.text?(f(),m(he,{key:2},[F(W(s.text)+" ",1),s.hasCursor?(f(),m("span",{key:0,class:"cursor-overlay",style:Ne({"--cursor-pos":s.cursorCharIndex,"--word-len":s.wordLen})},null,4)):B("",!0)],64)):(f(),m(he,{key:3},[s.hasCursor?(f(),m("span",Vt)):B("",!0)],64))])],2))),128))],2),ut(a("input",{ref_key:"inputRef",ref:E,"onUpdate:modelValue":n[0]||(n[0]=s=>u.value=s),type:"text",placeholder:T.placeholder,autocomplete:"off",autocapitalize:"none",autocorrect:"off",spellcheck:"false",class:"pairing-input hidden-input",onInput:ve,onKeydown:st,onMouseup:ze,onFocus:n[1]||(n[1]=s=>ue.value=!0),onBlur:n[2]||(n[2]=s=>ue.value=!1)},null,40,Ht),[[ct,u.value]])],2),y.value?(f(),m("div",Rt,[a("span",Kt,W(y.value==="pow"?"🔐":"📡"),1),n[3]||(n[3]=a("span",{class:"processing-spinner-small"},null,-1))])):B("",!0)]))],32)]))}},Jt=Ke(Gt,[["__scopeId","data-v-77a073d9"]]),Qt={class:"view-root","data-view":"profile"},Xt={class:"theme-toggle"},es=["title"],ts={class:"view-header"},ss={class:"remote-auth-inline"},ns={key:0,class:"remote-auth-label"},os={class:"section-block"},as={class:"section-body"},ls={class:"section-block"},rs=["disabled"],is=["disabled"],us=["disabled"],cs={key:0,class:"logout-note"},ds={key:1,class:"logout-note"},fs={__name:"ProfileView",setup(T){const o=Ae(),$=c(null),_=c(!1),I=c(!1),C=c(""),U=c(!1),d=c(null),V=c(null),E=c(!1),j=c(null),u=c(null),g=c(null),y=c(null),h=c(null),p=c(null),w=c(null),P=c(null),L=c(""),H=c(!1),X=A(()=>({"":"🌓",light:"☀️",dark:"🌙"})[L.value]||"🌓"),ue=A(()=>({"":"Auto (system)",light:"Light mode",dark:"Dark mode"})[L.value]||"Theme");He(()=>o.userInfo?.ctx?.user?.theme,l=>{L.value=l||""},{immediate:!0});function se(l){L.value=l,H.value=!1,kt({user:{theme:l}},!0),Re("/auth/api/user/theme",{method:"PATCH",body:{theme:l}}).catch(t=>o.showMessage(t.message,"error"))}const R=A(()=>_.value||I.value);He(_,l=>{l&&(C.value=o.userInfo?.ctx.user.display_name??"")}),Se(()=>{$.value=setInterval(()=>{o.userInfo&&(o.userInfo={...o.userInfo})},6e4)}),$e(()=>{$.value&&clearInterval($.value)});const ne=async()=>{try{await gt.register(null,null,()=>{o.showMessage("Adding new passkey...","info")}),await o.loadUserInfo(),o.showMessage("New passkey added successfully!","success",3e3)}catch(l){console.error("Failed to add new passkey:",l),o.showMessage(l.message,"error")}},G=()=>{o.showMessage("The other device is now signed in!","success",4e3),setTimeout(()=>j.value?.reset(),3e3)},oe=l=>{l.includes("cancelled")||o.showMessage(l,"error",4e3)},q=()=>{o.showMessage("📋 Link copied! Send it to your other device."),I.value=!1},N=l=>{Te(l,{primarySelector:".btn-primary",itemSelector:"button"})},Y=l=>{if(R.value)return;const t=ge(l);t&&t==="down"&&(l.preventDefault(),Te(P.value,{primarySelector:".mini-btn",itemSelector:".mini-btn, .pairing-input"}))},J=l=>{if(R.value)return;const t=ge(l);if(!t)return;l.preventDefault(),t==="left"||t==="right"?Ce(P.value,l.target,t,{itemSelector:".mini-btn, .pairing-input"}):t==="up"?p.value?.focusCurrent?.():t==="down"&&u.value?.$el?.focus()},ae=l=>{R.value||(l==="down"||l==="right"?N(g.value):(l==="up"||l==="left")&&Te(P.value,{primarySelector:".mini-btn",itemSelector:".mini-btn, .pairing-input"}))},me=l=>{if(R.value)return;const t=ge(l);t&&(l.preventDefault(),t==="left"||t==="right"?Ce(g.value,l.target,t,{itemSelector:"button"}):t==="up"?Ue(u.value?.$el,0,{itemSelector:".credential-item"}):t==="down"&&Ue(y.value?.$el,0,{itemSelector:".session-group"}))},ye=l=>{R.value||(l==="up"?N(g.value):l==="down"&&N(h.value))},le=l=>{if(R.value)return;const t=ge(l);t&&(l.preventDefault(),t==="left"||t==="right"?Ce(h.value,l.target,t,{itemSelector:"button"}):t==="up"&&Ue(y.value?.$el,-1,{itemSelector:".session-group"}))},D=async l=>{const t=l?.credential;if(t)try{await o.deleteCredential(t),o.showMessage("Passkey deleted! You should also remove it from your password manager or device.","success",3e3)}catch(v){o.showMessage(`Failed to delete passkey: ${v.message}`,"error")}},We=A(()=>o.settings?.rp_name||"this service"),ce=A(()=>o.userInfo?.sessions||[]),we=A(()=>ce.value.find(t=>t.is_current)?.host||"this host"),ee=c({}),Pe=async l=>{const t=l?.id;if(t){ee.value={...ee.value,[t]:!0};try{await o.terminateSession(t)}catch(v){o.showMessage(v.message||"Failed to terminate session","error",5e3)}finally{const v={...ee.value};delete v[t],ee.value=v}}},be=async()=>{await o.logoutEverywhere()},de=async()=>{await o.logout()},Me=()=>{C.value=o.userInfo?.ctx.user.display_name??"",_.value=!0},Ee=A(()=>{const l=o.userInfo?.ctx.permissions;return l.includes("auth:admin")||l.includes("auth:org:admin")}),fe=A(()=>ce.value.length>1),ke=A(()=>{const l=[{label:"Auth",href:ht()}];return Ee.value&&l.push({label:"Admin",href:pt()}),l}),De=async()=>{const l=C.value.trim();if(!l){o.showMessage("Name cannot be empty","error");return}try{U.value=!0,await Re("/auth/api/user/display-name",{method:"PATCH",body:{display_name:l}}),_.value=!1,await o.loadUserInfo(),o.showMessage("Name updated successfully!","success",3e3)}catch(t){o.showMessage(t.message||"Failed to update name","error")}finally{U.value=!1}};return(l,t)=>(f(),m("section",Qt,[a("div",Xt,[a("button",{class:"theme-btn",onClick:t[0]||(t[0]=v=>H.value=!H.value),title:ue.value},W(X.value),9,es),H.value?(f(),m("div",{key:0,class:"theme-menu",onClick:t[4]||(t[4]=v=>H.value=!1)},[a("button",{class:ie(["theme-option top",{active:L.value===""}]),onClick:t[1]||(t[1]=pe(v=>se(""),["stop"])),title:"Auto"},"🌓",2),a("button",{class:ie(["theme-option left",{active:L.value==="light"}]),onClick:t[2]||(t[2]=pe(v=>se("light"),["stop"])),title:"Light"},"☀️",2),a("button",{class:ie(["theme-option right",{active:L.value==="dark"}]),onClick:t[3]||(t[3]=pe(v=>se("dark"),["stop"])),title:"Dark"},"🌙",2)])):B("",!0)]),a("header",ts,[t[15]||(t[15]=a("h1",null,"User Profile",-1)),re(It,{ref_key:"breadcrumbs",ref:p,entries:ke.value,onKeydown:Y},null,8,["entries"]),t[16]||(t[16]=a("p",{class:"view-lede"},"Account dashboard for managing credentials and authenticating with other devices.",-1))]),a("section",{class:"section-block",ref_key:"userInfoSection",ref:P},[k(o).userInfo?.ctx?(f(),Q(et,{key:0,ref_key:"userBasicInfo",ref:w,name:k(o).userInfo.ctx.user.display_name,visits:k(o).userInfo.visits,"created-at":k(o).userInfo.created_at,"last-seen":k(o).userInfo.last_seen,loading:k(o).isLoading,"update-endpoint":"/auth/api/user/display-name",onSaved:t[6]||(t[6]=v=>k(o).loadUserInfo()),onEditName:Me,onKeydown:J},{default:Ye(()=>[a("div",ss,[E.value?B("",!0):(f(),m("label",ns,"Code words:")),re(Jt,{ref_key:"pairingEntry",ref:j,title:"",description:"",onCompleted:G,onError:oe,onDeviceInfoVisible:t[5]||(t[5]=v=>E.value=v)},null,512)]),t[17]||(t[17]=a("p",{class:"remote-auth-description"},"Provided by another device requesting remote auth.",-1))]),_:1},8,["name","visits","created-at","last-seen","loading"])):B("",!0)],512),a("section",os,[t[18]||(t[18]=a("div",{class:"section-header"},[a("h2",null,"Your Passkeys"),a("p",{class:"section-description"},[F("Ideally have at least two passkeys in case you lose one. More than one user can be registered on the same device, giving you a choice at login. "),a("a",{href:"https://bitwarden.com/pricing/",target:"_blank",rel:"noopener noreferrer"},"Bitwarden"),F(" can sync one passkey to all your devices. Other secure options include "),a("b",null,"local passkeys"),F(", as well as hardware keys such as "),a("a",{href:"https://www.yubico.com",target:"_blank",rel:"noopener noreferrer"},"YubiKey"),F(". Cloud sync via Google, Microsoft or iCloud is discouraged.")])],-1)),a("div",as,[re(xt,{ref_key:"credentialList",ref:u,credentials:k(o).userInfo?.credentials||[],"aaguid-info":k(o).userInfo?.aaguid_info||{},loading:k(o).isLoading,"hovered-credential-uuid":d.value,"hovered-session-credential-uuid":V.value?.credential,"navigation-disabled":R.value,"allow-delete":"",onDelete:D,onCredentialHover:t[7]||(t[7]=v=>d.value=v),onNavigateOut:ae},null,8,["credentials","aaguid-info","loading","hovered-credential-uuid","hovered-session-credential-uuid","navigation-disabled"]),a("div",{class:"button-row",ref_key:"credentialButtons",ref:g},[a("button",{onClick:ne,class:"btn-primary",onKeydown:me},"Register New",32),a("button",{onClick:t[8]||(t[8]=v=>I.value=!0),class:"btn-secondary",onKeydown:me},"Another Device",32)],512)])]),re(Ct,{ref_key:"sessionList",ref:y,sessions:ce.value,"terminating-sessions":ee.value,"hovered-credential-uuid":d.value,"navigation-disabled":R.value,onTerminate:Pe,onSessionHover:t[9]||(t[9]=v=>V.value=v),onNavigateOut:ye,"section-description":"You are currently signed in to the following sessions. If you don't recognize something, consider deleting not only the session but the associated passkey you suspect is compromised, as only this terminates all linked sessions and prevents logging in again."},null,8,["sessions","terminating-sessions","hovered-credential-uuid","navigation-disabled"]),_.value?(f(),Q($t,{key:0,onClose:t[12]||(t[12]=v=>_.value=!1)},{default:Ye(()=>[t[19]||(t[19]=a("h3",null,"Edit Display Name",-1)),a("form",{onSubmit:pe(De,["prevent"]),class:"modal-form"},[re(St,{label:"Display Name",modelValue:C.value,"onUpdate:modelValue":t[10]||(t[10]=v=>C.value=v),busy:U.value,onCancel:t[11]||(t[11]=v=>_.value=!1)},null,8,["modelValue","busy"])],32)]),_:1})):B("",!0),a("section",ls,[a("div",{class:"button-row",ref_key:"logoutButtons",ref:h},[a("button",{type:"button",class:"btn-secondary",onClick:t[13]||(t[13]=(...v)=>k(Le)&&k(Le)(...v)),onKeydown:le}," Back ",32),fe.value?(f(),m(he,{key:1},[a("button",{onClick:de,class:"btn-danger",disabled:k(o).isLoading,onKeydown:le},"Logout",40,is),a("button",{onClick:be,class:"btn-danger",disabled:k(o).isLoading,onKeydown:le},"All",40,us)],64)):(f(),m("button",{key:0,onClick:be,class:"btn-danger",disabled:k(o).isLoading,onKeydown:le},"Logout",40,rs))],512),fe.value?(f(),m("p",ds,[t[21]||(t[21]=a("strong",null,"Logout",-1)),F(" this session on "+W(we.value)+", or ",1),t[22]||(t[22]=a("strong",null,"All",-1)),F(" sessions across all sites and devices for "+W(We.value)+". You'll need to log in again with your passkey afterwards.",1)])):(f(),m("p",cs,[t[20]||(t[20]=a("strong",null,"Logout",-1)),F(" from "+W(we.value)+".",1)]))]),I.value?(f(),Q(Lt,{key:1,endpoint:"/auth/api/user/create-link",onClose:t[14]||(t[14]=v=>I.value=!1),onCopied:q})):B("",!0)]))}},vs=Ke(fs,[["__scopeId","data-v-2cc35e77"]]),hs={class:"view-root host-view","data-view":"host-profile"},ps={class:"view-header"},gs={class:"view-lede"},ms={class:"section-body"},ys={key:1,class:"empty-state"},ws={class:"section-block"},bs={class:"section-body host-actions"},ks=["disabled"],_s=["disabled"],Is={class:"note"},xs={__name:"HostProfileView",props:{initializing:{type:Boolean,default:!1}},setup(T){const o=Ae(),$=window.location.host,_=c(null),I=c(null),C=A(()=>o.userInfo?.ctx||null),U=A(()=>C.value?.org.display_name??""),d=A(()=>C.value?.role.display_name??""),V=A(()=>{const p=o.settings?.rp_name;return p?`${p} account`:"Account overview"}),E=A(()=>`You're signed in to ${$}.`),j=A(()=>o.settings?.auth_host||""),u=A(()=>{const p=j.value;if(!p)return"";let w=o.settings?.ui_base_path??"/auth/";return w.startsWith("/")||(w=`/${w}`),w.endsWith("/")||(w=`${w}/`),`${window.location.protocol||"https:"}//${p}${w}`}),g=()=>{u.value&&(window.location.href=u.value)},y=async()=>{await o.logout()},h=p=>{const w=ge(p);w&&(p.preventDefault(),(w==="left"||w==="right")&&Ce(I.value,p.target,w,{itemSelector:"button"}))};return(p,w)=>(f(),m("section",hs,[a("header",ps,[a("h1",null,W(V.value),1),a("p",gs,W(E.value),1)]),a("section",{class:"section-block",ref_key:"userInfoSection",ref:_},[a("div",ms,[C.value?(f(),Q(et,{key:0,name:C.value.user.display_name,visits:k(o).userInfo?.visits||0,"created-at":k(o).userInfo?.created_at,"last-seen":k(o).userInfo?.last_seen,"org-display-name":U.value,"role-name":d.value,"can-edit":!1},null,8,["name","visits","created-at","last-seen","org-display-name","role-name"])):(f(),m("p",ys,W(T.initializing?"Loading your account…":"No active session found."),1))])],512),a("section",ws,[a("div",bs,[a("div",{class:"button-row",ref_key:"buttonRow",ref:I,onKeydown:h},[a("button",{type:"button",class:"btn-secondary",onClick:w[0]||(w[0]=(...P)=>k(Le)&&k(Le)(...P))}," Back "),a("button",{type:"button",class:"btn-danger",disabled:k(o).isLoading,onClick:y},W(k(o).isLoading?"Signing out…":"Logout"),9,ks),u.value?(f(),m("button",{key:0,type:"button",class:"btn-primary",disabled:k(o).isLoading,onClick:g}," Full Profile ",8,_s)):B("",!0)],544),a("p",Is,[w[1]||(w[1]=a("strong",null,"Logout",-1)),F(" from "+W(k($))+", or access your ",1),w[2]||(w[2]=a("strong",null,"Full Profile",-1)),F(" at "+W(j.value)+" (you may need to sign in again).",1)])])])]))}},Cs=Ke(xs,[["__scopeId","data-v-237999a2"]]),Ss={class:"app-shell"},$s={class:"app-main"},Ls={__name:"App",setup(T){const o=Ae(),$=c("loading"),_=c("Loading...");function I(g){if(!g)return null;const y=g.trim().toLowerCase();return y?y.replace(/:80$/,"").replace(/:443$/,""):null}const C=A(()=>{const g=o.settings?.auth_host;if(!g)return!1;const y=I(window.location.host),h=I(g);return y!==h});function U(){o.userInfo=null,$.value="terminal"}const d=()=>o.userInfo?.ctx.user.uuid,V=new mt(d,U);Se(()=>V.start()),$e(()=>V.stop());async function E(){try{return o.userInfo=await Re("/auth/api/user-info",{method:"POST"}),$.value="profile",!0}catch{return o.userInfo=null,!1}}async function j(){const g=await Et("login");yt(g),_.value="Authentication required..."}function u(g){const y=g.data;if(y?.type)switch(y.type){case"auth-success":xe(),$.value="loading",_.value="Loading user profile...",E();break;case"auth-error":y.cancelled?console.log("Authentication cancelled by user"):o.showMessage(y.message||"Authentication failed","error",5e3);break;case"auth-cancelled":console.log("Authentication cancelled");break;case"auth-back":xe(),U();break;case"auth-close-request":xe();break}}return Se(async()=>{window.addEventListener("message",u),await o.loadSettings();const g=o.settings?.rp_name;if(g){const h=o.settings?.auth_host,p=h&&I(window.location.host)!==I(h);document.title=p?`${g} · Account summary`:g}await E()||j()}),$e(()=>{window.removeEventListener("message",u),xe()}),(g,y)=>(f(),m("div",Ss,[re(Wt),a("main",$s,[$.value==="profile"&&C.value?(f(),Q(Cs,{key:0})):$.value==="profile"?(f(),Q(vs,{key:1})):$.value==="loading"?(f(),Q(At,{key:2,message:_.value},null,8,["message"])):$.value==="terminal"?(f(),Q(Pt,{key:3})):B("",!0)])]))}};_t();const tt=wt(Ls);tt.use(Mt());tt.mount("#app");bt();
@@ -0,0 +1 @@
1
+ import{o as d,b as u,x as s,u as m,m as o,a8 as l,J as h,K as p}from"./_plugin-vue_export-helper-DJsHCwvl.js";import{R as f}from"./RestrictedAuth-DWKMTEV3.js";import{g as a}from"./helpers-DzjFIx78.js";import"./pow-DUr-T9XX.js";const w={__name:"RestrictedForward",setup(_){const n=o(()=>l()),r=o(()=>{const e=document.documentElement.getAttribute("data-mode");return e==="reauth"?"reauth":e==="forbidden"?"forbidden":"login"});function i(){location.reload()}function c(){const t=n.value||"/auth/";window.location.pathname!==t&&history.replaceState(null,"",t),window.location.href=t}return d(()=>{window.addEventListener("keydown",t=>{t.key==="Escape"&&a()})}),(t,e)=>(u(),s(f,{mode:r.value,onAuthenticated:i,onBack:m(a),onHome:c},null,8,["mode","onBack"]))}};h(w).mount("#app");p();
@@ -1 +1 @@
1
- function v(e){const a=e.replace(/-/g,"+").replace(/_/g,"/"),t=a+"=".repeat((4-a.length%4)%4);return Uint8Array.from(atob(t),i=>i.charCodeAt(0))}function k(e){return btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}const h=["able","about","absent","abuse","access","acid","across","act","adapt","add","adjust","admit","adult","advice","affair","afraid","again","age","agree","ahead","aim","air","aisle","alarm","album","alert","alien","all","almost","alone","alpha","also","alter","always","amazed","among","amused","anchor","angle","animal","ankle","annual","answer","any","apart","appear","april","arch","are","argue","army","around","array","art","ascent","ash","ask","aspect","assume","asthma","atom","attack","audit","august","aunt","author","avoid","away","awful","axis","baby","back","bad","bag","ball","bamboo","bank","bar","base","battle","beach","become","beef","before","begin","behind","below","bench","best","better","beyond","bid","bike","bind","bio","birth","bitter","black","bleak","blind","blood","blue","board","body","boil","bomb","bone","book","border","boss","bottom","bounce","bowl","box","boy","brain","bread","bring","brown","brush","bubble","buck","budget","build","bulk","bundle","burden","bus","but","buyer","buzz","cable","cache","cage","cake","call","came","can","car","case","catch","cause","cave","celery","cement","census","cereal","change","check","child","choice","chunk","cigar","circle","city","civil","class","clean","client","close","club","coast","code","coffee","coil","cold","come","cool","copy","core","cost","cotton","couch","cover","coyote","craft","cream","crime","cross","cruel","cry","cube","cue","cult","cup","curve","custom","cute","cycle","dad","damage","danger","daring","dash","dawn","day","deal","debate","decide","deer","define","degree","deity","delay","demand","denial","depth","derive","design","detail","device","dial","dice","die","differ","dim","dinner","direct","dish","divert","dizzy","doctor","dog","dollar","domain","donate","door","dose","double","dove","draft","dream","drive","drop","drum","dry","duck","dumb","dune","during","dust","dutch","dwarf","eager","early","east","echo","eco","edge","edit","effort","egg","eight","either","elbow","elder","elite","else","embark","emerge","emily","employ","enable","end","enemy","engine","enjoy","enlist","enough","enrich","ensure","entire","envy","equal","era","erode","error","erupt","escape","essay","estate","ethics","evil","evoke","exact","excess","exist","exotic","expect","extent","eye","fabric","face","fade","faith","fall","family","fan","far","father","fault","feel","female","fence","fetch","fever","few","fiber","field","figure","file","find","first","fish","fit","fix","flat","flesh","flight","float","fluid","fly","foam","focus","fog","foil","follow","food","force","fossil","found","fox","frame","fresh","friend","frog","fruit","fuel","fun","fury","future","gadget","gain","galaxy","game","gap","garden","gas","gate","gauge","gaze","genius","ghost","giant","gift","giggle","ginger","girl","give","glass","glide","globe","glue","goal","god","gold","good","gospel","govern","gown","grant","great","grid","group","grunt","guard","guess","guide","gulf","gun","gym","habit","hair","half","hammer","hand","happy","hard","hat","have","hawk","hay","hazard","head","hedge","height","help","hen","hero","hidden","high","hill","hint","hip","hire","hobby","hockey","hold","home","honey","hood","hope","horse","host","hotel","hour","hover","how","hub","huge","human","hungry","hurt","hybrid","ice","icon","idea","idle","ignore","ill","image","immune","impact","income","index","infant","inhale","inject","inmate","inner","input","inside","into","invest","iron","island","issue","italy","item","ivory","jacket","jaguar","james","jar","jazz","jeans","jelly","jewel","job","joe","joke","joy","judge","juice","july","jump","june","just","kansas","kate","keep","kernel","key","kick","kid","kind","kiss","kit","kiwi","knee","knife","know","labor","lady","lag","lake","lamp","laptop","large","later","laugh","lava","law","layer","lazy","leader","left","legal","lemon","length","lesson","letter","level","liar","libya","lid","life","light","like","limit","line","lion","liquid","list","little","live","lizard","load","local","logic","long","loop","lost","loud","love","low","loyal","lucky","lumber","lunch","lust","luxury","lyrics","mad","magic","main","major","make","male","mammal","man","map","market","mass","matter","maze","mccoy","meadow","media","meet","melt","member","men","mercy","mesh","method","middle","milk","mimic","mind","mirror","miss","mix","mobile","model","mom","monkey","moon","more","mother","mouse","move","much","muffin","mule","must","mutual","myself","myth","naive","name","napkin","narrow","nasty","nation","near","neck","need","nephew","nerve","nest","net","never","news","next","nice","night","noble","noise","noodle","normal","nose","note","novel","now","number","nurse","nut","oak","obey","object","oblige","obtain","occur","ocean","odor","off","often","oil","okay","old","olive","omit","once","one","onion","online","open","opium","oppose","option","orange","orbit","order","organ","orient","orphan","other","outer","oval","oven","own","oxygen","oyster","ozone","pact","paddle","page","pair","palace","panel","paper","parade","past","path","pause","pave","paw","pay","peace","pen","people","pepper","permit","pet","philip","phone","phrase","piano","pick","piece","pig","pilot","pink","pipe","pistol","pitch","pizza","place","please","pluck","poem","point","polar","pond","pool","post","pot","pound","powder","praise","prefer","price","profit","public","pull","punch","pupil","purity","push","put","puzzle","qatar","quasi","queen","quite","quoted","rabbit","race","radio","rail","rally","ramp","range","rapid","rare","rather","raven","raw","razor","real","rebel","recall","red","reform","region","reject","relief","remain","rent","reopen","report","result","return","review","reward","rhythm","rib","rich","ride","rifle","right","ring","riot","ripple","risk","ritual","river","road","robot","rocket","room","rose","rotate","round","row","royal","rubber","rude","rug","rule","run","rural","sad","safe","sage","sail","salad","same","santa","sauce","save","say","scale","scene","school","scope","screen","scuba","sea","second","seed","self","semi","sense","series","settle","seven","shadow","she","ship","shock","shrimp","shy","sick","side","siege","sign","silver","simple","since","siren","sister","six","size","skate","sketch","ski","skull","slab","sleep","slight","slogan","slush","small","smile","smooth","snake","sniff","snow","soap","soccer","soda","soft","solid","son","soon","sort","south","space","speak","sphere","spirit","split","spoil","spring","spy","square","state","step","still","story","strong","stuff","style","submit","such","sudden","suffer","sugar","suit","summer","sun","supply","sure","swamp","sweet","switch","sword","symbol","syntax","syria","system","table","tackle","tag","tail","talk","tank","tape","target","task","tattoo","taxi","team","tell","ten","term","test","text","that","theme","this","three","thumb","tibet","ticket","tide","tight","tilt","time","tiny","tip","tired","tissue","title","toast","today","toe","toilet","token","tomato","tone","tool","top","torch","toss","total","toward","toy","trade","tree","trial","trophy","true","try","tube","tumble","tunnel","turn","twenty","twice","two","type","ugly","unable","uncle","under","unfair","unique","unlock","until","unveil","update","uphold","upon","upper","upset","urban","urge","usage","use","usual","vacuum","vague","valid","van","vapor","vast","vault","vein","velvet","vendor","very","vessel","viable","video","view","villa","violin","virus","visit","vital","vivid","vocal","voice","volume","vote","voyage","wage","wait","wall","want","war","wash","water","wave","way","wealth","web","weird","were","west","wet","what","when","whip","wide","wife","will","window","wire","wish","wolf","woman","wonder","wood","work","wrap","wreck","write","wrong","xander","xbox","xerox","xray","yang","yard","year","yellow","yes","yin","york","you","zane","zara","zebra","zen","zero","zippo","zone","zoo","zorro","zulu"],o=new Map;for(const e of h)for(let a=1;a<=Math.min(e.length,6);a++){const t=e.slice(0,a);o.has(t)||o.set(t,[]),o.get(t).push(e)}function w(e){if(!e)return[];const a=e.toLowerCase();return o.get(a)||[]}function x(e){const a=w(e);return a.length===1?a[0]:null}function z(e){if(!e)return!1;const a=e.toLowerCase();return h.includes(a)}function j(e){if(!e)return!0;const a=e.toLowerCase();return o.has(a)}async function A(e,a,t={}){const{signal:i}=t,m=performance.now(),n=e instanceof ArrayBuffer?new Uint8Array(e):e;if(!(n instanceof Uint8Array)||n.length!==8)throw new Error("Challenge must be exactly 8 bytes");const b=await crypto.subtle.importKey("raw",n,"PBKDF2",!1,["deriveBits"]),u=new Uint8Array(8*a);let l=0;const s=2047,r=new Uint32Array(2);for(let c=0;c<a;c++){if(i?.aborted)throw new DOMException("PoW operation aborted","AbortError");let p;do l++,++r[0]===4294967296&&++r[1],p=new Uint32Array(await crypto.subtle.deriveBits({name:"PBKDF2",salt:r,iterations:128,hash:"SHA-512"},b,32));while(p[0]&s);u.set(new Uint8Array(r.buffer),c*8)}const d=(performance.now()-m)/1e3,g=a*(s+1),f=(l/g).toFixed(1),y=l/((s+1)*d);return console.log(`PoW work=${a} solved in ${d.toFixed(2)}s (${f}x expected ${y.toFixed(1)} work/s)`),u}export{z as a,k as b,v as c,x as g,j as i,A as s,h as w};
1
+ function v(e){const a=e.replace(/-/g,"+").replace(/_/g,"/"),t=a+"=".repeat((4-a.length%4)%4);return Uint8Array.from(atob(t),i=>i.charCodeAt(0))}function k(e){return btoa(String.fromCharCode(...e)).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}const h=["able","about","absent","abuse","access","acid","across","act","adapt","add","adjust","admit","adult","advice","affair","afraid","again","age","agree","ahead","aim","air","aisle","alarm","album","alert","alien","all","almost","alone","alpha","also","alter","always","amazed","among","amused","anchor","angle","animal","ankle","annual","answer","any","apart","appear","april","arch","are","argue","army","around","array","art","ascent","ash","ask","aspect","assume","asthma","atom","attack","audit","august","aunt","author","avoid","away","awful","axis","baby","back","bad","bag","ball","bamboo","bank","bar","base","battle","beach","become","beef","before","begin","behind","below","bench","best","better","beyond","bid","bike","bind","bio","birth","bitter","black","bleak","blind","blood","blue","board","body","boil","bomb","bone","book","border","boss","bottom","bounce","bowl","box","boy","brain","bread","bring","brown","brush","bubble","buck","budget","build","bulk","bundle","burden","bus","but","buyer","buzz","cable","cache","cage","cake","call","came","can","car","case","catch","cause","cave","celery","cement","census","cereal","change","check","child","choice","chunk","cigar","circle","city","civil","class","clean","client","close","club","coast","code","coffee","coil","cold","come","cool","copy","core","cost","cotton","couch","cover","coyote","craft","cream","crime","cross","cruel","cry","cube","cue","cult","cup","curve","custom","cute","cycle","dad","damage","danger","daring","dash","dawn","day","deal","debate","decide","deer","define","degree","deity","delay","demand","denial","depth","derive","design","detail","device","dial","dice","die","differ","dim","dinner","direct","dish","divert","dizzy","doctor","dog","dollar","domain","donate","door","dose","double","dove","draft","dream","drive","drop","drum","dry","duck","dumb","dune","during","dust","dutch","dwarf","eager","early","east","echo","eco","edge","edit","effort","egg","eight","either","elbow","elder","elite","else","embark","emerge","emily","employ","enable","end","enemy","engine","enjoy","enlist","enough","enrich","ensure","entire","envy","equal","era","erode","error","erupt","escape","essay","estate","ethics","evil","evoke","exact","excess","exist","exotic","expect","extent","eye","fabric","face","fade","faith","fall","family","fan","far","father","fault","feel","female","fence","fetch","fever","few","fiber","field","figure","file","find","first","fish","fit","fix","flat","flesh","flight","float","fluid","fly","foam","focus","fog","foil","follow","food","force","fossil","found","fox","frame","fresh","friend","frog","fruit","fuel","fun","fury","future","gadget","gain","galaxy","game","gap","garden","gas","gate","gauge","gaze","genius","ghost","giant","gift","giggle","ginger","girl","give","glass","glide","globe","glue","goal","god","gold","good","gospel","govern","gown","grant","great","grid","group","grunt","guard","guess","guide","gulf","gun","gym","habit","hair","half","hammer","hand","happy","hard","hat","have","hawk","hay","hazard","head","hedge","height","help","hen","hero","hidden","high","hill","hint","hip","hire","hobby","hockey","hold","home","honey","hood","hope","horse","host","hotel","hour","hover","how","hub","huge","human","hungry","hurt","hybrid","ice","icon","idea","idle","ignore","ill","image","immune","impact","income","index","infant","inhale","inject","inmate","inner","input","inside","into","invest","iron","island","issue","italy","item","ivory","jacket","jaguar","james","jar","jazz","jeans","jelly","jewel","job","joe","joke","joy","judge","juice","july","jump","june","just","kansas","kate","keep","kernel","key","kick","kid","kind","kiss","kit","kiwi","knee","knife","know","labor","lady","lag","lake","lamp","laptop","large","later","laugh","lava","law","layer","lazy","leader","left","legal","lemon","length","lesson","letter","level","liar","libya","lid","life","light","like","limit","line","lion","liquid","list","little","live","lizard","load","local","logic","long","loop","lost","loud","love","low","loyal","lucky","lumber","lunch","lust","luxury","lyrics","mad","magic","main","major","make","male","mammal","man","map","market","mass","matter","maze","mccoy","meadow","media","meet","melt","member","men","mercy","mesh","method","middle","milk","mimic","mind","mirror","miss","mix","mobile","model","mom","monkey","moon","more","mother","mouse","move","much","muffin","mule","must","mutual","myself","myth","naive","name","napkin","narrow","nasty","nation","near","neck","need","nephew","nerve","nest","net","never","news","next","nice","night","noble","noise","noodle","normal","nose","note","novel","now","number","nurse","nut","oak","obey","object","oblige","obtain","occur","ocean","odor","off","often","oil","okay","old","olive","omit","once","one","onion","online","open","opium","oppose","option","orange","orbit","order","organ","orient","orphan","other","outer","oval","oven","own","oxygen","oyster","ozone","pact","paddle","page","pair","palace","panel","paper","parade","past","path","pause","pave","paw","pay","peace","pen","people","pepper","permit","pet","philip","phone","phrase","piano","pick","piece","pig","pilot","pink","pipe","pistol","pitch","pizza","place","please","pluck","poem","point","polar","pond","pool","post","pot","pound","powder","praise","prefer","price","profit","public","pull","punch","pupil","purity","push","put","puzzle","qatar","quasi","queen","quite","quoted","rabbit","race","radio","rail","rally","ramp","range","rapid","rare","rather","raven","raw","razor","real","rebel","recall","red","reform","region","reject","relief","remain","rent","reopen","report","result","return","review","reward","rhythm","rib","rich","ride","rifle","right","ring","riot","ripple","risk","ritual","river","road","robot","rocket","room","rose","rotate","round","row","royal","rubber","rude","rug","rule","run","rural","sad","safe","sage","sail","salad","same","santa","sauce","save","say","scale","scene","school","scope","screen","scuba","sea","second","seed","self","semi","sense","series","settle","seven","shadow","she","ship","shock","shrimp","shy","sick","side","siege","sign","silver","simple","since","siren","sister","six","size","skate","sketch","ski","skull","slab","sleep","slight","slogan","slush","small","smile","smooth","snake","sniff","snow","soap","soccer","soda","soft","solid","son","soon","sort","south","space","speak","sphere","spirit","split","spoil","spring","spy","square","state","step","still","story","strong","stuff","style","submit","such","sudden","suffer","sugar","suit","summer","sun","supply","sure","swamp","sweet","switch","sword","symbol","syntax","syria","system","table","tackle","tag","tail","talk","tank","tape","target","task","tattoo","taxi","team","tell","ten","term","test","text","that","theme","this","three","thumb","tibet","ticket","tide","tight","tilt","time","tiny","tip","tired","tissue","title","toast","today","toe","toilet","token","tomato","tone","tool","top","torch","toss","total","toward","toy","trade","tree","trial","trophy","true","try","tube","tumble","tunnel","turn","twenty","twice","two","type","ugly","unable","uncle","under","unfair","unique","unlock","until","unveil","update","uphold","upon","upper","upset","urban","urge","usage","use","usual","vacuum","vague","valid","van","vapor","vast","vault","vein","velvet","vendor","very","vessel","viable","video","view","villa","violin","virus","visit","vital","vivid","vocal","voice","volume","vote","voyage","wage","wait","wall","want","war","wash","water","wave","way","wealth","web","weird","were","west","wet","what","when","whip","wide","wife","will","window","wire","wish","wolf","woman","wonder","wood","work","wrap","wreck","write","wrong","xander","xbox","xerox","xray","yang","yard","year","yellow","yes","yin","york","you","zane","zara","zebra","zen","zero","zippo","zone","zoo","zorro","zulu"],o=new Map;for(const e of h)for(let a=1;a<=Math.min(e.length,6);a++){const t=e.slice(0,a);o.has(t)||o.set(t,[]),o.get(t).push(e)}function w(e){if(!e)return[];const a=e.toLowerCase();return o.get(a)||[]}function x(e){const a=w(e);return a.length===1?a[0]:null}function z(e){if(!e)return!1;const a=e.toLowerCase();return h.includes(a)}function j(e){if(!e)return!0;const a=e.toLowerCase();return o.has(a)}async function A(e,a,t={}){const{signal:i}=t,m=performance.now(),n=e instanceof ArrayBuffer?new Uint8Array(e):e;if(!(n instanceof Uint8Array)||n.length!==8)throw new Error("Challenge must be exactly 8 bytes");const b=await crypto.subtle.importKey("raw",n,"PBKDF2",!1,["deriveBits"]),u=new Uint8Array(8*a);let l=0;const s=2047,r=new Uint32Array(2);for(let c=0;c<a;c++){if(i?.aborted)throw new DOMException("PoW operation aborted","AbortError");let p;do l++,++r[0]===4294967296&&++r[1],p=new Uint32Array(await crypto.subtle.deriveBits({name:"PBKDF2",salt:r,iterations:128,hash:"SHA-512"},b,32));while(p[0]&s);u.set(new Uint8Array(r.buffer),c*8)}const d=(performance.now()-m)/1e3,g=a*(s+1),f=(l/g).toFixed(1),y=l/((s+1)*d);return console.log(`PoW work=${a} solved in ${d.toFixed(2)}s (${f}x expected ${y.toFixed(1)} work/s)`),u}export{j as a,k as b,v as c,x as g,z as i,A as s,h as w};
@@ -0,0 +1 @@
1
+ .center[data-v-4830d223]{text-align:center}.button-row.center[data-v-4830d223]{display:flex;justify-content:center}.section-body[data-v-4830d223]{gap:1.25rem}.name-edit span[data-v-4830d223]{color:var(--color-text-muted);font-size:.9rem}
@@ -0,0 +1 @@
1
+ import{_ as z,o as A,b as c,c as u,d as t,t as f,e as M,g as N,i as $,v as F,L as K,k as i,l as V,z as b,a6 as D,a7 as E,X as I,m as p,a8 as H,H as L,J as U,K as j}from"./_plugin-vue_export-helper-DJsHCwvl.js";const G={class:"app-shell"},J={key:0,class:"global-status",style:{display:"block"}},O={class:"view-root"},X={class:"surface surface--tight",style:{"max-width":"560px",margin:"0 auto",width:"100%"}},Y={class:"view-header",style:{"text-align":"center"}},q={class:"view-lede"},Q={key:0,class:"section-block"},W={key:1,class:"section-block"},Z={key:2,class:"section-block"},ee={class:"section-body"},se={class:"name-edit"},te=["disabled"],ae=["disabled"],ne={__name:"ResetApp",setup(ie){const o=I({show:!1,message:"",type:"info"}),d=i(!0),n=i(!1),l=i(""),x=i(null),g=i(null),m=i(""),y=i("");let v=null;const T=p(()=>g.value?.token_type||"your enrollment"),P=p(()=>d.value?"Preparing your secure enrollment…":h.value?`Finish up ${T.value}. You may edit the name below if needed, and it will be saved to your passkey.`:"This authentication link is no longer valid."),h=p(()=>!!(l.value&&g.value));function r(e,s="info",a=3e3){o.show=!0,o.message=e,o.type=s,v&&clearTimeout(v),a>0&&(v=setTimeout(()=>{o.show=!1},a))}async function R(){try{const e=await V();x.value=e,e?.rp_name&&(document.title=`${e.rp_name} · Passkey Setup`)}catch(e){console.warn("Unable to load settings",e)}}async function S(){if(l.value)try{g.value=await b("/auth/api/token-info",{method:"GET",headers:{Authorization:`Bearer ${l.value}`}}),m.value=g.value.display_name}catch(e){console.error("Failed to load token info",e);const s=e instanceof D?e.data?.detail||"The authentication link is invalid or expired.":E(e);y.value=s}}async function _(){if(!h.value||n.value)return;n.value=!0,r("Starting passkey registration…","info");let e;try{const s=m.value.trim()||null;e=await L.register(l.value,s)}catch(s){n.value=!1;const a=s?.message||"Passkey registration cancelled",w=a==="Passkey registration cancelled";r(w?a:`Registration failed: ${a}`,w?"info":"error",4e3);return}try{await B(e)}catch(s){n.value=!1;const a=s?.message||"Failed to establish session";r(a,"error",4e3);return}r("Passkey registered successfully!","success",800),setTimeout(()=>{n.value=!1,k()},800)}async function B(e){if(!e?.session_token)throw new Error("Registration response missing session_token");return await b("/auth/api/set-session",{method:"POST",headers:{Authorization:`Bearer ${e.session_token}`}})}function k(){const e=H.value||"/auth/";window.location.pathname!==e&&history.replaceState(null,"",e),window.location.reload()}function C(){const e=window.location.pathname.split("/").filter(Boolean);if(!e.length)return"";const s=e[e.length-1],a=e.slice(0,-1);return a.length>1||a.length===1&&a[0]!=="auth"||!s.includes(".")?"":s}return A(async()=>{if(l.value=C(),await R(),!l.value){const e="Reset link is missing or malformed.";y.value=e,r(e,"error",0),d.value=!1;return}await S(),d.value=!1}),(e,s)=>(c(),u("div",G,[o.show?(c(),u("div",J,[t("div",{class:M(["status",o.type])},f(o.message),3)])):N("",!0),t("main",O,[t("div",X,[t("header",Y,[s[1]||(s[1]=t("h1",null,"🔑 Registration",-1)),t("p",q,f(P.value),1)]),d.value?(c(),u("section",Q,[...s[2]||(s[2]=[t("div",{class:"section-body center"},[t("p",null,"Loading reset details…")],-1)])])):h.value?(c(),u("section",Z,[t("div",ee,[t("label",se,[s[3]||(s[3]=t("span",null,"👤 Name",-1)),$(t("input",{type:"text","onUpdate:modelValue":s[0]||(s[0]=a=>m.value=a),disabled:n.value,maxlength:"64",onKeyup:K(_,["enter"])},null,40,te),[[F,m.value]])]),t("button",{class:"btn-primary",disabled:n.value,onClick:_},f(n.value?"Registering…":"Register Passkey"),9,ae)])])):(c(),u("section",W,[t("div",{class:"section-body center"},[t("div",{class:"button-row center",style:{"justify-content":"center"}},[t("button",{class:"btn-secondary",onClick:k},"Return to sign-in")])])]))])])]))}},oe=z(ne,[["__scopeId","data-v-4830d223"]]);U(oe).mount("#app");j();
@@ -0,0 +1 @@
1
+ import{o as f,b as w,x as k,u as g,k as y,J as _,K as T}from"./_plugin-vue_export-helper-DJsHCwvl.js";import{a as i,g as v}from"./theme-C2WysaSw.js";import{R as A}from"./RestrictedAuth-DWKMTEV3.js";import"./pow-DUr-T9XX.js";function h(){return new URLSearchParams(location.hash.slice(1)).get("theme")||v()||""}i(h(),".surface");addEventListener("hashchange",()=>i(h(),".surface"));const R={__name:"RestrictedApi",setup(u){const n=y(null);function d(){const a=window.location.pathname.match(/\/auth\/([^/]+)$/);if(a){const r=a[1],c=r.split(".");if(c.length===5&&c.every(l=>l.length>0))return r}return null}const o=new URLSearchParams(window.location.hash.slice(1)),p=["reauth","forbidden"].includes(o.get("mode"))?o.get("mode"):"login";function t(e){window.parent&&window.parent!==window&&window.parent.postMessage(e,"*")}function m(e){t({type:"auth-success",authenticated:!0,sessionToken:e.session_token})}function s(){t({type:"auth-back"})}return f(()=>{n.value=d(),t({type:"auth-ready"}),window.addEventListener("keydown",e=>{e.key==="Escape"&&s()})}),(e,a)=>(w(),k(A,{mode:g(p),"remote-auth-token":n.value,onAuthenticated:m,onBack:s},null,8,["mode","remote-auth-token"]))}};_(R).mount("#app");T();
@@ -0,0 +1 @@
1
+ const l={light:{"color-canvas":"#ffffff","color-surface":"#eff6ff","color-surface-subtle":"#dbeafe","color-border":"#2563eb","color-border-strong":"#1e40af","color-heading":"#1e3a8a","color-text":"#1e293b","color-text-muted":"#475569","color-link":"#1d4ed8","color-link-hover":"#1e40af","color-accent":"#2563eb","color-accent-strong":"#1e40af","color-accent-contrast":"#ffffff","color-success-text":"#166534","color-success-bg":"#dcfce7","color-error-text":"#b91c1c","color-error-bg":"#fee2e2","color-info-text":"#1e40af","color-info-bg":"#dbeafe","color-danger":"#dc2626","shadow-soft":"0 10px 30px rgba(30, 64, 175, 0.15)"},dark:{"color-canvas":"#0f172a","color-surface":"#141b2f","color-surface-subtle":"#1b243b","color-border":"#25304a","color-border-strong":"#3d4d6b","color-heading":"#fff","color-text":"#e2e8f0","color-text-muted":"#94a3b8","color-link":"#60a5fa","color-link-hover":"#93c5fd","color-accent":"#60a5fa","color-accent-strong":"#3b82f6","color-accent-contrast":"#0b1120","color-success-text":"#34d399","color-success-bg":"#1a4d2e","color-error-text":"#fca5a5","color-error-bg":"#4a1f1f","color-info-text":"#bae6fd","color-info-bg":"#1e3a5f","color-danger":"#f87171","shadow-soft":"0 0 0 #000000"}},s="theme-override",a="theme-transition",n="paskia-theme";function f(o,c=":root",t=!1){if(t){let e=document.getElementById(a);e||(e=document.createElement("style"),e.id=a,e.textContent="*, *::before, *::after { transition: background-color 0.3s, color 0.3s, border-color 0.3s, box-shadow 0.3s !important; }",document.head.appendChild(e)),setTimeout(()=>document.getElementById(a)?.remove(),350)}if(document.getElementById(s)?.remove(),o&&l[o]){const e=`${c} { ${Object.entries(l[o]).map(([d,i])=>`--${d}: ${i}`).join("; ")}; }`,r=document.createElement("style");r.id=s,r.textContent=e,document.head.appendChild(r)}}function m(){return localStorage.getItem(n)||""}function b(o){o?localStorage.setItem(n,o):localStorage.removeItem(n)}function u(){f(m())}function g(o,c=!1){const t=o?.user?.theme||"";b(t),f(t,":root",c)}export{f as a,m as g,u as i,g as u};
@@ -4,14 +4,15 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Auth Profile</title>
7
- <script type="module" crossorigin src="/auth/assets/auth-YIZvPlW_.js"></script>
8
- <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-nhjnO_bd.js">
7
+ <script type="module" crossorigin src="/auth/assets/auth-Pe-PKe8b.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js">
9
+ <link rel="modulepreload" crossorigin href="/auth/assets/theme-C2WysaSw.js">
9
10
  <link rel="modulepreload" crossorigin href="/auth/assets/helpers-DzjFIx78.js">
10
- <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-Fmeb6EtF.js">
11
- <link rel="modulepreload" crossorigin href="/auth/assets/pow-2N9bxgAo.js">
12
- <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css">
13
- <link rel="stylesheet" crossorigin href="/auth/assets/AccessDenied-DPkUS8LZ.css">
14
- <link rel="stylesheet" crossorigin href="/auth/assets/auth-C7k64Wad.css">
11
+ <link rel="modulepreload" crossorigin href="/auth/assets/AccessDenied-Licr0tqA.js">
12
+ <link rel="modulepreload" crossorigin href="/auth/assets/pow-DUr-T9XX.js">
13
+ <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css">
14
+ <link rel="stylesheet" crossorigin href="/auth/assets/AccessDenied-CVQZxSIL.css">
15
+ <link rel="stylesheet" crossorigin href="/auth/assets/auth-B4EpDxom.css">
15
16
  </head>
16
17
  <body>
17
18
  <div id="app"></div>
@@ -3,12 +3,13 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <script type="module" crossorigin src="/auth/assets/restricted-D3AJx3_6.js"></script>
7
- <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-nhjnO_bd.js">
8
- <link rel="modulepreload" crossorigin href="/auth/assets/pow-2N9bxgAo.js">
9
- <link rel="modulepreload" crossorigin href="/auth/assets/RestrictedAuth-DsJXicIw.js">
10
- <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css">
11
- <link rel="stylesheet" crossorigin href="/auth/assets/RestrictedAuth-CvR33_Z0.css">
6
+ <script type="module" crossorigin src="/auth/assets/restricted-C9cJlHkd.js"></script>
7
+ <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js">
8
+ <link rel="modulepreload" crossorigin href="/auth/assets/theme-C2WysaSw.js">
9
+ <link rel="modulepreload" crossorigin href="/auth/assets/pow-DUr-T9XX.js">
10
+ <link rel="modulepreload" crossorigin href="/auth/assets/RestrictedAuth-DWKMTEV3.js">
11
+ <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css">
12
+ <link rel="stylesheet" crossorigin href="/auth/assets/RestrictedAuth-0MFeNWS2.css">
12
13
  </head>
13
14
  <body>
14
15
  <div id="app"></div>
@@ -4,13 +4,13 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Access Restricted</title>
7
- <script type="module" crossorigin src="/auth/assets/forward-DmqVHZ7e.js"></script>
8
- <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-nhjnO_bd.js">
9
- <link rel="modulepreload" crossorigin href="/auth/assets/pow-2N9bxgAo.js">
10
- <link rel="modulepreload" crossorigin href="/auth/assets/RestrictedAuth-DsJXicIw.js">
7
+ <script type="module" crossorigin src="/auth/assets/forward-BC0p23CH.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js">
9
+ <link rel="modulepreload" crossorigin href="/auth/assets/pow-DUr-T9XX.js">
10
+ <link rel="modulepreload" crossorigin href="/auth/assets/RestrictedAuth-DWKMTEV3.js">
11
11
  <link rel="modulepreload" crossorigin href="/auth/assets/helpers-DzjFIx78.js">
12
- <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css">
13
- <link rel="stylesheet" crossorigin href="/auth/assets/RestrictedAuth-CvR33_Z0.css">
12
+ <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css">
13
+ <link rel="stylesheet" crossorigin href="/auth/assets/RestrictedAuth-0MFeNWS2.css">
14
14
  </head>
15
15
  <body>
16
16
  <div id="app"></div>
@@ -4,10 +4,10 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Complete Passkey Setup</title>
7
- <script type="module" crossorigin src="/auth/assets/reset-s20PATTN.js"></script>
8
- <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-nhjnO_bd.js">
9
- <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-BTzJAQlS.css">
10
- <link rel="stylesheet" crossorigin href="/auth/assets/reset-Chtv69AT.css">
7
+ <script type="module" crossorigin src="/auth/assets/reset-CkY9h28U.js"></script>
8
+ <link rel="modulepreload" crossorigin href="/auth/assets/_plugin-vue_export-helper-DJsHCwvl.js">
9
+ <link rel="stylesheet" crossorigin href="/auth/assets/_plugin-vue_export-helper-DUBf8-iM.css">
10
+ <link rel="stylesheet" crossorigin href="/auth/assets/reset-B8PlNXuP.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="app"></div>
Binary file
File without changes
@@ -0,0 +1,110 @@
1
+ """API response utilities using msgspec for JSON serialization.
2
+
3
+ msgspec handles UUID and datetime conversion automatically.
4
+ API structs inherit from db structs with kw_only=True to add uuid/key fields.
5
+ """
6
+
7
+ from datetime import UTC, datetime
8
+ from uuid import UUID
9
+
10
+ import msgspec
11
+
12
+ from paskia.db.structs import Org, Permission, Role, User
13
+ from paskia.util import useragent
14
+
15
+
16
+ def _utc_datetime(dt: datetime | None) -> datetime | None:
17
+ """Convert datetime to UTC, handling both aware and naive datetimes."""
18
+ if dt is None:
19
+ return None
20
+ if dt.tzinfo:
21
+ return dt.astimezone(UTC)
22
+ return dt.replace(tzinfo=UTC)
23
+
24
+
25
+ def format_datetime(dt: datetime | None) -> str | None:
26
+ """Format a datetime to ISO 8601 string with Z suffix for UTC."""
27
+ if dt is None:
28
+ return None
29
+ utc_dt = _utc_datetime(dt)
30
+ return utc_dt.isoformat().replace("+00:00", "Z") if utc_dt else None
31
+
32
+
33
+ # -------------------------------------------------------------------------
34
+ # API structs - inherit from db structs, add uuid for serialization
35
+ # -------------------------------------------------------------------------
36
+
37
+
38
+ class ApiUser(User, kw_only=True):
39
+ """User with uuid serialized."""
40
+
41
+ uuid: UUID
42
+
43
+ @classmethod
44
+ def from_db(cls, u: User) -> "ApiUser":
45
+ return cls(uuid=u.uuid, **msgspec.structs.asdict(u))
46
+
47
+
48
+ class ApiOrg(Org, kw_only=True):
49
+ """Org with uuid serialized."""
50
+
51
+ uuid: UUID
52
+
53
+ @classmethod
54
+ def from_db(cls, o: Org) -> "ApiOrg":
55
+ return cls(uuid=o.uuid, **msgspec.structs.asdict(o))
56
+
57
+
58
+ class ApiRole(Role, kw_only=True):
59
+ """Role with uuid serialized."""
60
+
61
+ uuid: UUID
62
+
63
+ @classmethod
64
+ def from_db(cls, r: Role) -> "ApiRole":
65
+ return cls(uuid=r.uuid, **msgspec.structs.asdict(r))
66
+
67
+
68
+ class ApiPermission(Permission, kw_only=True):
69
+ """Permission with uuid serialized."""
70
+
71
+ uuid: UUID
72
+
73
+ @classmethod
74
+ def from_db(cls, p: Permission) -> "ApiPermission":
75
+ return cls(uuid=p.uuid, **msgspec.structs.asdict(p))
76
+
77
+
78
+ class ApiSession(msgspec.Struct):
79
+ """Session for API responses with computed fields."""
80
+
81
+ id: str
82
+ credential_uuid: UUID = msgspec.field(name="credential")
83
+ host: str
84
+ ip: str
85
+ user_agent: str
86
+ last_renewed: datetime
87
+ is_current: bool = False
88
+ is_current_host: bool = False
89
+
90
+ @classmethod
91
+ def from_db(
92
+ cls,
93
+ s, # Session
94
+ *,
95
+ current_key: str,
96
+ normalized_host: str | None,
97
+ expires_delta, # timedelta
98
+ ) -> "ApiSession":
99
+ return cls(
100
+ id=s.key,
101
+ credential_uuid=s.credential_uuid,
102
+ host=s.host,
103
+ ip=s.ip,
104
+ user_agent=useragent.compact_user_agent(s.user_agent),
105
+ last_renewed=s.expiry - expires_delta,
106
+ is_current=s.key == current_key,
107
+ is_current_host=bool(
108
+ normalized_host and s.host and s.host == normalized_host
109
+ ),
110
+ )