@hitchy/plugin-auth 0.3.6 → 0.3.8
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.
- package/.editorconfig +9 -0
- package/api/model/authorization/rule.js +1 -1
- package/api/model/role.js +1 -1
- package/api/model/user.js +1 -1
- package/api/policy/authentication.js +44 -13
- package/api/policy/user.js +1 -1
- package/api/service/auth/manager.js +2 -2
- package/api/service/authentication/passport.js +3 -3
- package/api/service/authentication/strategies.js +4 -4
- package/api/service/authorization/node.js +25 -17
- package/api/service/authorization/tree.js +10 -8
- package/api/service/session.js +150 -0
- package/index.js +2 -2
- package/package.json +18 -18
- package/public/404.html +21 -0
- package/public/api/config.html +100 -0
- package/public/api/controller/index.html +24 -0
- package/public/api/controller/user.html +29 -0
- package/public/api/index.html +24 -0
- package/public/api/model/authorization-rule.html +24 -0
- package/public/api/model/index.html +24 -0
- package/public/api/model/role.html +24 -0
- package/public/api/model/user-to-role.html +24 -0
- package/public/api/model/user.html +24 -0
- package/public/api/policy/authentication.html +28 -0
- package/public/api/policy/authorization.html +31 -0
- package/public/api/policy/index.html +24 -0
- package/public/api/policy/user.html +24 -0
- package/public/api/routing.html +40 -0
- package/public/api/service/auth-manager.html +24 -0
- package/public/api/service/authentication-passport.html +24 -0
- package/public/api/service/authentication-strategies.html +24 -0
- package/public/api/service/authorization-node.html +24 -0
- package/public/api/service/authorization-policy-generator.html +42 -0
- package/public/api/service/authorization-tree.html +24 -0
- package/public/api/service/index.html +24 -0
- package/public/assets/api_config.md.BiPnBhyk.js +77 -0
- package/public/assets/api_config.md.BiPnBhyk.lean.js +1 -0
- package/public/assets/api_controller_index.md.mhiyhr_C.js +1 -0
- package/public/assets/api_controller_index.md.mhiyhr_C.lean.js +1 -0
- package/public/assets/api_controller_user.md.BiFYPTow.js +6 -0
- package/public/assets/api_controller_user.md.BiFYPTow.lean.js +1 -0
- package/public/assets/api_index.md.j6eBaebO.js +1 -0
- package/public/assets/api_index.md.j6eBaebO.lean.js +1 -0
- package/public/assets/api_model_authorization-rule.md.CFNqudsp.js +1 -0
- package/public/assets/api_model_authorization-rule.md.CFNqudsp.lean.js +1 -0
- package/public/assets/api_model_index.md.Dw3UH73J.js +1 -0
- package/public/assets/api_model_index.md.Dw3UH73J.lean.js +1 -0
- package/public/assets/api_model_role.md.DFCGXTBA.js +1 -0
- package/public/assets/api_model_role.md.DFCGXTBA.lean.js +1 -0
- package/public/assets/api_model_user-to-role.md.QNC96rs-.js +1 -0
- package/public/assets/api_model_user-to-role.md.QNC96rs-.lean.js +1 -0
- package/public/assets/api_model_user.md.C2GSzwZj.js +1 -0
- package/public/assets/api_model_user.md.C2GSzwZj.lean.js +1 -0
- package/public/assets/api_policy_authentication.md.Ccj8Rneb.js +5 -0
- package/public/assets/api_policy_authentication.md.Ccj8Rneb.lean.js +1 -0
- package/public/assets/api_policy_authorization.md.CP3y7VOT.js +8 -0
- package/public/assets/api_policy_authorization.md.CP3y7VOT.lean.js +1 -0
- package/public/assets/api_policy_index.md.CmaeRtru.js +1 -0
- package/public/assets/api_policy_index.md.CmaeRtru.lean.js +1 -0
- package/public/assets/api_policy_user.md.ePU_LHGT.js +1 -0
- package/public/assets/api_policy_user.md.ePU_LHGT.lean.js +1 -0
- package/public/assets/api_routing.md.BP98xeNw.js +17 -0
- package/public/assets/api_routing.md.BP98xeNw.lean.js +1 -0
- package/public/assets/api_service_auth-manager.md.CcpV6slZ.js +1 -0
- package/public/assets/api_service_auth-manager.md.CcpV6slZ.lean.js +1 -0
- package/public/assets/api_service_authentication-passport.md.DvhoW1TR.js +1 -0
- package/public/assets/api_service_authentication-passport.md.DvhoW1TR.lean.js +1 -0
- package/public/assets/api_service_authentication-strategies.md.DjDT2F9g.js +1 -0
- package/public/assets/api_service_authentication-strategies.md.DjDT2F9g.lean.js +1 -0
- package/public/assets/api_service_authorization-node.md.DAN4WdDZ.js +1 -0
- package/public/assets/api_service_authorization-node.md.DAN4WdDZ.lean.js +1 -0
- package/public/assets/api_service_authorization-policy-generator.md.IaQjgxfZ.js +19 -0
- package/public/assets/api_service_authorization-policy-generator.md.IaQjgxfZ.lean.js +1 -0
- package/public/assets/api_service_authorization-tree.md.I7ff4vao.js +1 -0
- package/public/assets/api_service_authorization-tree.md.I7ff4vao.lean.js +1 -0
- package/public/assets/api_service_index.md.Bfk1E4Zn.js +1 -0
- package/public/assets/api_service_index.md.Bfk1E4Zn.lean.js +1 -0
- package/public/assets/app.Bnek3cfe.js +1 -0
- package/public/assets/chunks/framework.BaHG-QLs.js +17 -0
- package/public/assets/chunks/idp-login.B596H5Zv.js +1 -0
- package/public/assets/chunks/theme.BUrgq2uM.js +1 -0
- package/public/assets/guides_getting-started.md.BMwF59kE.js +5 -0
- package/public/assets/guides_getting-started.md.BMwF59kE.lean.js +1 -0
- package/public/assets/guides_index.md.CUqoqPFW.js +1 -0
- package/public/assets/guides_index.md.CUqoqPFW.lean.js +1 -0
- package/public/assets/guides_openid-connect.md.CWezg52j.js +49 -0
- package/public/assets/guides_openid-connect.md.CWezg52j.lean.js +1 -0
- package/public/assets/guides_saml.md.BBlq_CTl.js +44 -0
- package/public/assets/guides_saml.md.BBlq_CTl.lean.js +1 -0
- package/public/assets/idp-login.B4Dj1tzS.png +0 -0
- package/public/assets/idp-saml-cert.Dyrxdyfk.png +0 -0
- package/public/assets/index.md.B8uyAhM4.js +1 -0
- package/public/assets/index.md.B8uyAhM4.lean.js +1 -0
- package/public/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 +0 -0
- package/public/assets/inter-italic-cyrillic.By2_1cv3.woff2 +0 -0
- package/public/assets/inter-italic-greek-ext.1u6EdAuj.woff2 +0 -0
- package/public/assets/inter-italic-greek.DJ8dCoTZ.woff2 +0 -0
- package/public/assets/inter-italic-latin-ext.CN1xVJS-.woff2 +0 -0
- package/public/assets/inter-italic-latin.C2AdPX0b.woff2 +0 -0
- package/public/assets/inter-italic-vietnamese.BSbpV94h.woff2 +0 -0
- package/public/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 +0 -0
- package/public/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 +0 -0
- package/public/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 +0 -0
- package/public/assets/inter-roman-greek.BBVDIX6e.woff2 +0 -0
- package/public/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 +0 -0
- package/public/assets/inter-roman-latin.Di8DUHzh.woff2 +0 -0
- package/public/assets/inter-roman-vietnamese.BjW4sHH5.woff2 +0 -0
- package/public/assets/introduction.md.DjcXFFe8.js +9 -0
- package/public/assets/introduction.md.DjcXFFe8.lean.js +1 -0
- package/public/assets/style.C4vbPc5Z.css +1 -0
- package/public/guides/getting-started.html +28 -0
- package/public/guides/index.html +24 -0
- package/public/guides/openid-connect.html +73 -0
- package/public/guides/saml.html +68 -0
- package/public/hashmap.json +1 -0
- package/public/index.html +24 -0
- package/public/introduction.html +32 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{d as _,o as a,c,r as l,n as w,a as O,t as T,b as k,w as v,e as f,T as de,_ as b,u as Oe,i as Ge,f as Ue,g as ve,h as g,j as p,k as r,p as C,l as H,m as W,q as ie,s as I,v as G,x as Z,y as K,z as pe,A as he,B as je,C as ze,D as R,F as M,E,G as ye,H as x,I as m,J as F,K as Pe,L as ee,M as q,N as te,O as qe,P as Ve,Q as We,R as Ke,S as Le,U as oe,V as Re,W as Se,X as Te,Y as Je,Z as Ye,$ as Qe,a0 as Xe,a1 as Ze}from"./framework.BaHG-QLs.js";const xe=_({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(o){return(e,t)=>(a(),c("span",{class:w(["VPBadge",e.type])},[l(e.$slots,"default",{},()=>[O(T(e.text),1)])],2))}}),et={key:0,class:"VPBackdrop"},tt=_({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(o){return(e,t)=>(a(),k(de,{name:"fade"},{default:v(()=>[e.show?(a(),c("div",et)):f("",!0)]),_:1}))}}),ot=b(tt,[["__scopeId","data-v-c79a1216"]]),P=Oe;function nt(o,e){let t,s=!1;return()=>{t&&clearTimeout(t),s?t=setTimeout(o,e):(o(),(s=!0)&&setTimeout(()=>s=!1,e))}}function le(o){return/^\//.test(o)?o:`/${o}`}function fe(o){const{pathname:e,search:t,hash:s,protocol:n}=new URL(o,"http://a.com");if(Ge(o)||o.startsWith("#")||!n.startsWith("http")||!Ue(e))return o;const{site:i}=P(),u=e.endsWith("/")||e.endsWith(".html")?o:o.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,i.value.cleanUrls?"":".html")}${t}${s}`);return ve(u)}function Y({correspondingLink:o=!1}={}){const{site:e,localeIndex:t,page:s,theme:n,hash:i}=P(),u=g(()=>{var d,$;return{label:(d=e.value.locales[t.value])==null?void 0:d.label,link:(($=e.value.locales[t.value])==null?void 0:$.link)||(t.value==="root"?"/":`/${t.value}/`)}});return{localeLinks:g(()=>Object.entries(e.value.locales).flatMap(([d,$])=>u.value.label===$.label?[]:{text:$.label,link:st($.link||(d==="root"?"/":`/${d}/`),n.value.i18nRouting!==!1&&o,s.value.relativePath.slice(u.value.link.length-1),!e.value.cleanUrls)+i.value})),currentLang:u}}function st(o,e,t,s){return e?o.replace(/\/$/,"")+le(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,s?".html":"")):o}const at=o=>(C("data-v-d6be1790"),o=o(),H(),o),rt={class:"NotFound"},it={class:"code"},lt={class:"title"},ct=at(()=>p("div",{class:"divider"},null,-1)),ut={class:"quote"},dt={class:"action"},vt=["href","aria-label"],pt=_({__name:"NotFound",setup(o){const{theme:e}=P(),{currentLang:t}=Y();return(s,n)=>{var i,u,h,d,$;return a(),c("div",rt,[p("p",it,T(((i=r(e).notFound)==null?void 0:i.code)??"404"),1),p("h1",lt,T(((u=r(e).notFound)==null?void 0:u.title)??"PAGE NOT FOUND"),1),ct,p("blockquote",ut,T(((h=r(e).notFound)==null?void 0:h.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),p("div",dt,[p("a",{class:"link",href:r(ve)(r(t).link),"aria-label":((d=r(e).notFound)==null?void 0:d.linkLabel)??"go to home"},T((($=r(e).notFound)==null?void 0:$.linkText)??"Take me home"),9,vt)])])}}}),ht=b(pt,[["__scopeId","data-v-d6be1790"]]);function Ie(o,e){if(Array.isArray(o))return Q(o);if(o==null)return[];e=le(e);const t=Object.keys(o).sort((n,i)=>i.split("/").length-n.split("/").length).find(n=>e.startsWith(le(n))),s=t?o[t]:[];return Array.isArray(s)?Q(s):Q(s.items,s.base)}function ft(o){const e=[];let t=0;for(const s in o){const n=o[s];if(n.items){t=e.push(n);continue}e[t]||e.push({items:[]}),e[t].items.push(n)}return e}function _t(o){const e=[];function t(s){for(const n of s)n.text&&n.link&&e.push({text:n.text,link:n.link,docFooterText:n.docFooterText}),n.items&&t(n.items)}return t(o),e}function ce(o,e){return Array.isArray(e)?e.some(t=>ce(o,t)):W(o,e.link)?!0:e.items?ce(o,e.items):!1}function Q(o,e){return[...o].map(t=>{const s={...t},n=s.base||e;return n&&s.link&&(s.link=n+s.link),s.items&&(s.items=Q(s.items,n)),s})}function U(){const{frontmatter:o,page:e,theme:t}=P(),s=ie("(min-width: 960px)"),n=I(!1),i=g(()=>{const B=t.value.sidebar,S=e.value.relativePath;return B?Ie(B,S):[]}),u=I(i.value);G(i,(B,S)=>{JSON.stringify(B)!==JSON.stringify(S)&&(u.value=i.value)});const h=g(()=>o.value.sidebar!==!1&&u.value.length>0&&o.value.layout!=="home"),d=g(()=>$?o.value.aside==null?t.value.aside==="left":o.value.aside==="left":!1),$=g(()=>o.value.layout==="home"?!1:o.value.aside!=null?!!o.value.aside:t.value.aside!==!1),V=g(()=>h.value&&s.value),y=g(()=>h.value?ft(u.value):[]);function L(){n.value=!0}function N(){n.value=!1}function A(){n.value?N():L()}return{isOpen:n,sidebar:u,sidebarGroups:y,hasSidebar:h,hasAside:$,leftAside:d,isSidebarEnabled:V,open:L,close:N,toggle:A}}function mt(o,e){let t;Z(()=>{t=o.value?document.activeElement:void 0}),K(()=>{window.addEventListener("keyup",s)}),pe(()=>{window.removeEventListener("keyup",s)});function s(n){n.key==="Escape"&&o.value&&(e(),t==null||t.focus())}}function kt(o){const{page:e,hash:t}=P(),s=I(!1),n=g(()=>o.value.collapsed!=null),i=g(()=>!!o.value.link),u=I(!1),h=()=>{u.value=W(e.value.relativePath,o.value.link)};G([e,o,t],h),K(h);const d=g(()=>u.value?!0:o.value.items?ce(e.value.relativePath,o.value.items):!1),$=g(()=>!!(o.value.items&&o.value.items.length));Z(()=>{s.value=!!(n.value&&o.value.collapsed)}),he(()=>{(u.value||d.value)&&(s.value=!1)});function V(){n.value&&(s.value=!s.value)}return{collapsed:s,collapsible:n,isLink:i,isActiveLink:u,hasActiveLink:d,hasChildren:$,toggle:V}}function bt(){const{hasSidebar:o}=U(),e=ie("(min-width: 960px)"),t=ie("(min-width: 1280px)");return{isAsideEnabled:g(()=>!t.value&&!e.value?!1:o.value?t.value:e.value)}}const ue=[];function we(o){return typeof o.outline=="object"&&!Array.isArray(o.outline)&&o.outline.label||o.outlineTitle||"On this page"}function _e(o){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const s=Number(t.tagName[1]);return{element:t,title:$t(t),link:"#"+t.id,level:s}});return gt(e,o)}function $t(o){let e="";for(const t of o.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor")||t.classList.contains("ignore-header"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function gt(o,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[s,n]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;o=o.filter(u=>u.level>=s&&u.level<=n),ue.length=0;for(const{element:u,link:h}of o)ue.push({element:u,link:h});const i=[];e:for(let u=0;u<o.length;u++){const h=o[u];if(u===0)i.push(h);else{for(let d=u-1;d>=0;d--){const $=o[d];if($.level<h.level){($.children||($.children=[])).push(h);continue e}}i.push(h)}}return i}function yt(o,e){const{isAsideEnabled:t}=bt(),s=nt(i,100);let n=null;K(()=>{requestAnimationFrame(i),window.addEventListener("scroll",s)}),je(()=>{u(location.hash)}),pe(()=>{window.removeEventListener("scroll",s)});function i(){if(!t.value)return;const h=window.scrollY,d=window.innerHeight,$=document.body.offsetHeight,V=Math.abs(h+d-$)<1,y=ue.map(({element:N,link:A})=>({link:A,top:Pt(N)})).filter(({top:N})=>!Number.isNaN(N)).sort((N,A)=>N.top-A.top);if(!y.length){u(null);return}if(h<1){u(null);return}if(V){u(y[y.length-1].link);return}let L=null;for(const{link:N,top:A}of y){if(A>h+ze()+4)break;L=N}u(L)}function u(h){n&&n.classList.remove("active"),h==null?n=null:n=o.value.querySelector(`a[href="${decodeURIComponent(h)}"]`);const d=n;d?(d.classList.add("active"),e.value.style.top=d.offsetTop+39+"px",e.value.style.opacity="1"):(e.value.style.top="33px",e.value.style.opacity="0")}}function Pt(o){let e=0;for(;o!==document.body;){if(o===null)return NaN;e+=o.offsetTop,o=o.offsetParent}return e}const Vt=["href","title"],Lt=_({__name:"VPDocOutlineItem",props:{headers:{},root:{type:Boolean}},setup(o){function e({target:t}){const s=t.href.split("#")[1],n=document.getElementById(decodeURIComponent(s));n==null||n.focus({preventScroll:!0})}return(t,s)=>{const n=R("VPDocOutlineItem",!0);return a(),c("ul",{class:w(["VPDocOutlineItem",t.root?"root":"nested"])},[(a(!0),c(M,null,E(t.headers,({children:i,link:u,title:h})=>(a(),c("li",null,[p("a",{class:"outline-link",href:u,onClick:e,title:h},T(h),9,Vt),i!=null&&i.length?(a(),k(n,{key:0,headers:i},null,8,["headers"])):f("",!0)]))),256))],2)}}}),Ne=b(Lt,[["__scopeId","data-v-b933a997"]]),St={class:"content"},Tt={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},It=_({__name:"VPDocAsideOutline",setup(o){const{frontmatter:e,theme:t}=P(),s=ye([]);x(()=>{s.value=_e(e.value.outline??t.value.outline)});const n=I(),i=I();return yt(n,i),(u,h)=>(a(),c("nav",{"aria-labelledby":"doc-outline-aria-label",class:w(["VPDocAsideOutline",{"has-outline":s.value.length>0}]),ref_key:"container",ref:n},[p("div",St,[p("div",{class:"outline-marker",ref_key:"marker",ref:i},null,512),p("div",Tt,T(r(we)(r(t))),1),m(Ne,{headers:s.value,root:!0},null,8,["headers"])])],2))}}),wt=b(It,[["__scopeId","data-v-a5bbad30"]]),Nt={class:"VPDocAsideCarbonAds"},Mt=_({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(o){const e=()=>null;return(t,s)=>(a(),c("div",Nt,[m(r(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),At=o=>(C("data-v-3f215769"),o=o(),H(),o),Bt={class:"VPDocAside"},Ct=At(()=>p("div",{class:"spacer"},null,-1)),Ht=_({__name:"VPDocAside",setup(o){const{theme:e}=P();return(t,s)=>(a(),c("div",Bt,[l(t.$slots,"aside-top",{},void 0,!0),l(t.$slots,"aside-outline-before",{},void 0,!0),m(wt),l(t.$slots,"aside-outline-after",{},void 0,!0),Ct,l(t.$slots,"aside-ads-before",{},void 0,!0),r(e).carbonAds?(a(),k(Mt,{key:0,"carbon-ads":r(e).carbonAds},null,8,["carbon-ads"])):f("",!0),l(t.$slots,"aside-ads-after",{},void 0,!0),l(t.$slots,"aside-bottom",{},void 0,!0)]))}}),Et=b(Ht,[["__scopeId","data-v-3f215769"]]);function Ft(){const{theme:o,page:e}=P();return g(()=>{const{text:t="Edit this page",pattern:s=""}=o.value.editLink||{};let n;return typeof s=="function"?n=s(e.value):n=s.replace(/:path/g,e.value.filePath),{url:n,text:t}})}function Dt(){const{page:o,theme:e,frontmatter:t}=P();return g(()=>{var $,V,y,L,N,A,B,S;const s=Ie(e.value.sidebar,o.value.relativePath),n=_t(s),i=Ot(n,j=>j.link.replace(/[?#].*$/,"")),u=i.findIndex(j=>W(o.value.relativePath,j.link)),h=(($=e.value.docFooter)==null?void 0:$.prev)===!1&&!t.value.prev||t.value.prev===!1,d=((V=e.value.docFooter)==null?void 0:V.next)===!1&&!t.value.next||t.value.next===!1;return{prev:h?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((y=i[u-1])==null?void 0:y.docFooterText)??((L=i[u-1])==null?void 0:L.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((N=i[u-1])==null?void 0:N.link)},next:d?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((A=i[u+1])==null?void 0:A.docFooterText)??((B=i[u+1])==null?void 0:B.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((S=i[u+1])==null?void 0:S.link)}}})}function Ot(o,e){const t=new Set;return o.filter(s=>{const n=e(s);return t.has(n)?!1:t.add(n)})}const D=_({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(o){const e=o,t=g(()=>e.tag??(e.href?"a":"span")),s=g(()=>e.href&&Pe.test(e.href)||e.target==="_blank");return(n,i)=>(a(),k(F(t.value),{class:w(["VPLink",{link:n.href,"vp-external-link-icon":s.value,"no-icon":n.noIcon}]),href:n.href?r(fe)(n.href):void 0,target:n.target??(s.value?"_blank":void 0),rel:n.rel??(s.value?"noreferrer":void 0)},{default:v(()=>[l(n.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Gt={class:"VPLastUpdated"},Ut=["datetime"],jt=_({__name:"VPDocFooterLastUpdated",setup(o){const{theme:e,page:t,lang:s}=P(),n=g(()=>new Date(t.value.lastUpdated)),i=g(()=>n.value.toISOString()),u=I("");return K(()=>{Z(()=>{var h,d,$;u.value=new Intl.DateTimeFormat((d=(h=e.value.lastUpdated)==null?void 0:h.formatOptions)!=null&&d.forceLocale?s.value:void 0,(($=e.value.lastUpdated)==null?void 0:$.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(n.value)})}),(h,d)=>{var $;return a(),c("p",Gt,[O(T((($=r(e).lastUpdated)==null?void 0:$.text)||r(e).lastUpdatedText||"Last updated")+": ",1),p("time",{datetime:i.value},T(u.value),9,Ut)])}}}),zt=b(jt,[["__scopeId","data-v-e98dd255"]]),Me=o=>(C("data-v-e257564d"),o=o(),H(),o),qt={key:0,class:"VPDocFooter"},Wt={key:0,class:"edit-info"},Kt={key:0,class:"edit-link"},Rt=Me(()=>p("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),Jt={key:1,class:"last-updated"},Yt={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Qt=Me(()=>p("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),Xt={class:"pager"},Zt=["innerHTML"],xt=["innerHTML"],eo={class:"pager"},to=["innerHTML"],oo=["innerHTML"],no=_({__name:"VPDocFooter",setup(o){const{theme:e,page:t,frontmatter:s}=P(),n=Ft(),i=Dt(),u=g(()=>e.value.editLink&&s.value.editLink!==!1),h=g(()=>t.value.lastUpdated),d=g(()=>u.value||h.value||i.value.prev||i.value.next);return($,V)=>{var y,L,N,A;return d.value?(a(),c("footer",qt,[l($.$slots,"doc-footer-before",{},void 0,!0),u.value||h.value?(a(),c("div",Wt,[u.value?(a(),c("div",Kt,[m(D,{class:"edit-link-button",href:r(n).url,"no-icon":!0},{default:v(()=>[Rt,O(" "+T(r(n).text),1)]),_:1},8,["href"])])):f("",!0),h.value?(a(),c("div",Jt,[m(zt)])):f("",!0)])):f("",!0),(y=r(i).prev)!=null&&y.link||(L=r(i).next)!=null&&L.link?(a(),c("nav",Yt,[Qt,p("div",Xt,[(N=r(i).prev)!=null&&N.link?(a(),k(D,{key:0,class:"pager-link prev",href:r(i).prev.link},{default:v(()=>{var B;return[p("span",{class:"desc",innerHTML:((B=r(e).docFooter)==null?void 0:B.prev)||"Previous page"},null,8,Zt),p("span",{class:"title",innerHTML:r(i).prev.text},null,8,xt)]}),_:1},8,["href"])):f("",!0)]),p("div",eo,[(A=r(i).next)!=null&&A.link?(a(),k(D,{key:0,class:"pager-link next",href:r(i).next.link},{default:v(()=>{var B;return[p("span",{class:"desc",innerHTML:((B=r(e).docFooter)==null?void 0:B.next)||"Next page"},null,8,to),p("span",{class:"title",innerHTML:r(i).next.text},null,8,oo)]}),_:1},8,["href"])):f("",!0)])])):f("",!0)])):f("",!0)}}}),so=b(no,[["__scopeId","data-v-e257564d"]]),ao=o=>(C("data-v-39a288b8"),o=o(),H(),o),ro={class:"container"},io=ao(()=>p("div",{class:"aside-curtain"},null,-1)),lo={class:"aside-container"},co={class:"aside-content"},uo={class:"content"},vo={class:"content-container"},po={class:"main"},ho=_({__name:"VPDoc",setup(o){const{theme:e}=P(),t=ee(),{hasSidebar:s,hasAside:n,leftAside:i}=U(),u=g(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(h,d)=>{const $=R("Content");return a(),c("div",{class:w(["VPDoc",{"has-sidebar":r(s),"has-aside":r(n)}])},[l(h.$slots,"doc-top",{},void 0,!0),p("div",ro,[r(n)?(a(),c("div",{key:0,class:w(["aside",{"left-aside":r(i)}])},[io,p("div",lo,[p("div",co,[m(Et,null,{"aside-top":v(()=>[l(h.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[l(h.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[l(h.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(h.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(h.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(h.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):f("",!0),p("div",uo,[p("div",vo,[l(h.$slots,"doc-before",{},void 0,!0),p("main",po,[m($,{class:w(["vp-doc",[u.value,r(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),m(so,null,{"doc-footer-before":v(()=>[l(h.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),l(h.$slots,"doc-after",{},void 0,!0)])])]),l(h.$slots,"doc-bottom",{},void 0,!0)],2)}}}),fo=b(ho,[["__scopeId","data-v-39a288b8"]]),_o=_({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(o){const e=o,t=g(()=>e.href&&Pe.test(e.href)),s=g(()=>e.tag||e.href?"a":"button");return(n,i)=>(a(),k(F(s.value),{class:w(["VPButton",[n.size,n.theme]]),href:n.href?r(fe)(n.href):void 0,target:e.target??(t.value?"_blank":void 0),rel:e.rel??(t.value?"noreferrer":void 0)},{default:v(()=>[O(T(n.text),1)]),_:1},8,["class","href","target","rel"]))}}),mo=b(_o,[["__scopeId","data-v-cad61b99"]]),ko=["src","alt"],bo=_({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(o){return(e,t)=>{const s=R("VPImage",!0);return e.image?(a(),c(M,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),c("img",q({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:r(ve)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,ko)):(a(),c(M,{key:1},[m(s,q({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),m(s,q({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):f("",!0)}}}),X=b(bo,[["__scopeId","data-v-8426fc1a"]]),$o=o=>(C("data-v-303bb580"),o=o(),H(),o),go={class:"container"},yo={class:"main"},Po={key:0,class:"name"},Vo=["innerHTML"],Lo=["innerHTML"],So=["innerHTML"],To={key:0,class:"actions"},Io={key:0,class:"image"},wo={class:"image-container"},No=$o(()=>p("div",{class:"image-bg"},null,-1)),Mo=_({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(o){const e=te("hero-image-slot-exists");return(t,s)=>(a(),c("div",{class:w(["VPHero",{"has-image":t.image||r(e)}])},[p("div",go,[p("div",yo,[l(t.$slots,"home-hero-info-before",{},void 0,!0),l(t.$slots,"home-hero-info",{},()=>[t.name?(a(),c("h1",Po,[p("span",{innerHTML:t.name,class:"clip"},null,8,Vo)])):f("",!0),t.text?(a(),c("p",{key:1,innerHTML:t.text,class:"text"},null,8,Lo)):f("",!0),t.tagline?(a(),c("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,So)):f("",!0)],!0),l(t.$slots,"home-hero-info-after",{},void 0,!0),t.actions?(a(),c("div",To,[(a(!0),c(M,null,E(t.actions,n=>(a(),c("div",{key:n.link,class:"action"},[m(mo,{tag:"a",size:"medium",theme:n.theme,text:n.text,href:n.link,target:n.target,rel:n.rel},null,8,["theme","text","href","target","rel"])]))),128))])):f("",!0),l(t.$slots,"home-hero-actions-after",{},void 0,!0)]),t.image||r(e)?(a(),c("div",Io,[p("div",wo,[No,l(t.$slots,"home-hero-image",{},()=>[t.image?(a(),k(X,{key:0,class:"image-src",image:t.image},null,8,["image"])):f("",!0)],!0)])])):f("",!0)])],2))}}),Ao=b(Mo,[["__scopeId","data-v-303bb580"]]),Bo=_({__name:"VPHomeHero",setup(o){const{frontmatter:e}=P();return(t,s)=>r(e).hero?(a(),k(Ao,{key:0,class:"VPHomeHero",name:r(e).hero.name,text:r(e).hero.text,tagline:r(e).hero.tagline,image:r(e).hero.image,actions:r(e).hero.actions},{"home-hero-info-before":v(()=>[l(t.$slots,"home-hero-info-before")]),"home-hero-info":v(()=>[l(t.$slots,"home-hero-info")]),"home-hero-info-after":v(()=>[l(t.$slots,"home-hero-info-after")]),"home-hero-actions-after":v(()=>[l(t.$slots,"home-hero-actions-after")]),"home-hero-image":v(()=>[l(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):f("",!0)}}),Co=o=>(C("data-v-a3976bdc"),o=o(),H(),o),Ho={class:"box"},Eo={key:0,class:"icon"},Fo=["innerHTML"],Do=["innerHTML"],Oo=["innerHTML"],Go={key:4,class:"link-text"},Uo={class:"link-text-value"},jo=Co(()=>p("span",{class:"vpi-arrow-right link-text-icon"},null,-1)),zo=_({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(o){return(e,t)=>(a(),k(D,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:v(()=>[p("article",Ho,[typeof e.icon=="object"&&e.icon.wrap?(a(),c("div",Eo,[m(X,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),k(X,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),c("div",{key:2,class:"icon",innerHTML:e.icon},null,8,Fo)):f("",!0),p("h2",{class:"title",innerHTML:e.title},null,8,Do),e.details?(a(),c("p",{key:3,class:"details",innerHTML:e.details},null,8,Oo)):f("",!0),e.linkText?(a(),c("div",Go,[p("p",Uo,[O(T(e.linkText)+" ",1),jo])])):f("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),qo=b(zo,[["__scopeId","data-v-a3976bdc"]]),Wo={key:0,class:"VPFeatures"},Ko={class:"container"},Ro={class:"items"},Jo=_({__name:"VPFeatures",props:{features:{}},setup(o){const e=o,t=g(()=>{const s=e.features.length;if(s){if(s===2)return"grid-2";if(s===3)return"grid-3";if(s%3===0)return"grid-6";if(s>3)return"grid-4"}else return});return(s,n)=>s.features?(a(),c("div",Wo,[p("div",Ko,[p("div",Ro,[(a(!0),c(M,null,E(s.features,i=>(a(),c("div",{key:i.title,class:w(["item",[t.value]])},[m(qo,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText,rel:i.rel,target:i.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):f("",!0)}}),Yo=b(Jo,[["__scopeId","data-v-a6181336"]]),Qo=_({__name:"VPHomeFeatures",setup(o){const{frontmatter:e}=P();return(t,s)=>r(e).features?(a(),k(Yo,{key:0,class:"VPHomeFeatures",features:r(e).features},null,8,["features"])):f("",!0)}}),Xo=_({__name:"VPHomeContent",setup(o){const{width:e}=qe({initialWidth:0,includeScrollbar:!1});return(t,s)=>(a(),c("div",{class:"vp-doc container",style:Ve(r(e)?{"--vp-offset":`calc(50% - ${r(e)/2}px)`}:{})},[l(t.$slots,"default",{},void 0,!0)],4))}}),Zo=b(Xo,[["__scopeId","data-v-8e2d4988"]]),xo={class:"VPHome"},en=_({__name:"VPHome",setup(o){const{frontmatter:e}=P();return(t,s)=>{const n=R("Content");return a(),c("div",xo,[l(t.$slots,"home-hero-before",{},void 0,!0),m(Bo,null,{"home-hero-info-before":v(()=>[l(t.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(t.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(t.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(t.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(t.$slots,"home-hero-image",{},void 0,!0)]),_:3}),l(t.$slots,"home-hero-after",{},void 0,!0),l(t.$slots,"home-features-before",{},void 0,!0),m(Qo),l(t.$slots,"home-features-after",{},void 0,!0),r(e).markdownStyles!==!1?(a(),k(Zo,{key:0},{default:v(()=>[m(n)]),_:1})):(a(),k(n,{key:1}))])}}}),tn=b(en,[["__scopeId","data-v-686f80a6"]]),on={},nn={class:"VPPage"};function sn(o,e){const t=R("Content");return a(),c("div",nn,[l(o.$slots,"page-top"),m(t),l(o.$slots,"page-bottom")])}const an=b(on,[["render",sn]]),rn=_({__name:"VPContent",setup(o){const{page:e,frontmatter:t}=P(),{hasSidebar:s}=U();return(n,i)=>(a(),c("div",{class:w(["VPContent",{"has-sidebar":r(s),"is-home":r(t).layout==="home"}]),id:"VPContent"},[r(e).isNotFound?l(n.$slots,"not-found",{key:0},()=>[m(ht)],!0):r(t).layout==="page"?(a(),k(an,{key:1},{"page-top":v(()=>[l(n.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[l(n.$slots,"page-bottom",{},void 0,!0)]),_:3})):r(t).layout==="home"?(a(),k(tn,{key:2},{"home-hero-before":v(()=>[l(n.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[l(n.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(n.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(n.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(n.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(n.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[l(n.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[l(n.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[l(n.$slots,"home-features-after",{},void 0,!0)]),_:3})):r(t).layout&&r(t).layout!=="doc"?(a(),k(F(r(t).layout),{key:3})):(a(),k(fo,{key:4},{"doc-top":v(()=>[l(n.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[l(n.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":v(()=>[l(n.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[l(n.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[l(n.$slots,"doc-after",{},void 0,!0)]),"aside-top":v(()=>[l(n.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":v(()=>[l(n.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(n.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(n.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(n.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":v(()=>[l(n.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),ln=b(rn,[["__scopeId","data-v-1428d186"]]),cn={class:"container"},un=["innerHTML"],dn=["innerHTML"],vn=_({__name:"VPFooter",setup(o){const{theme:e,frontmatter:t}=P(),{hasSidebar:s}=U();return(n,i)=>r(e).footer&&r(t).footer!==!1?(a(),c("footer",{key:0,class:w(["VPFooter",{"has-sidebar":r(s)}])},[p("div",cn,[r(e).footer.message?(a(),c("p",{key:0,class:"message",innerHTML:r(e).footer.message},null,8,un)):f("",!0),r(e).footer.copyright?(a(),c("p",{key:1,class:"copyright",innerHTML:r(e).footer.copyright},null,8,dn)):f("",!0)])],2)):f("",!0)}}),pn=b(vn,[["__scopeId","data-v-e315a0ad"]]);function hn(){const{theme:o,frontmatter:e}=P(),t=ye([]),s=g(()=>t.value.length>0);return x(()=>{t.value=_e(e.value.outline??o.value.outline)}),{headers:t,hasLocalNav:s}}const fn=o=>(C("data-v-17a5e62e"),o=o(),H(),o),_n={class:"menu-text"},mn=fn(()=>p("span",{class:"vpi-chevron-right icon"},null,-1)),kn={class:"header"},bn={class:"outline"},$n=_({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(o){const e=o,{theme:t}=P(),s=I(!1),n=I(0),i=I(),u=I();function h(y){var L;(L=i.value)!=null&&L.contains(y.target)||(s.value=!1)}G(s,y=>{if(y){document.addEventListener("click",h);return}document.removeEventListener("click",h)}),We("Escape",()=>{s.value=!1}),x(()=>{s.value=!1});function d(){s.value=!s.value,n.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function $(y){y.target.classList.contains("outline-link")&&(u.value&&(u.value.style.transition="none"),Ke(()=>{s.value=!1}))}function V(){s.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(y,L)=>(a(),c("div",{class:"VPLocalNavOutlineDropdown",style:Ve({"--vp-vh":n.value+"px"}),ref_key:"main",ref:i},[y.headers.length>0?(a(),c("button",{key:0,onClick:d,class:w({open:s.value})},[p("span",_n,T(r(we)(r(t))),1),mn],2)):(a(),c("button",{key:1,onClick:V},T(r(t).returnToTopLabel||"Return to top"),1)),m(de,{name:"flyout"},{default:v(()=>[s.value?(a(),c("div",{key:0,ref_key:"items",ref:u,class:"items",onClick:$},[p("div",kn,[p("a",{class:"top-link",href:"#",onClick:V},T(r(t).returnToTopLabel||"Return to top"),1)]),p("div",bn,[m(Ne,{headers:y.headers},null,8,["headers"])])],512)):f("",!0)]),_:1})],4))}}),gn=b($n,[["__scopeId","data-v-17a5e62e"]]),yn=o=>(C("data-v-a6f0e41e"),o=o(),H(),o),Pn={class:"container"},Vn=["aria-expanded"],Ln=yn(()=>p("span",{class:"vpi-align-left menu-icon"},null,-1)),Sn={class:"menu-text"},Tn=_({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(o){const{theme:e,frontmatter:t}=P(),{hasSidebar:s}=U(),{headers:n}=hn(),{y:i}=Le(),u=I(0);K(()=>{u.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),x(()=>{n.value=_e(t.value.outline??e.value.outline)});const h=g(()=>n.value.length===0),d=g(()=>h.value&&!s.value),$=g(()=>({VPLocalNav:!0,"has-sidebar":s.value,empty:h.value,fixed:d.value}));return(V,y)=>r(t).layout!=="home"&&(!d.value||r(i)>=u.value)?(a(),c("div",{key:0,class:w($.value)},[p("div",Pn,[r(s)?(a(),c("button",{key:0,class:"menu","aria-expanded":V.open,"aria-controls":"VPSidebarNav",onClick:y[0]||(y[0]=L=>V.$emit("open-menu"))},[Ln,p("span",Sn,T(r(e).sidebarMenuLabel||"Menu"),1)],8,Vn)):f("",!0),m(gn,{headers:r(n),navHeight:u.value},null,8,["headers","navHeight"])])],2)):f("",!0)}}),In=b(Tn,[["__scopeId","data-v-a6f0e41e"]]);function wn(){const o=I(!1);function e(){o.value=!0,window.addEventListener("resize",n)}function t(){o.value=!1,window.removeEventListener("resize",n)}function s(){o.value?t():e()}function n(){window.outerWidth>=768&&t()}const i=ee();return G(()=>i.path,t),{isScreenOpen:o,openScreen:e,closeScreen:t,toggleScreen:s}}const Nn={},Mn={class:"VPSwitch",type:"button",role:"switch"},An={class:"check"},Bn={key:0,class:"icon"};function Cn(o,e){return a(),c("button",Mn,[p("span",An,[o.$slots.default?(a(),c("span",Bn,[l(o.$slots,"default",{},void 0,!0)])):f("",!0)])])}const Hn=b(Nn,[["render",Cn],["__scopeId","data-v-1d5665e3"]]),Ae=o=>(C("data-v-5337faa4"),o=o(),H(),o),En=Ae(()=>p("span",{class:"vpi-sun sun"},null,-1)),Fn=Ae(()=>p("span",{class:"vpi-moon moon"},null,-1)),Dn=_({__name:"VPSwitchAppearance",setup(o){const{isDark:e,theme:t}=P(),s=te("toggle-appearance",()=>{e.value=!e.value}),n=I("");return he(()=>{n.value=e.value?t.value.lightModeSwitchTitle||"Switch to light theme":t.value.darkModeSwitchTitle||"Switch to dark theme"}),(i,u)=>(a(),k(Hn,{title:n.value,class:"VPSwitchAppearance","aria-checked":r(e),onClick:r(s)},{default:v(()=>[En,Fn]),_:1},8,["title","aria-checked","onClick"]))}}),me=b(Dn,[["__scopeId","data-v-5337faa4"]]),On={key:0,class:"VPNavBarAppearance"},Gn=_({__name:"VPNavBarAppearance",setup(o){const{site:e}=P();return(t,s)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),c("div",On,[m(me)])):f("",!0)}}),Un=b(Gn,[["__scopeId","data-v-6c893767"]]),ke=I();let Be=!1,re=0;function jn(o){const e=I(!1);if(oe){!Be&&zn(),re++;const t=G(ke,s=>{var n,i,u;s===o.el.value||(n=o.el.value)!=null&&n.contains(s)?(e.value=!0,(i=o.onFocus)==null||i.call(o)):(e.value=!1,(u=o.onBlur)==null||u.call(o))});pe(()=>{t(),re--,re||qn()})}return Re(e)}function zn(){document.addEventListener("focusin",Ce),Be=!0,ke.value=document.activeElement}function qn(){document.removeEventListener("focusin",Ce)}function Ce(){ke.value=document.activeElement}const Wn={class:"VPMenuLink"},Kn=_({__name:"VPMenuLink",props:{item:{}},setup(o){const{page:e}=P();return(t,s)=>(a(),c("div",Wn,[m(D,{class:w({active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel},{default:v(()=>[O(T(t.item.text),1)]),_:1},8,["class","href","target","rel"])]))}}),ne=b(Kn,[["__scopeId","data-v-43f1e123"]]),Rn={class:"VPMenuGroup"},Jn={key:0,class:"title"},Yn=_({__name:"VPMenuGroup",props:{text:{},items:{}},setup(o){return(e,t)=>(a(),c("div",Rn,[e.text?(a(),c("p",Jn,T(e.text),1)):f("",!0),(a(!0),c(M,null,E(e.items,s=>(a(),c(M,null,["link"in s?(a(),k(ne,{key:0,item:s},null,8,["item"])):f("",!0)],64))),256))]))}}),Qn=b(Yn,[["__scopeId","data-v-69e747b5"]]),Xn={class:"VPMenu"},Zn={key:0,class:"items"},xn=_({__name:"VPMenu",props:{items:{}},setup(o){return(e,t)=>(a(),c("div",Xn,[e.items?(a(),c("div",Zn,[(a(!0),c(M,null,E(e.items,s=>(a(),c(M,{key:JSON.stringify(s)},["link"in s?(a(),k(ne,{key:0,item:s},null,8,["item"])):"component"in s?(a(),k(F(s.component),q({key:1,ref_for:!0},s.props),null,16)):(a(),k(Qn,{key:2,text:s.text,items:s.items},null,8,["text","items"]))],64))),128))])):f("",!0),l(e.$slots,"default",{},void 0,!0)]))}}),es=b(xn,[["__scopeId","data-v-b98bc113"]]),ts=o=>(C("data-v-b6c34ac9"),o=o(),H(),o),os=["aria-expanded","aria-label"],ns={key:0,class:"text"},ss=["innerHTML"],as=ts(()=>p("span",{class:"vpi-chevron-down text-icon"},null,-1)),rs={key:1,class:"vpi-more-horizontal icon"},is={class:"menu"},ls=_({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(o){const e=I(!1),t=I();jn({el:t,onBlur:s});function s(){e.value=!1}return(n,i)=>(a(),c("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:i[1]||(i[1]=u=>e.value=!0),onMouseleave:i[2]||(i[2]=u=>e.value=!1)},[p("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":n.label,onClick:i[0]||(i[0]=u=>e.value=!e.value)},[n.button||n.icon?(a(),c("span",ns,[n.icon?(a(),c("span",{key:0,class:w([n.icon,"option-icon"])},null,2)):f("",!0),n.button?(a(),c("span",{key:1,innerHTML:n.button},null,8,ss)):f("",!0),as])):(a(),c("span",rs))],8,os),p("div",is,[m(es,{items:n.items},{default:v(()=>[l(n.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),be=b(ls,[["__scopeId","data-v-b6c34ac9"]]),cs=["href","aria-label","innerHTML"],us=_({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(o){const e=o,t=g(()=>typeof e.icon=="object"?e.icon.svg:`<span class="vpi-social-${e.icon}" />`);return(s,n)=>(a(),c("a",{class:"VPSocialLink no-icon",href:s.link,"aria-label":s.ariaLabel??(typeof s.icon=="string"?s.icon:""),target:"_blank",rel:"noopener",innerHTML:t.value},null,8,cs))}}),ds=b(us,[["__scopeId","data-v-eee4e7cb"]]),vs={class:"VPSocialLinks"},ps=_({__name:"VPSocialLinks",props:{links:{}},setup(o){return(e,t)=>(a(),c("div",vs,[(a(!0),c(M,null,E(e.links,({link:s,icon:n,ariaLabel:i})=>(a(),k(ds,{key:s,icon:n,link:s,ariaLabel:i},null,8,["icon","link","ariaLabel"]))),128))]))}}),$e=b(ps,[["__scopeId","data-v-7bc22406"]]),hs={key:0,class:"group translations"},fs={class:"trans-title"},_s={key:1,class:"group"},ms={class:"item appearance"},ks={class:"label"},bs={class:"appearance-action"},$s={key:2,class:"group"},gs={class:"item social-links"},ys=_({__name:"VPNavBarExtra",setup(o){const{site:e,theme:t}=P(),{localeLinks:s,currentLang:n}=Y({correspondingLink:!0}),i=g(()=>s.value.length&&n.value.label||e.value.appearance||t.value.socialLinks);return(u,h)=>i.value?(a(),k(be,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:v(()=>[r(s).length&&r(n).label?(a(),c("div",hs,[p("p",fs,T(r(n).label),1),(a(!0),c(M,null,E(r(s),d=>(a(),k(ne,{key:d.link,item:d},null,8,["item"]))),128))])):f("",!0),r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),c("div",_s,[p("div",ms,[p("p",ks,T(r(t).darkModeSwitchLabel||"Appearance"),1),p("div",bs,[m(me)])])])):f("",!0),r(t).socialLinks?(a(),c("div",$s,[p("div",gs,[m($e,{class:"social-links-list",links:r(t).socialLinks},null,8,["links"])])])):f("",!0)]),_:1})):f("",!0)}}),Ps=b(ys,[["__scopeId","data-v-bb2aa2f0"]]),Vs=o=>(C("data-v-e5dd9c1c"),o=o(),H(),o),Ls=["aria-expanded"],Ss=Vs(()=>p("span",{class:"container"},[p("span",{class:"top"}),p("span",{class:"middle"}),p("span",{class:"bottom"})],-1)),Ts=[Ss],Is=_({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(o){return(e,t)=>(a(),c("button",{type:"button",class:w(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=s=>e.$emit("click"))},Ts,10,Ls))}}),ws=b(Is,[["__scopeId","data-v-e5dd9c1c"]]),Ns=["innerHTML"],Ms=_({__name:"VPNavBarMenuLink",props:{item:{}},setup(o){const{page:e}=P();return(t,s)=>(a(),k(D,{class:w({VPNavBarMenuLink:!0,active:r(W)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,noIcon:t.item.noIcon,target:t.item.target,rel:t.item.rel,tabindex:"0"},{default:v(()=>[p("span",{innerHTML:t.item.text},null,8,Ns)]),_:1},8,["class","href","noIcon","target","rel"]))}}),As=b(Ms,[["__scopeId","data-v-9c663999"]]),Bs=_({__name:"VPNavBarMenuGroup",props:{item:{}},setup(o){const e=o,{page:t}=P(),s=i=>"component"in i?!1:"link"in i?W(t.value.relativePath,i.link,!!e.item.activeMatch):i.items.some(s),n=g(()=>s(e.item));return(i,u)=>(a(),k(be,{class:w({VPNavBarMenuGroup:!0,active:r(W)(r(t).relativePath,i.item.activeMatch,!!i.item.activeMatch)||n.value}),button:i.item.text,items:i.item.items},null,8,["class","button","items"]))}}),Cs=o=>(C("data-v-dc692963"),o=o(),H(),o),Hs={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Es=Cs(()=>p("span",{id:"main-nav-aria-label",class:"visually-hidden"}," Main Navigation ",-1)),Fs=_({__name:"VPNavBarMenu",setup(o){const{theme:e}=P();return(t,s)=>r(e).nav?(a(),c("nav",Hs,[Es,(a(!0),c(M,null,E(r(e).nav,n=>(a(),c(M,{key:JSON.stringify(n)},["link"in n?(a(),k(As,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(F(n.component),q({key:1,ref_for:!0},n.props),null,16)):(a(),k(Bs,{key:2,item:n},null,8,["item"]))],64))),128))])):f("",!0)}}),Ds=b(Fs,[["__scopeId","data-v-dc692963"]]);function Os(o){const{localeIndex:e,theme:t}=P();function s(n){var A,B,S;const i=n.split("."),u=(A=t.value.search)==null?void 0:A.options,h=u&&typeof u=="object",d=h&&((S=(B=u.locales)==null?void 0:B[e.value])==null?void 0:S.translations)||null,$=h&&u.translations||null;let V=d,y=$,L=o;const N=i.pop();for(const j of i){let z=null;const J=L==null?void 0:L[j];J&&(z=L=J);const se=y==null?void 0:y[j];se&&(z=y=se);const ae=V==null?void 0:V[j];ae&&(z=V=ae),J||(L=z),se||(y=z),ae||(V=z)}return(V==null?void 0:V[N])??(y==null?void 0:y[N])??(L==null?void 0:L[N])??""}return s}const Gs=["aria-label"],Us={class:"DocSearch-Button-Container"},js=p("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1),zs={class:"DocSearch-Button-Placeholder"},qs=p("span",{class:"DocSearch-Button-Keys"},[p("kbd",{class:"DocSearch-Button-Key"}),p("kbd",{class:"DocSearch-Button-Key"},"K")],-1),ge=_({__name:"VPNavBarSearchButton",setup(o){const t=Os({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(s,n)=>(a(),c("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":r(t)("button.buttonAriaLabel")},[p("span",Us,[js,p("span",zs,T(r(t)("button.buttonText")),1)]),qs],8,Gs))}}),Ws={class:"VPNavBarSearch"},Ks={id:"local-search"},Rs={key:1,id:"docsearch"},Js=_({__name:"VPNavBarSearch",setup(o){const e=()=>null,t=()=>null,{theme:s}=P(),n=I(!1),i=I(!1);K(()=>{});function u(){n.value||(n.value=!0,setTimeout(h,16))}function h(){const V=new Event("keydown");V.key="k",V.metaKey=!0,window.dispatchEvent(V),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||h()},16)}const d=I(!1),$="";return(V,y)=>{var L;return a(),c("div",Ws,[r($)==="local"?(a(),c(M,{key:0},[d.value?(a(),k(r(e),{key:0,onClose:y[0]||(y[0]=N=>d.value=!1)})):f("",!0),p("div",Ks,[m(ge,{onClick:y[1]||(y[1]=N=>d.value=!0)})])],64)):r($)==="algolia"?(a(),c(M,{key:1},[n.value?(a(),k(r(t),{key:0,algolia:((L=r(s).search)==null?void 0:L.options)??r(s).algolia,onVnodeBeforeMount:y[2]||(y[2]=N=>i.value=!0)},null,8,["algolia"])):f("",!0),i.value?f("",!0):(a(),c("div",Rs,[m(ge,{onClick:u})]))],64)):f("",!0)])}}}),Ys=_({__name:"VPNavBarSocialLinks",setup(o){const{theme:e}=P();return(t,s)=>r(e).socialLinks?(a(),k($e,{key:0,class:"VPNavBarSocialLinks",links:r(e).socialLinks},null,8,["links"])):f("",!0)}}),Qs=b(Ys,[["__scopeId","data-v-0394ad82"]]),Xs=["href","rel","target"],Zs={key:1},xs={key:2},ea=_({__name:"VPNavBarTitle",setup(o){const{site:e,theme:t}=P(),{hasSidebar:s}=U(),{currentLang:n}=Y(),i=g(()=>{var d;return typeof t.value.logoLink=="string"?t.value.logoLink:(d=t.value.logoLink)==null?void 0:d.link}),u=g(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.rel}),h=g(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.target});return(d,$)=>(a(),c("div",{class:w(["VPNavBarTitle",{"has-sidebar":r(s)}])},[p("a",{class:"title",href:i.value??r(fe)(r(n).link),rel:u.value,target:h.value},[l(d.$slots,"nav-bar-title-before",{},void 0,!0),r(t).logo?(a(),k(X,{key:0,class:"logo",image:r(t).logo},null,8,["image"])):f("",!0),r(t).siteTitle?(a(),c("span",Zs,T(r(t).siteTitle),1)):r(t).siteTitle===void 0?(a(),c("span",xs,T(r(e).title),1)):f("",!0),l(d.$slots,"nav-bar-title-after",{},void 0,!0)],8,Xs)],2))}}),ta=b(ea,[["__scopeId","data-v-ab179fa1"]]),oa={class:"items"},na={class:"title"},sa=_({__name:"VPNavBarTranslations",setup(o){const{theme:e}=P(),{localeLinks:t,currentLang:s}=Y({correspondingLink:!0});return(n,i)=>r(t).length&&r(s).label?(a(),k(be,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:r(e).langMenuLabel||"Change language"},{default:v(()=>[p("div",oa,[p("p",na,T(r(s).label),1),(a(!0),c(M,null,E(r(t),u=>(a(),k(ne,{key:u.link,item:u},null,8,["item"]))),128))])]),_:1},8,["label"])):f("",!0)}}),aa=b(sa,[["__scopeId","data-v-88af2de4"]]),ra=o=>(C("data-v-6aa21345"),o=o(),H(),o),ia={class:"wrapper"},la={class:"container"},ca={class:"title"},ua={class:"content"},da={class:"content-body"},va=ra(()=>p("div",{class:"divider"},[p("div",{class:"divider-line"})],-1)),pa=_({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(o){const e=o,{y:t}=Le(),{hasSidebar:s}=U(),{frontmatter:n}=P(),i=I({});return he(()=>{i.value={"has-sidebar":s.value,home:n.value.layout==="home",top:t.value===0,"screen-open":e.isScreenOpen}}),(u,h)=>(a(),c("div",{class:w(["VPNavBar",i.value])},[p("div",ia,[p("div",la,[p("div",ca,[m(ta,null,{"nav-bar-title-before":v(()=>[l(u.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(u.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),p("div",ua,[p("div",da,[l(u.$slots,"nav-bar-content-before",{},void 0,!0),m(Js,{class:"search"}),m(Ds,{class:"menu"}),m(aa,{class:"translations"}),m(Un,{class:"appearance"}),m(Qs,{class:"social-links"}),m(Ps,{class:"extra"}),l(u.$slots,"nav-bar-content-after",{},void 0,!0),m(ws,{class:"hamburger",active:u.isScreenOpen,onClick:h[0]||(h[0]=d=>u.$emit("toggle-screen"))},null,8,["active"])])])])]),va],2))}}),ha=b(pa,[["__scopeId","data-v-6aa21345"]]),fa={key:0,class:"VPNavScreenAppearance"},_a={class:"text"},ma=_({__name:"VPNavScreenAppearance",setup(o){const{site:e,theme:t}=P();return(s,n)=>r(e).appearance&&r(e).appearance!=="force-dark"&&r(e).appearance!=="force-auto"?(a(),c("div",fa,[p("p",_a,T(r(t).darkModeSwitchLabel||"Appearance"),1),m(me)])):f("",!0)}}),ka=b(ma,[["__scopeId","data-v-b44890b2"]]),ba=_({__name:"VPNavScreenMenuLink",props:{item:{}},setup(o){const e=te("close-screen");return(t,s)=>(a(),k(D,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:r(e),innerHTML:t.item.text},null,8,["href","target","rel","onClick","innerHTML"]))}}),$a=b(ba,[["__scopeId","data-v-7f31e1f6"]]),ga=_({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(o){const e=te("close-screen");return(t,s)=>(a(),k(D,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:r(e)},{default:v(()=>[O(T(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),He=b(ga,[["__scopeId","data-v-19976ae1"]]),ya={class:"VPNavScreenMenuGroupSection"},Pa={key:0,class:"title"},Va=_({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(o){return(e,t)=>(a(),c("div",ya,[e.text?(a(),c("p",Pa,T(e.text),1)):f("",!0),(a(!0),c(M,null,E(e.items,s=>(a(),k(He,{key:s.text,item:s},null,8,["item"]))),128))]))}}),La=b(Va,[["__scopeId","data-v-8133b170"]]),Sa=o=>(C("data-v-b9ab8c58"),o=o(),H(),o),Ta=["aria-controls","aria-expanded"],Ia=["innerHTML"],wa=Sa(()=>p("span",{class:"vpi-plus button-icon"},null,-1)),Na=["id"],Ma={key:0,class:"item"},Aa={key:1,class:"item"},Ba={key:2,class:"group"},Ca=_({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(o){const e=o,t=I(!1),s=g(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function n(){t.value=!t.value}return(i,u)=>(a(),c("div",{class:w(["VPNavScreenMenuGroup",{open:t.value}])},[p("button",{class:"button","aria-controls":s.value,"aria-expanded":t.value,onClick:n},[p("span",{class:"button-text",innerHTML:i.text},null,8,Ia),wa],8,Ta),p("div",{id:s.value,class:"items"},[(a(!0),c(M,null,E(i.items,h=>(a(),c(M,{key:JSON.stringify(h)},["link"in h?(a(),c("div",Ma,[m(He,{item:h},null,8,["item"])])):"component"in h?(a(),c("div",Aa,[(a(),k(F(h.component),q({ref_for:!0},h.props,{"screen-menu":""}),null,16))])):(a(),c("div",Ba,[m(La,{text:h.text,items:h.items},null,8,["text","items"])]))],64))),128))],8,Na)],2))}}),Ha=b(Ca,[["__scopeId","data-v-b9ab8c58"]]),Ea={key:0,class:"VPNavScreenMenu"},Fa=_({__name:"VPNavScreenMenu",setup(o){const{theme:e}=P();return(t,s)=>r(e).nav?(a(),c("nav",Ea,[(a(!0),c(M,null,E(r(e).nav,n=>(a(),c(M,{key:JSON.stringify(n)},["link"in n?(a(),k($a,{key:0,item:n},null,8,["item"])):"component"in n?(a(),k(F(n.component),q({key:1,ref_for:!0},n.props,{"screen-menu":""}),null,16)):(a(),k(Ha,{key:2,text:n.text||"",items:n.items},null,8,["text","items"]))],64))),128))])):f("",!0)}}),Da=_({__name:"VPNavScreenSocialLinks",setup(o){const{theme:e}=P();return(t,s)=>r(e).socialLinks?(a(),k($e,{key:0,class:"VPNavScreenSocialLinks",links:r(e).socialLinks},null,8,["links"])):f("",!0)}}),Ee=o=>(C("data-v-858fe1a4"),o=o(),H(),o),Oa=Ee(()=>p("span",{class:"vpi-languages icon lang"},null,-1)),Ga=Ee(()=>p("span",{class:"vpi-chevron-down icon chevron"},null,-1)),Ua={class:"list"},ja=_({__name:"VPNavScreenTranslations",setup(o){const{localeLinks:e,currentLang:t}=Y({correspondingLink:!0}),s=I(!1);function n(){s.value=!s.value}return(i,u)=>r(e).length&&r(t).label?(a(),c("div",{key:0,class:w(["VPNavScreenTranslations",{open:s.value}])},[p("button",{class:"title",onClick:n},[Oa,O(" "+T(r(t).label)+" ",1),Ga]),p("ul",Ua,[(a(!0),c(M,null,E(r(e),h=>(a(),c("li",{key:h.link,class:"item"},[m(D,{class:"link",href:h.link},{default:v(()=>[O(T(h.text),1)]),_:2},1032,["href"])]))),128))])],2)):f("",!0)}}),za=b(ja,[["__scopeId","data-v-858fe1a4"]]),qa={class:"container"},Wa=_({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(o){const e=I(null),t=Se(oe?document.body:null);return(s,n)=>(a(),k(de,{name:"fade",onEnter:n[0]||(n[0]=i=>t.value=!0),onAfterLeave:n[1]||(n[1]=i=>t.value=!1)},{default:v(()=>[s.open?(a(),c("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[p("div",qa,[l(s.$slots,"nav-screen-content-before",{},void 0,!0),m(Fa,{class:"menu"}),m(za,{class:"translations"}),m(ka,{class:"appearance"}),m(Da,{class:"social-links"}),l(s.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):f("",!0)]),_:3}))}}),Ka=b(Wa,[["__scopeId","data-v-f2779853"]]),Ra={key:0,class:"VPNav"},Ja=_({__name:"VPNav",setup(o){const{isScreenOpen:e,closeScreen:t,toggleScreen:s}=wn(),{frontmatter:n}=P(),i=g(()=>n.value.navbar!==!1);return Te("close-screen",t),Z(()=>{oe&&document.documentElement.classList.toggle("hide-nav",!i.value)}),(u,h)=>i.value?(a(),c("header",Ra,[m(ha,{"is-screen-open":r(e),onToggleScreen:r(s)},{"nav-bar-title-before":v(()=>[l(u.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(u.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[l(u.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[l(u.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),m(Ka,{open:r(e)},{"nav-screen-content-before":v(()=>[l(u.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[l(u.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):f("",!0)}}),Ya=b(Ja,[["__scopeId","data-v-ae24b3ad"]]),Fe=o=>(C("data-v-b7550ba0"),o=o(),H(),o),Qa=["role","tabindex"],Xa=Fe(()=>p("div",{class:"indicator"},null,-1)),Za=Fe(()=>p("span",{class:"vpi-chevron-right caret-icon"},null,-1)),xa=[Za],er={key:1,class:"items"},tr=_({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(o){const e=o,{collapsed:t,collapsible:s,isLink:n,isActiveLink:i,hasActiveLink:u,hasChildren:h,toggle:d}=kt(g(()=>e.item)),$=g(()=>h.value?"section":"div"),V=g(()=>n.value?"a":"div"),y=g(()=>h.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),L=g(()=>n.value?void 0:"button"),N=g(()=>[[`level-${e.depth}`],{collapsible:s.value},{collapsed:t.value},{"is-link":n.value},{"is-active":i.value},{"has-active":u.value}]);function A(S){"key"in S&&S.key!=="Enter"||!e.item.link&&d()}function B(){e.item.link&&d()}return(S,j)=>{const z=R("VPSidebarItem",!0);return a(),k(F($.value),{class:w(["VPSidebarItem",N.value])},{default:v(()=>[S.item.text?(a(),c("div",q({key:0,class:"item",role:L.value},Ye(S.item.items?{click:A,keydown:A}:{},!0),{tabindex:S.item.items&&0}),[Xa,S.item.link?(a(),k(D,{key:0,tag:V.value,class:"link",href:S.item.link,rel:S.item.rel,target:S.item.target},{default:v(()=>[(a(),k(F(y.value),{class:"text",innerHTML:S.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),k(F(y.value),{key:1,class:"text",innerHTML:S.item.text},null,8,["innerHTML"])),S.item.collapsed!=null&&S.item.items&&S.item.items.length?(a(),c("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:B,onKeydown:Je(B,["enter"]),tabindex:"0"},xa,32)):f("",!0)],16,Qa)):f("",!0),S.item.items&&S.item.items.length?(a(),c("div",er,[S.depth<5?(a(!0),c(M,{key:0},E(S.item.items,J=>(a(),k(z,{key:J.text,item:J,depth:S.depth+1},null,8,["item","depth"]))),128)):f("",!0)])):f("",!0)]),_:1},8,["class"])}}}),or=b(tr,[["__scopeId","data-v-b7550ba0"]]),nr=_({__name:"VPSidebarGroup",props:{items:{}},setup(o){const e=I(!0);let t=null;return K(()=>{t=setTimeout(()=>{t=null,e.value=!1},300)}),Qe(()=>{t!=null&&(clearTimeout(t),t=null)}),(s,n)=>(a(!0),c(M,null,E(s.items,i=>(a(),c("div",{key:i.text,class:w(["group",{"no-transition":e.value}])},[m(or,{item:i,depth:0},null,8,["item"])],2))),128))}}),sr=b(nr,[["__scopeId","data-v-c40bc020"]]),De=o=>(C("data-v-319d5ca6"),o=o(),H(),o),ar=De(()=>p("div",{class:"curtain"},null,-1)),rr={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},ir=De(()=>p("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),lr=_({__name:"VPSidebar",props:{open:{type:Boolean}},setup(o){const{sidebarGroups:e,hasSidebar:t}=U(),s=o,n=I(null),i=Se(oe?document.body:null);G([s,n],()=>{var h;s.open?(i.value=!0,(h=n.value)==null||h.focus()):i.value=!1},{immediate:!0,flush:"post"});const u=I(0);return G(e,()=>{u.value+=1},{deep:!0}),(h,d)=>r(t)?(a(),c("aside",{key:0,class:w(["VPSidebar",{open:h.open}]),ref_key:"navEl",ref:n,onClick:d[0]||(d[0]=Xe(()=>{},["stop"]))},[ar,p("nav",rr,[ir,l(h.$slots,"sidebar-nav-before",{},void 0,!0),(a(),k(sr,{items:r(e),key:u.value},null,8,["items"])),l(h.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):f("",!0)}}),cr=b(lr,[["__scopeId","data-v-319d5ca6"]]),ur=_({__name:"VPSkipLink",setup(o){const e=ee(),t=I();G(()=>e.path,()=>t.value.focus());function s({target:n}){const i=document.getElementById(decodeURIComponent(n.hash).slice(1));if(i){const u=()=>{i.removeAttribute("tabindex"),i.removeEventListener("blur",u)};i.setAttribute("tabindex","-1"),i.addEventListener("blur",u),i.focus(),window.scrollTo(0,0)}}return(n,i)=>(a(),c(M,null,[p("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),p("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:s}," Skip to content ")],64))}}),dr=b(ur,[["__scopeId","data-v-0f60ec36"]]),vr=_({__name:"Layout",setup(o){const{isOpen:e,open:t,close:s}=U(),n=ee();G(()=>n.path,s),mt(e,s);const{frontmatter:i}=P(),u=Ze(),h=g(()=>!!u["home-hero-image"]);return Te("hero-image-slot-exists",h),(d,$)=>{const V=R("Content");return r(i).layout!==!1?(a(),c("div",{key:0,class:w(["Layout",r(i).pageClass])},[l(d.$slots,"layout-top",{},void 0,!0),m(dr),m(ot,{class:"backdrop",show:r(e),onClick:r(s)},null,8,["show","onClick"]),m(Ya,null,{"nav-bar-title-before":v(()=>[l(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[l(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[l(d.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":v(()=>[l(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[l(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),m(In,{open:r(e),onOpenMenu:r(t)},null,8,["open","onOpenMenu"]),m(cr,{open:r(e)},{"sidebar-nav-before":v(()=>[l(d.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":v(()=>[l(d.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),m(ln,null,{"page-top":v(()=>[l(d.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[l(d.$slots,"page-bottom",{},void 0,!0)]),"not-found":v(()=>[l(d.$slots,"not-found",{},void 0,!0)]),"home-hero-before":v(()=>[l(d.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[l(d.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(d.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(d.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(d.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(d.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[l(d.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[l(d.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[l(d.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":v(()=>[l(d.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[l(d.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[l(d.$slots,"doc-after",{},void 0,!0)]),"doc-top":v(()=>[l(d.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[l(d.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":v(()=>[l(d.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[l(d.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[l(d.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(d.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(d.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(d.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),m(pn),l(d.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),k(V,{key:1}))}}}),pr=b(vr,[["__scopeId","data-v-5d98c3a5"]]),hr={Layout:pr,enhanceApp:({app:o})=>{o.component("Badge",xe)}},_r={extends:hr,async enhanceApp(o){}};export{_r as R};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{_ as a,c as t,o as s,a2 as i}from"./chunks/framework.BaHG-QLs.js";const u=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"prev":"../guides/","next":"openid-connect.md"},"headers":[],"relativePath":"guides/getting-started.md","filePath":"guides/getting-started.md"}'),e={name:"guides/getting-started.md"},n=i(`<h1 id="quick-start" tabindex="-1">Quick Start <a class="header-anchor" href="#quick-start" aria-label="Permalink to "Quick Start""></a></h1><p>This document is a step-by-step guide for basically setting up <a href="https://core.hitchy.org/" target="_blank" rel="noreferrer">Hitchy</a> with support for authentication and authorization using this plugin.</p><h2 id="setup-project" tabindex="-1">Setup Project <a class="header-anchor" href="#setup-project" aria-label="Permalink to "Setup Project""></a></h2><p>Create a folder for your application and enter it:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">mkdir</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> my-new-app</span></span>
|
|
2
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">cd</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> my-new-app</span></span></code></pre></div><p>Initialize your application's package management so dependencies are tracked:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> init</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> -y</span></span></code></pre></div><p>Install <a href="https://www.npmjs.com/package/@hitchy/core" target="_blank" rel="noreferrer">@hitchy/core</a> and <a href="https://www.npmjs.com/package/@hitchy/plugin-auth" target="_blank" rel="noreferrer">@hitchy/plugin-auth</a> as dependencies:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> install</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> @hitchy/core</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> @hitchy/plugin-auth</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>Plugin <a href="https://www.npmjs.com/package/@hitchy/plugin-auth" target="_blank" rel="noreferrer">@hitchy/plugin-auth</a> is implicitly fetching <a href="https://www.npmjs.com/package/@hitchy/plugin-odem" target="_blank" rel="noreferrer">@hitchy/plugin-odem</a>, <a href="https://www.npmjs.com/package/@hitchy/plugin-session" target="_blank" rel="noreferrer">@hitchy/plugin-session</a> and <a href="https://www.npmjs.com/package/@hitchy/plugin-cookies" target="_blank" rel="noreferrer">@hitchy/plugin-cookies</a> as dependencies.</p></div><h2 id="start-hitchy" tabindex="-1">Start Hitchy <a class="header-anchor" href="#start-hitchy" aria-label="Permalink to "Start Hitchy""></a></h2><p>At this point Hitchy is ready. Thus, start it with:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">hitchy</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> start</span></span></code></pre></div><div class="warning custom-block"><p class="custom-block-title">Hitchy not found?</p><p>If Hitchy isn't found this might be due to issues with your Node.js and npm setup. In most cases using <strong>npx</strong> solves this issue for now:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npx</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> hitchy</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> start</span></span></code></pre></div></div><p>Keep it running while trying out next.</p><div class="tip custom-block"><p class="custom-block-title">TIP</p><p>Whenever you want to stop Hitchy press Ctrl+C to gracefully shut it down.</p></div><h2 id="try-it-out" tabindex="-1">Try It Out <a class="header-anchor" href="#try-it-out" aria-label="Permalink to "Try It Out""></a></h2><p>The plugin <a href="./../api/routing.html">injects special endpoints for managing a user's authentication</a> by default. You should be able to authenticate with a request like this:</p><div class="language-http vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">http</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">POST</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> /api/auth/login </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">HTTP</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">/</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">1.0</span></span>
|
|
3
|
+
<span class="line"><span style="--shiki-light:#22863A;--shiki-dark:#85E89D;">Content-Type</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> application/x-www-form-urlencoded</span></span>
|
|
4
|
+
<span class="line"></span>
|
|
5
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">username=admin&password=nimda</span></span></code></pre></div><p>This works due to the default <a href="./../api/config.html#config-auth-admin">configuration</a> for implicitly <a href="/api/service/auth-manager.html#createadminifmissing">setting up administrator account</a> on application start.</p>`,20),h=[n];function p(l,r,o,c,d,k){return s(),t("div",null,h)}const y=a(e,[["render",p]]);export{u as __pageData,y as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as a,c as t,o as s,a2 as i}from"./chunks/framework.BaHG-QLs.js";const u=JSON.parse('{"title":"Quick Start","description":"","frontmatter":{"prev":"../guides/","next":"openid-connect.md"},"headers":[],"relativePath":"guides/getting-started.md","filePath":"guides/getting-started.md"}'),e={name:"guides/getting-started.md"},n=i("",20),h=[n];function p(l,r,o,c,d,k){return s(),t("div",null,h)}const y=a(e,[["render",p]]);export{u as __pageData,y as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,c as t,o as i,a2 as a}from"./chunks/framework.BaHG-QLs.js";const m=JSON.parse('{"title":"Guides","description":"","frontmatter":{"prev":"../introduction.md","next":false},"headers":[],"relativePath":"guides/index.md","filePath":"guides/index.md"}'),s={name:"guides/index.md"},n=a('<h1 id="guides" tabindex="-1">Guides <a class="header-anchor" href="#guides" aria-label="Permalink to "Guides""></a></h1><p>This section provides simple tutorials explaining how to work with this plugin:</p><ul><li><a href="./getting-started.html">Getting started</a></li><li><a href="./openid-connect.html">OpenID Connect</a></li><li><a href="./saml.html">SAML 2.0</a></li></ul>',3),o=[n];function d(r,l,_,c,h,p){return i(),t("div",null,o)}const f=e(s,[["render",d]]);export{m as __pageData,f as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,c as t,o as i,a2 as a}from"./chunks/framework.BaHG-QLs.js";const m=JSON.parse('{"title":"Guides","description":"","frontmatter":{"prev":"../introduction.md","next":false},"headers":[],"relativePath":"guides/index.md","filePath":"guides/index.md"}'),s={name:"guides/index.md"},n=a("",3),o=[n];function d(r,l,_,c,h,p){return i(),t("div",null,o)}const f=e(s,[["render",d]]);export{m as __pageData,f as default};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import{_ as s}from"./chunks/idp-login.B596H5Zv.js";import{_ as i,c as t,o as a,a2 as e}from"./chunks/framework.BaHG-QLs.js";const y=JSON.parse('{"title":"OpenID Connect","description":"","frontmatter":{"prev":"getting-started.md","next":"saml.md"},"headers":[],"relativePath":"guides/openid-connect.md","filePath":"guides/openid-connect.md"}'),n={name:"guides/openid-connect.md"},l=e(`<h1 id="openid-connect" tabindex="-1">OpenID Connect <a class="header-anchor" href="#openid-connect" aria-label="Permalink to "OpenID Connect""></a></h1><p><a href="https://en.wikipedia.org/wiki/OpenID#OpenID_Connect_(OIDC)" target="_blank" rel="noreferrer">OpenID Connect</a> is a security protocol for authenticating in distributed applications as a centrally managed user without exposing its credentials to either application.</p><p>OpenID Connect supports different flows. This example is about <a href="https://auth0.com/docs/flows/authorization-code-flow" target="_blank" rel="noreferrer">Authorization Code Flow</a>.</p><h2 id="prerequisites" tabindex="-1">Prerequisites <a class="header-anchor" href="#prerequisites" aria-label="Permalink to "Prerequisites""></a></h2><p>This protocol depends on a remotely set up <em>identity provider</em> (IdP). This tutorial is illustrating how to enable a Hitchy-based application to support authentication against such an IdP via OpenID Connect. Setting up an IdP is beyond its scope, though.</p><p>There are plenty of solutions available for running your own IdP. There are multiple options including commercial and open-source software. <a href="https://www.keycloak.org/" target="_blank" rel="noreferrer">Keycloak</a> and <a href="https://goauthentik.io/" target="_blank" rel="noreferrer">authentik</a> are examples for the latter. See our rough <a href="https://gist.github.com/soletan/02e141993a811221ce8347c5a6129021" target="_blank" rel="noreferrer">step-by-step tutorial for setting up Keycloak on a server using a stack of Docker containers</a>. This tutorial is based on Keycloak.</p><h2 id="create-custom-strategy" tabindex="-1">Create custom strategy <a class="header-anchor" href="#create-custom-strategy" aria-label="Permalink to "Create custom strategy""></a></h2><p>This plugin relies on <a href="https://www.passportjs.org/" target="_blank" rel="noreferrer">passport.js</a> which in turn supports so called <em>strategies</em> to support different kinds of authentication services and security protocols. The strategy used in this example is included with 3rd-party package <a href="https://www.npmjs.com/package/openid-client" target="_blank" rel="noreferrer">openid-client</a> which you need to install in context of your application:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> openid-client</span></span></code></pre></div><p><strong>@hitchy/plugin-auth</strong> is picking up all <a href="./../api/config.html#config-auth-strategies">strategies configured in its runtime configuration</a> on application start. It includes a factory service offering methods for generating strategies from a set of configuration options. That's what we use here.</p><p>Open file <strong>config/auth.js</strong> of your project and add the <code>oidc</code> property to the list of strategies as illustrated in this example:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"use strict"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
|
|
2
|
+
<span class="line"></span>
|
|
3
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">() {</span></span>
|
|
4
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">service</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.runtime;</span></span>
|
|
5
|
+
<span class="line"></span>
|
|
6
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
7
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> auth: {</span></span>
|
|
8
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> strategies: {</span></span>
|
|
9
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> oidc: </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">await</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> service.AuthenticationStrategies.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">generateOpenIdConnect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"oidc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, {</span></span>
|
|
10
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Provides URL of IdP for discovering settings for this</span></span>
|
|
11
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // application's authentication realm.</span></span>
|
|
12
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> discovery_url: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
13
|
+
<span class="line"></span>
|
|
14
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Provides this application's name in context of discovered</span></span>
|
|
15
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // IdP realm. Think of it as the application's username for</span></span>
|
|
16
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // authenticating itself at the IdP.</span></span>
|
|
17
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> client_id: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"ci-openid-test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
18
|
+
<span class="line"></span>
|
|
19
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Provides IdP-specific secret used to authenticate this</span></span>
|
|
20
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // application as a valid client of IdP. Think of it as the</span></span>
|
|
21
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // application's password for authenticating itself.</span></span>
|
|
22
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> client_secret: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"some-random-secret"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
23
|
+
<span class="line"></span>
|
|
24
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Lists valid URLs user may be redirected to after logging</span></span>
|
|
25
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // in successfully at IdP.</span></span>
|
|
26
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> redirect_uris: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"http://localhost:3000/api/auth/login/oidc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
|
|
27
|
+
<span class="line"></span>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Lists metadata/attributes of authenticated user to be</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // delivered by IdP in redirect after successful login.</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> response_types: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"code"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
|
|
31
|
+
<span class="line"></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // Lists valid URLs user may be redirected to after logging</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // out from IdP.</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> post_logout_redirect_uris: [</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"http://localhost:3000/api/auth/logout"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">],</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } ),</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> };</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">Multiple strategies?</p><p>It's fine to have multiple strategies of different name being set up like that. Just make sure name of property is different, URL-safe and provided as first argument to the generator method.</p></div><p>The strategy in this example is named <code>oidc</code>. Thus, the same name is given as first argument to the generator function, too. Its second argument provides configuration options for used to set up the strategy in detail.</p><div class="warning custom-block"><p class="custom-block-title">Adapt to your IdP</p><p>The configuration in code example is for illustration, only. You need to adapt the settings to work with your IdP.</p></div><ul><li>The <strong>discovery_url</strong> is addressing your IdP's meta settings which is a computable set of configuration parameters the client will pick up and adapt to your IdP properly. In our case, which is based on Keycloak, it is <a href="https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test" target="_blank" rel="noreferrer">https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test</a>. Click the link to see all meta settings related to a realm named <code>hitchy-plugin-auth-ci-test</code>.</li><li><strong>client_id</strong> and <strong>client_secret</strong> are just like a user's name and password, but this time they are suitable for authenticating your application at your IdP.</li><li>A list of <strong>redirect_uris</strong> can be provided, but usually it consists of a single URL, only. In Authorization Code Flow, users are redirected to that URL right after successful authentication. Additional data on authenticated user is passed in query parameters there. The strategy is required to process that request, too. Thus, in case of <strong>hitchy/plugin-auth</strong>, this is usually referring to the same login endpoint used to trigger the authentication in the first place, e.g. <a href="http://localhost:3000/api/auth/login/oidc" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/oidc</a>. This support is based on some <a href="./../api/routing.html">routing defaults</a>.</li><li>Similar to that, another list of possible redirect URIs is given in <strong>post_logout_redirect_uris</strong>, this time for redirecting a user's browser after successful logout at IdP. The target is meant to finish the logout process on behalf of your application, thus you should point it to the logout URL <a href="http://localhost:3000/api/auth/logout" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/logout</a> accordingly.</li><li>A list of required meta attributes to deliver on an authenticated user can be configured, too. Requesting <code>code</code> response type is essential to Authorization Code Flow. That's referring to the user's name. You might query for additional data such as the user's mail address or similar.</li></ul><div class="tip custom-block"><p class="custom-block-title">Additional options</p><p>All but the <strong>discovery_url</strong> are metadata options supported by the underlying <strong>openid-client</strong> package, thus you should look into <a href="https://github.com/panva/node-openid-client/blob/main/docs/README.md#new-clientmetadata-jwks-options" target="_blank" rel="noreferrer">its documentation</a> for additional options available. Some of them might be failing to work because of the way the client is integrated with Hitchy.</p></div><h2 id="restart-and-test" tabindex="-1">Restart and test <a class="header-anchor" href="#restart-and-test" aria-label="Permalink to "Restart and test""></a></h2><p>Restart your hitchy instance. Open browser at <a href="http://localhot:3000/api/auth/current" target="_blank" rel="noreferrer">http://localhot:3000/api/auth/current</a> and get a response like this one:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>The request has succeeded, but no user is authenticated currently.</p><p>Trigger authentication by opening URL <a href="http://localhost:3000/api/auth/login/oidc" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/oidc</a> next. Last segment in this URL is referring to the strategy name you've picked when integrating it with the list of configured strategies.</p><p>This will redirect the browser to your IdP for prompting to log in:</p><p><img src="`+s+`" alt="screenshot of IdP login"></p><p>After logging in there, your browser is instantly redirected back to the URL <a href="http://localhost:3000/api/auth/login/oidc" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/oidc</a> as given in configuration above. This time it is succeeding with a response:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>You are authenticated!</p><p>Return to the first URL requested above. It's providing a different set of information this time:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"uuid"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"d778afae-1234-4a58-a254-b56b1f36e914"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"your-user"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"roles"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:[]}}</span></span></code></pre></div><p>Try repeating this request if you like. Unless closing your browser, you stay authenticated.</p><p>If you happen to close the browser, just return to the login URL and - based on your IdP configuration - you will be re-authenticated instantly without being prompted for entering username and password again.</p><p>Next send a request to <a href="http://localhost:3000/api/auth/logout" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/logout</a>. It gets approved:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Fetch current state of authentication at <a href="https://localhost:3000/api/auth/current" target="_blank" rel="noreferrer">https://localhost:3000/api/auth/current</a> once again.</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Try to re-authenticate instantly at <a href="http://localhost:3000/api/auth/login/oidc" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/oidc</a>. It's going to prompt for username and password at your IdP again.</p><h2 id="how-to-welcome-users" tabindex="-1">How to welcome users? <a class="header-anchor" href="#how-to-welcome-users" aria-label="Permalink to "How to welcome users?""></a></h2><p>Well, if you don't want your application's users to see the approval of logging in as raw JSON data, you simply have to replace the controller for route <code>GET /api/auth/login/oidc</code> and make it provide any other response, e.g. some redirection to a welcome page of your application.</p><p>Adjust your application's file <strong>config/routes.js</strong> accordingly:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.routes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "GET /api/auth/login/oidc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: ( </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">_</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">redirect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"/welcome"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ),</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div><p>This is possible in a custom policy <a href="https://core.hitchy.org/internals/routing-basics.html#routing-slots" target="_blank" rel="noreferrer">processed after this plugin's policy</a>, either. This could be your <strong>config/policies.js</strong> file:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.policies </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "GET /api/auth/login/oidc"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">req</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ) {</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ( req.user ) {</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">redirect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"/welcome"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> );</span></span>
|
|
45
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
46
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
|
|
47
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
48
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
49
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div>`,42),h=[l];function p(r,o,k,d,c,g){return a(),t("div",null,h)}const f=i(n,[["render",p]]);export{y as __pageData,f as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as s}from"./chunks/idp-login.B596H5Zv.js";import{_ as i,c as t,o as a,a2 as e}from"./chunks/framework.BaHG-QLs.js";const y=JSON.parse('{"title":"OpenID Connect","description":"","frontmatter":{"prev":"getting-started.md","next":"saml.md"},"headers":[],"relativePath":"guides/openid-connect.md","filePath":"guides/openid-connect.md"}'),n={name:"guides/openid-connect.md"},l=e("",42),h=[l];function p(r,o,k,d,c,g){return a(),t("div",null,h)}const f=i(n,[["render",p]]);export{y as __pageData,f as default};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import{_ as s}from"./chunks/idp-login.B596H5Zv.js";import{_ as i,c as t,o as a,a2 as e}from"./chunks/framework.BaHG-QLs.js";const n="/assets/idp-saml-cert.Dyrxdyfk.png",m=JSON.parse('{"title":"SAML 2.0","description":"","frontmatter":{"prev":"openid-connect.md","next":"../api/"},"headers":[],"relativePath":"guides/saml.md","filePath":"guides/saml.md"}'),l={name:"guides/saml.md"},h=e(`<h1 id="saml-2-0" tabindex="-1">SAML 2.0 <a class="header-anchor" href="#saml-2-0" aria-label="Permalink to "SAML 2.0""></a></h1><p><a href="https://en.wikipedia.org/wiki/SAML_2.0" target="_blank" rel="noreferrer">SAML 2.0</a> is a security protocol for authenticating centrally managed users in multiple distributed applications without exposing their credentials to either application.</p><h2 id="prerequisites" tabindex="-1">Prerequisites <a class="header-anchor" href="#prerequisites" aria-label="Permalink to "Prerequisites""></a></h2><p>This protocol depends on a remotely set up identity provider (IdP). This tutorial is illustrating how to enable a Hitchy-based application to support authentication against such an IdP via SAML 2.0. Setting up an IdP is beyond its scope, though.</p><p>There are plenty of solutions available for running your own IdP. There are multiple options including commercial and open-source software. <a href="https://www.keycloak.org/" target="_blank" rel="noreferrer">Keycloak</a> and <a href="https://goauthentik.io/" target="_blank" rel="noreferrer">authentik</a> are examples for the latter. See our rough <a href="https://gist.github.com/soletan/02e141993a811221ce8347c5a6129021" target="_blank" rel="noreferrer">step-by-step tutorial for setting up Keycloak on a server using a stack of Docker containers</a>. This tutorial is based on Keycloak.</p><h2 id="create-custom-strategy" tabindex="-1">Create custom strategy <a class="header-anchor" href="#create-custom-strategy" aria-label="Permalink to "Create custom strategy""></a></h2><p>This plugin relies on <a href="https://www.passportjs.org/" target="_blank" rel="noreferrer">passport.js</a> which in turn supports so called <em>strategies</em> to support different kinds of authentication services and security protocols. The strategy used in this example is included with 3rd-party package <a href="https://www.npmjs.com/package/passport-saml" target="_blank" rel="noreferrer">passport-saml</a> which you need to install in context of your application:</p><div class="language-bash vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">bash</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">npm</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> i</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> passport-saml</span></span></code></pre></div><p><strong>@hitchy/plugin-auth</strong> is picking up all <a href="./../api/config.html#config-auth-strategies">strategies configured in its runtime configuration</a> on application start. It includes a factory service offering methods for generating strategies from a set of configuration options. That's what we use here.</p><p>Open file <strong>config/auth.js</strong> of your project and add the <code>saml</code> property to the list of strategies as illustrated in this example:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"use strict"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">;</span></span>
|
|
2
|
+
<span class="line"></span>
|
|
3
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">module</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> =</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> async</span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> function</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">() {</span></span>
|
|
4
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> const</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> { </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">service</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> this</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.runtime;</span></span>
|
|
5
|
+
<span class="line"></span>
|
|
6
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> return</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
7
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> auth: {</span></span>
|
|
8
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> strategies: {</span></span>
|
|
9
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> saml: service.AuthenticationStrategies.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">generateSaml</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"saml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, {</span></span>
|
|
10
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // URL of IdP receiving SAML requests</span></span>
|
|
11
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> entryPoint: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"https://idp.cepharum.de/auth/realms/hitchy-plugin-auth-ci-test/protocol/saml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
12
|
+
<span class="line"></span>
|
|
13
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // name of this client as registered with the IdP</span></span>
|
|
14
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> issuer: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"ci-saml-test"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
15
|
+
<span class="line"></span>
|
|
16
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // signature algorithm to use (set to prevent insecure </span></span>
|
|
17
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // SHA-1 used by default)</span></span>
|
|
18
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> signatureAlgorithm: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"sha256"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
19
|
+
<span class="line"></span>
|
|
20
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // URL for redirecting user to after successful login</span></span>
|
|
21
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> callbackUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"http://localhost:3000/api/auth/login/saml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
22
|
+
<span class="line"></span>
|
|
23
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // URL of local application's endpoint for logging </span></span>
|
|
24
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // out to be exposed as meta of this service provider</span></span>
|
|
25
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> logoutCallbackUrl: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"http://localhost:3000/api/auth/logout"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
26
|
+
<span class="line"></span>
|
|
27
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // public certificate of IdP's realm used for signing </span></span>
|
|
28
|
+
<span class="line"><span style="--shiki-light:#6A737D;--shiki-dark:#6A737D;"> // SAML responses</span></span>
|
|
29
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> cert: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"MIICwzCCAasCfB5l4jANBg...SDuuWWgsPnlrNpCnOnM6ycT/PCDyad"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
30
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } ),</span></span>
|
|
31
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
32
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
33
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> };</span></span>
|
|
34
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">Multiple strategies?</p><p>It's fine to have multiple strategies of different name being set up like that. Just make sure name of property is different, URL-safe and provided as first argument to the generator method.</p></div><p>The strategy in this example is named <code>saml</code>. Thus, the same name is given as first argument to the generator function, too. Its second argument provides configuration options for used to set up the strategy in detail.</p><div class="warning custom-block"><p class="custom-block-title">Adapt to your IdP</p><p>The configuration in code example is for illustration, only. You need to adapt the settings to work with your IdP.</p></div><ul><li><p>The <strong>entryPoint</strong> URL <a href="https://idp.example.com/auth/realms/app-users/protocol/saml" target="_blank" rel="noreferrer">https://idp.example.com/auth/realms/app-users/protocol/saml</a> is your IdP's URL for the realm of users meant to gain access to your application. In Keycloak this is called <em>Master SAML Processing URL</em>.</p></li><li><p>The <strong>issuer</strong> <code>ci-saml-test</code> is the name of your application as registered with your IdP. In Keycloak that's the name of a registered <em>client</em>.</p></li><li><p>The <strong>signatureAlgorithm</strong> must be changed due to <code>passport-saml</code> is defaulting to the more insecure <code>sha1</code> otherwise.</p></li><li><p>The <strong>cert</strong>ificate - it's been significantly shortened in example above - is provided by your IdP for validating its signatures. It is required by <code>passport-saml</code> to validate signatures on responses provided by IdP.</p><p>In Keycloak the certificate to be used is found in your realm's settings, in tab <em>Keys</em>:</p><p><img src="`+n+'" alt="screenshot of Keycloak providing signing certificate"></p></li><li><p>The <strong>callbackUrl</strong> <a href="http://localhost:3000/api/auth/login/saml" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/saml</a> is your application's absolute URL processing login requests returning from IdP via redirecting the user's browser after successful authentication. The path should be addressing the same endpoint triggering the authentication, thus it should be very similar to the example. It works due to <a href="./../api/routing.html">routing defaults</a>.</p><p>No matter your eventual redirect URI, you have to configure it as a <em>valid redirect URI</em> at your IdP.</p></li><li><p>According to that, the <strong>logoutCallbackUrl</strong> <a href="http://localhost:3000/api/auth/logout" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/logout</a> is your application's absolute URL processing logout requests after returning from IdP via redirecting the user's browser. Just like before, the path should be addressing the same endpoint triggering the logout. Don't forget to declare it a <em>valid redirect URI</em> at your IdP, too.</p></li></ul><h2 id="restart-and-test" tabindex="-1">Restart and test <a class="header-anchor" href="#restart-and-test" aria-label="Permalink to "Restart and test""></a></h2><p>Restart your hitchy instance. Open browser at <a href="http://localhot:3000/api/auth/current" target="_blank" rel="noreferrer">http://localhot:3000/api/auth/current</a> and get a response like this one:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>The request has succeeded, but no user is authenticated currently.</p><p>Trigger authentication by opening URL <a href="http://localhost:3000/api/auth/login/saml" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/saml</a> next. Last segment in this URL is referring to the strategy name you've picked when integrating it with the list of configured strategies.</p><p>This will redirect the browser to your IdP for prompting to log in:</p><p><img src="'+s+`" alt="screenshot of IdP login"></p><p>After logging in there, your browser is instantly redirected back to the URL <a href="http://localhost:3000/api/auth/login/saml" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/saml</a> as given in configuration above. This time it is succeeding with a response:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>You are authenticated!</p><p>Return to the first URL requested above. It's providing a different set of information this time:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"uuid"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"d778afae-1234-4a58-a254-b56b1f36e914"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"name"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"your-user"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"roles"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:[]}}</span></span></code></pre></div><p>Try repeating this request if you like. Unless closing your browser, you stay authenticated.</p><p>If you happen to close the browser, just return to the login URL and - based on your IdP configuration - you will be re-authenticated instantly without being prompted for entering username and password again.</p><p>Next send a request to <a href="http://localhost:3000/api/auth/logout" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/logout</a>. It gets approved:</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Fetch current state of authentication at <a href="https://localhost:3000/api/auth/current" target="_blank" rel="noreferrer">https://localhost:3000/api/auth/current</a> once again.</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"success"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">true</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">"authenticated"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">:</span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">false</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><p>Try to re-authenticate instantly at <a href="http://localhost:3000/api/auth/login/saml" target="_blank" rel="noreferrer">http://localhost:3000/api/auth/login/saml</a>. It's going to prompt for username and password at your IdP again.</p><h2 id="how-to-welcome-users" tabindex="-1">How to welcome users? <a class="header-anchor" href="#how-to-welcome-users" aria-label="Permalink to "How to welcome users?""></a></h2><p>Well, if you don't want your application's users to see the approval of logging in as raw JSON data, you simply have to replace the controller for route <code>GET /api/auth/login/saml</code> and make it provide any other response, e.g. some redirection to a welcome page of your application.</p><p>Adjust your application's file <strong>config/routes.js</strong> accordingly:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.routes </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
35
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "GET /api/auth/login/saml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: ( </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">_</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ) </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=></span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">redirect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"/welcome"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ),</span></span>
|
|
36
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div><p>This is possible in a custom policy <a href="https://core.hitchy.org/internals/routing-basics.html#routing-slots" target="_blank" rel="noreferrer">processed after this plugin's policy</a>, either. This could be your <strong>config/policies.js</strong> file:</p><div class="language-javascript vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">javascript</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">exports</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">.policies </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">=</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
37
|
+
<span class="line"><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;"> "GET /api/auth/login/saml"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">req</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">res</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#E36209;--shiki-dark:#FFAB70;">next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ) {</span></span>
|
|
38
|
+
<span class="line"><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;"> if</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> ( req.user ) {</span></span>
|
|
39
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> res.</span><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;">redirect</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">( </span><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;">301</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">, </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"/welcome"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> );</span></span>
|
|
40
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> } </span><span style="--shiki-light:#D73A49;--shiki-dark:#F97583;">else</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> {</span></span>
|
|
41
|
+
<span class="line"><span style="--shiki-light:#6F42C1;--shiki-dark:#B392F0;"> next</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">();</span></span>
|
|
42
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> }</span></span>
|
|
43
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;"> },</span></span>
|
|
44
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">};</span></span></code></pre></div>`,40),p=[h];function r(o,k,d,g,c,u){return a(),t("div",null,p)}const f=i(l,[["render",r]]);export{m as __pageData,f as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as s}from"./chunks/idp-login.B596H5Zv.js";import{_ as i,c as t,o as a,a2 as e}from"./chunks/framework.BaHG-QLs.js";const n="/assets/idp-saml-cert.Dyrxdyfk.png",m=JSON.parse('{"title":"SAML 2.0","description":"","frontmatter":{"prev":"openid-connect.md","next":"../api/"},"headers":[],"relativePath":"guides/saml.md","filePath":"guides/saml.md"}'),l={name:"guides/saml.md"},h=e("",40),p=[h];function r(o,k,d,g,c,u){return a(),t("div",null,p)}const f=i(l,[["render",r]]);export{m as __pageData,f as default};
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as t,c as a,o as e,a2 as i}from"./chunks/framework.BaHG-QLs.js";const f=JSON.parse('{"title":"Authentication and authorization","description":"","frontmatter":{"sidebar":false},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"},o=i('<h1 id="authentication-and-authorization" tabindex="-1">Authentication and authorization <a class="header-anchor" href="#authentication-and-authorization" aria-label="Permalink to "Authentication and authorization""></a></h1><p>This plugin implements generic support for authentication and authorization and easily integrates with your <a href="https://core.hitchy.org" target="_blank" rel="noreferrer">Hitchy</a>-based application.</p><ul><li><a href="./introduction.html">Introduction</a></li><li><a href="./guides/getting-started.html">Getting Started</a></li><li><a href="./api/">API</a></li></ul>',3),r=[o];function h(c,d,s,l,_,u){return e(),a("div",null,r)}const m=t(n,[["render",h]]);export{f as __pageData,m as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as t,c as a,o as e,a2 as i}from"./chunks/framework.BaHG-QLs.js";const f=JSON.parse('{"title":"Authentication and authorization","description":"","frontmatter":{"sidebar":false},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"},o=i("",3),r=[o];function h(c,d,s,l,_,u){return e(),a("div",null,r)}const m=t(n,[["render",h]]);export{f as __pageData,m as default};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{_ as e,c as a,o as t,a2 as s}from"./chunks/framework.BaHG-QLs.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"prev":"/","next":"/guides/"},"headers":[],"relativePath":"introduction.md","filePath":"introduction.md"}'),o={name:"introduction.md"},r=s(`<h1 id="introduction" tabindex="-1">Introduction <a class="header-anchor" href="#introduction" aria-label="Permalink to "Introduction""></a></h1><p><strong>@hitchy/plugin-auth</strong> is a plugin for the server-side framework <a href="https://core.hitchy.org" target="_blank" rel="noreferrer">Hitchy</a>. It provides request handlers and policies for server-side request <a href="#authentication">authentication</a> and <a href="#authorization">authorization</a>. It processes <a href="./api/config.html#config-auth-authorizations">authorization rules</a> in <a href="https://core.hitchy.org/api/hitchy.html#configuration" target="_blank" rel="noreferrer">runtime configuration</a>. There are <a href="https://odem.hitchy.org" target="_blank" rel="noreferrer">ODM-based</a> <a href="https://core.hitchy.org/internals/components.html#models" target="_blank" rel="noreferrer">models</a> for managing <a href="./api/model/user.html">users</a>, <a href="./api/model/role.html">roles</a> and their authorizations using <a href="./api/model/authorization-rule.html">rules</a> at runtime, too.</p><h2 id="authentication" tabindex="-1">Authentication <a class="header-anchor" href="#authentication" aria-label="Permalink to "Authentication""></a></h2><p>Authentication refers to the process of assuring a user's identity.</p><p>This plugin includes request handlers for instantly supporting basic authentication against some local database managed with <a href="https://odem.hitchy.org/" target="_blank" rel="noreferrer">Odem</a>.</p><div class="tip custom-block"><p class="custom-block-title">Supported authentication strategies</p><p>This plugin integrates with <a href="http://www.passportjs.org/" target="_blank" rel="noreferrer">Passport</a> to handle authentication. Passport provides a wide array of <a href="http://www.passportjs.org/packages/" target="_blank" rel="noreferrer">strategies</a> that can handle all kinds of authentication which might work with external sources, as well. There is a built-in strategy for local authentication used by default.</p></div><h3 id="users" tabindex="-1">Users <a class="header-anchor" href="#users" aria-label="Permalink to "Users""></a></h3><p>Requests may include data for authenticating as a user selected by its name. Usually, this involves additional provision of some identification token such as a password.</p><p>Server-side sessions keep track of authentication requests once they succeeded to consider follow-up requests of same client as authenticated, too. This relies on separate plugins <a href="https://www.npmjs.com/package/@hitchy/plugin-session" target="_blank" rel="noreferrer">session</a> and <a href="https://www.npmjs.com/package/@hitchy/plugin-cookies" target="_blank" rel="noreferrer">cookies</a>.</p><h2 id="authorization" tabindex="-1">Authorization <a class="header-anchor" href="#authorization" aria-label="Permalink to "Authorization""></a></h2><p>Authorization is the process of controlling an <a href="#authentication">identified user's</a> permissions to access certain resources such as features, functions, data etc.</p><p>By intention, users have restricted access to your application. <a href="./api/model/authorization-rule.html">Authorization rules</a> associate a named resource with one or more <a href="./api/model/user.html">users</a> and/or <a href="./api/model/role.html">roles</a> for granting or revoking access on those resources.</p><h3 id="administrator-account" tabindex="-1">Administrator account <a class="header-anchor" href="#administrator-account" aria-label="Permalink to "Administrator account""></a></h3><p>On every start of application, the plugin checks if there is a <a href="./api/config.html#config-auth-admin">configurable</a> <a href="./api/model/user.html">user</a> with full access to all routes and models and creates it if missing. Unless <a href="./api/config.html#config-auth-admin">configured</a> otherwise, that user's name is <code>admin</code> and its password is <code>nimda</code>.</p><h3 id="resources" tabindex="-1">Resources <a class="header-anchor" href="#resources" aria-label="Permalink to "Resources""></a></h3><p>A resource is basically just a name, e.g. of a feature or some data entity in your application. It doesn't mean anything to this plugin on its own. In context of your application the name is meant to represent something you want to control access on.</p><p>Resource names work hierarchically by design. They consist of segments similar to a filesystem's pathname. However, segments of a resource name are separated by periods instead of slashes.</p><h3 id="roles" tabindex="-1">Roles <a class="header-anchor" href="#roles" aria-label="Permalink to "Roles""></a></h3><p>Roles are an addition to managing users. There is a model for managing roles in a database. Every role is meant to represent a group of users. They simplify access control based on authorization rules.</p><h3 id="an-example" tabindex="-1">An example <a class="header-anchor" href="#an-example" aria-label="Permalink to "An example""></a></h3><p>Let's assume your application provides a set of dedicated features and you want to control who's permitted to use what feature. Every such feature is a <em>resource</em>. For example, your application could have a <em>print</em> feature and an <em>export</em> feature. The latter could be divided into different supported export formats such as <em>PDF</em> and <em>Excel spreadsheet</em>.</p><p>On naming related resources, consider future improvements relying on resources not representing some feature, such as controlling access on individual data records of your application. Thus, you should start naming your resources with a type name such as <code>features</code>. Group features as good as possible and pick a second-level name for either group, e.g. <code>print</code> and <code>export</code>. If you've combined features in such a group, add another segment for either individual feature in that group, e.g. <code>pdf</code> and <code>excel</code> for the <code>export</code> group.</p><div class="language- vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang"></span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span>+- features</span></span>
|
|
2
|
+
<span class="line"><span> +- print</span></span>
|
|
3
|
+
<span class="line"><span> +- export</span></span>
|
|
4
|
+
<span class="line"><span> +- pdf</span></span>
|
|
5
|
+
<span class="line"><span> +- excel</span></span></code></pre></div><p>In this case, resulting resource names are <code>features.print</code>, <code>features.export.pdf</code> and <code>features.export.excel</code>.</p><p><a href="./api/model/authorization-rule.html">Authorization rules</a> are capable of granting/revoking access on either particular feature selected by its name as given above. In addition, authorization rules may address groups of features or all features altogether by using partial resource names such as <code>features</code> or <code>features.export</code>.</p><div class="language-json vp-adaptive-theme"><button title="Copy Code" class="copy"></button><span class="lang">json</span><pre class="shiki shiki-themes github-light github-dark vp-code" tabindex="0"><code><span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">{</span></span>
|
|
6
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "features"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"@platinum"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
7
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "features.export"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"@gold,@silver"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">,</span></span>
|
|
8
|
+
<span class="line"><span style="--shiki-light:#005CC5;--shiki-dark:#79B8FF;"> "features.export.pdf"</span><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">: </span><span style="--shiki-light:#032F62;--shiki-dark:#9ECBFF;">"-@silver,john.doe"</span></span>
|
|
9
|
+
<span class="line"><span style="--shiki-light:#24292E;--shiki-dark:#E1E4E8;">}</span></span></code></pre></div><div class="tip custom-block"><p class="custom-block-title">Example explained</p><p>Users with <code>platinum</code> role are granted access to all features. Users with <code>gold</code> role may access all export features. Eventually, users with <code>silver</code> role may access all export features except for the PDF export. Without regards to being assigned to either role, user <code>john.doe</code> has been granted access on PDF export feature.</p></div><p>On checking a user's authorization to access some resource, you provide that resource's qualified resource name. All rules addressing this resource or any of its superordinated resources will be checked for granting/revoking access to/from current user and/or some role this user is associated with.</p><p>Your application's code can check a user's authorization using some service. In addition, a policy generator is included with this plugin to create separate policies to be injected into your application's routing configuration.</p>`,29),i=[r];function n(c,l,h,u,p,d){return t(),a("div",null,i)}const g=e(o,[["render",n]]);export{m as __pageData,g as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,c as a,o as t,a2 as s}from"./chunks/framework.BaHG-QLs.js";const m=JSON.parse('{"title":"Introduction","description":"","frontmatter":{"prev":"/","next":"/guides/"},"headers":[],"relativePath":"introduction.md","filePath":"introduction.md"}'),o={name:"introduction.md"},r=s("",29),i=[r];function n(c,l,h,u,p,d){return t(),a("div",null,i)}const g=e(o,[["render",n]]);export{m as __pageData,g as default};
|