diff --git a/404.html b/404.html new file mode 100644 index 00000000..58f4f600 --- /dev/null +++ b/404.html @@ -0,0 +1,33 @@ + + + + + + + + + Angular Security Training + + + + +

404

There's nothing here.
Take me home
+ + + diff --git a/advanced/api/api-defense.html b/advanced/api/api-defense.html new file mode 100644 index 00000000..65d65d79 --- /dev/null +++ b/advanced/api/api-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 7.2 Unprotected API Defense | Angular Security Training + + + + +

7.2 Unprotected API Defense

  • Consistent Input Validation to validate during code reviews.
  • API Authentication gateway to secure authentications (OAuth)
  • API Management platform allow security features like:
    • Rate limiting and quota policies against service availability threats.
    • Audit changes in API management systems against insider threats.
    • Track logs of API attempts to detect spam and crawlers abuse.
  • Browsers
  • Framework & Custom implementation

Implementation

HTML5's technologies : Same-origin policy (SOP), Content Security Policy (CSP), Cross-Origin Resource Sharing (CORS) Protection based on custom implementation: Filters, Interceptors, Annotations (JSR-250, spring-security, …), Insecure Direct Objects Reference (IDOR) Hide template portions depending on user profile:

  • Mitigation (not a protection!)
  • Combined with server-side API protection
  • Client-side implementation : ngIf directive or dedicated module.

Cross-origin resource sharing (CORS)

Allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.

api-cors

Request headers:

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers

Response headers:

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

api-decision-tree

Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/api/api-overview.html b/advanced/api/api-overview.html new file mode 100644 index 00000000..1ed07f98 --- /dev/null +++ b/advanced/api/api-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + 7.1 Unprotected APIs Overview | Angular Security Training + + + + +

7.1 Unprotected APIs Overview

  • OWASP Top 10+ 2017 new topic (draft version)
  • REST API architecture growing (mobile, SPA, IoT,…)
  • Restrict access to API for authenticated/authorized users/clients
  • This area impacts or is related to many others potential vulnerabilities :
    • Broken Authentication & Session Management
    • Broken Access Control
    • Sensitive data exposure,
    • Insufficient Attack Protection
    • Cross-Site Request Forgery
    • JSONP vulnerability
    • Service Availability Threats

Authorization

  • Used with strong authentication : JWT OAuth, multi-factor authentication, …
  • Web address request security: approach to secure your request URIs
  • Implement Service layer and Domain object security: separation of concerns, reusability, support for rich clients and web services
  • Define a realm: user, group, role, permission, right management …
Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/api/api-pw.html b/advanced/api/api-pw.html new file mode 100644 index 00000000..916a2390 --- /dev/null +++ b/advanced/api/api-pw.html @@ -0,0 +1,35 @@ + + + + + + + + + 7.3 Unprotected API Practical Work | Angular Security Training + + + + +

7.3 Unprotected API Practical Work

pw

Part 1

For this PW, we need to have an authentication process. So, we recommend implementing/use the JWT authentication before starting. You can get the JWT OAuth implementation from the previous PW-JWT-OAuth : git clone -b PW-JWT-OAuth TO_DO /secure-angular-training-app.git

1 - Protect your API - Add server-side protection for the following actions :

  • Unauthenticated users have access to the news API (/api/news/**)
  • Only "Admin" profile can delete a news (authenticated user with ROLE_ADMIN role)
  • Only "User" profile can add news (authenticated user with ROLE_USER role)
  • Any authenticated user can "like" news

Hints :

  • use spring security annotation org.springframework.security.access.annotation.Secured to protect your REST endpoints
  • Check existing roles in /bookstore/src/main/resources/config/liquibase/authorities.csv file
  • for a global setting, use the org.springframework.security.config.annotation.web.builders.HttpSecurity in SecurityConfiguration#configure method
  • More details on spring security api : https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-method

2 - Protect the GUI - Adapt client-side according to server-side protection:

  • Unauthenticated users have access to the news API (/api/news/**)
  • Only "Admin" profile can delete a news (authenticated user with ROLE_ADMIN role)
  • Only "User" profile can add news (authenticated user with ROLE_USER role)
  • Any authenticated user can "like" news

Hints :

  • in src/app/services/auth/principal.service.ts, declare implement functions isAdmin and isUser (check roles from authorities list of "this._identity" attribute)
  • in home.html, use ngIf directive and previous functions to hide portions of template
  • declare principal service in constructor as public in home.ts to make it accessible from home.html
  • don't forget to ng build the frontend (and 'mvn' the backend) after any modification (no live reloading for this PW)

Part 2

Configure CORS

1 - Protect your API against other domains:

CORS is effective only in case of cross-origin requests, to simulate a cross-origin request:

  • Modify app/src/services/newsService.ts, in getNews() function, update the existing request api/news
    • Use an absolute url with the port 8080
    • Explicit the Content-Type header for the request
    • Stop using a proxy (ng serve --proxy-config proxy.conf.json) if any and launch ng build command to update the dist repo (needed for the next steps)

Hint : to set a header for a request, use a Headers object and pass it as a second param of the http#get method

let headers = new Headers({'Content-Type': 'application/json'});
+return this.http.get(_url_, {headers: headers}) 
+
  • Launch a second server in a different port : mvn -Drun.arguments="--server.port=9000" and access to corresponding home page http://localhost:9000/#/home

  • What's the result ? Why ? Observe the client console.

Hint : Read about Single Origin Policy(SOP)

2 - Allow cross-origin requests

  • Configure CORS in order to allow cross-origin request from localhost only (choose the right port)

  • Observe the network traffic - Look for CORS headers for the api/news request/response

Hint :

  • To enable CORS, see bookstore/src/main/resources/config/application.yml

  • To authorize an HTTP method for an API, use HttpSecurity#authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll()

Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/index.html b/advanced/index.html new file mode 100644 index 00000000..f89fb7ba --- /dev/null +++ b/advanced/index.html @@ -0,0 +1,33 @@ + + + + + + + + + 3- Advanced Threats | Angular Security Training + + + + +

3- Advanced Threats

Additional best practices to learn further about securing angular application:

  • API security
  • Json protection
Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/xssi/xssi-angular.html b/advanced/xssi/xssi-angular.html new file mode 100644 index 00000000..d17d4f1c --- /dev/null +++ b/advanced/xssi/xssi-angular.html @@ -0,0 +1,35 @@ + + + + + + + + + 8.3 XSSI protection in Angular | Angular Security Training + + + + +

8.3 XSSI protection in Angular

Angular automatically strips the )]}',\n prefix if any Configure your server to prefix: )]}',\n before all your JSON responses.

A normal response of ['one', 'two'] will be returned as :

)]}',
+['one', 'two']
+

xssi-jax-rs

Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/xssi/xssi-defense.html b/advanced/xssi/xssi-defense.html new file mode 100644 index 00000000..e41d0584 --- /dev/null +++ b/advanced/xssi/xssi-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 8.2 XSSI Defense | Angular Security Training + + + + +

8.2 XSSI Defense

  • JSON response by POST requests only.
  • Don’t return an array or prefix your JSON response
Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/xssi/xssi-overview.html b/advanced/xssi/xssi-overview.html new file mode 100644 index 00000000..b9304057 --- /dev/null +++ b/advanced/xssi/xssi-overview.html @@ -0,0 +1,63 @@ + + + + + + + + + 8.1 XSSI Overview | Angular Security Training + + + + +

8.1 XSSI Overview

A JSON vulnerability allows third party website to turn your JSON resource URL into JSONP request under some conditions. Exposed when a GET request is made to retrieve JSON information as an array. Combined to a CSRF attack.

What is JSONP ?

JSONP means JSON with Padding or Prefix, is a technique created before the CORS which allows GET requests to bypass same-origin policy. The same-origin policy does not prevent execution of external <script> tags. The JSON is wrapped inside a function which allows the data to be loaded in a <script> element as an ordinary JavaScript.

callbackFunction({
+  payload: {
+    username: 'ben',
+    session_id: '123'
+  }
+})
+
+
<script>
+  function callbackFunction(userData) {
+      console.log(userData.payload.username);
+   }
+</script>
+<script src="http://api.example.com/userdata.jsonp?callback=callbackFunction"></script>
+

How XSSI works ?

  1. Cross-domain HTTP requests is possible via the src attribute of <script> tag
  2. Your browser sends your credential automatically
  3. Returning a JSON array is valid as the source for a JavaScript <script> tag
  4. JavaScript allows us to redefine the Array constructor

Sample Exploit:

<script type="text/javascript">
+    var secrets;
+    Array = function() {
+        secrets = this;
+    };
+</script>
+
+<script src="http://victim.com/demos/secret-info.json" type="text/javascript"></script>
+
+<script type="text/javascript">
+    var yourData = '';
+    var i = -1;
+    while(secrets[++i]) {
+        yourData += secrets[i] + ' ';
+    }
+    alert('I stole your data, I can exploit it / send it to another server : ' + yourData);
+</script>
+
Last Updated:
Contributors: Nourredine K
+ + + diff --git a/advanced/xssi/xssi-pw.html b/advanced/xssi/xssi-pw.html new file mode 100644 index 00000000..fbafd912 --- /dev/null +++ b/advanced/xssi/xssi-pw.html @@ -0,0 +1,55 @@ + + + + + + + + + 8.4 XSSI Practical Work | Angular Security Training + + + + +

8.4 XSSI Practical Work

pw

1 - Configure protection against JSONP vulnerability:

Add this config in /bookstore/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java

      /**
+       * Add config to set a prefix on json response to protect against JSONP vulnerability
+       * */
+      @Bean
+      public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
+          MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
+          /**
+           * Set the expected prefix for Angular
+           * */
+          converter.setJsonPrefix(")]}',\n");
+          
+          /**
+           * Trick to solve a lazyloading exception when you use collections in your entities
+           * See https://stackoverflow.com/a/21760361
+           * */        
+          ObjectMapper mapper = new ObjectMapper();
+          //Registering Hibernate5Module to support lazy objects
+          mapper.registerModule(new Hibernate5Module());
+          converter.setObjectMapper(mapper);
+                 
+          return converter;
+      }
+

2 - Test your protection:

check that json responses are prefixed by the expected token and that there is no repercussion on the GUI (stripped automatically by Angular)

Last Updated:
Contributors: Nourredine K
+ + + diff --git a/assets/404.c6b298bb.js b/assets/404.c6b298bb.js new file mode 100644 index 00000000..83187ef5 --- /dev/null +++ b/assets/404.c6b298bb.js @@ -0,0 +1 @@ +import{f as i,u as _,g as p,r as f,o as k,c as v,a as o,t as c,e as L,w as g,h as l,b as x}from"./app.14588456.js";const B={class:"theme-container"},N={class:"theme-default-content"},T=o("h1",null,"404",-1),V=i({setup(b){var a,s,n;const u=_(),e=p(),t=(a=e.value.notFound)!=null?a:["Not Found"],r=()=>t[Math.floor(Math.random()*t.length)],h=(s=e.value.home)!=null?s:u.value,m=(n=e.value.backToHome)!=null?n:"Back to home";return(C,M)=>{const d=f("RouterLink");return k(),v("div",B,[o("div",N,[T,o("blockquote",null,c(r()),1),L(d,{to:l(h)},{default:g(()=>[x(c(l(m)),1)]),_:1},8,["to"])])])}}});export{V as default}; diff --git a/assets/404.html.bee13de6.js b/assets/404.html.bee13de6.js new file mode 100644 index 00000000..88b1d339 --- /dev/null +++ b/assets/404.html.bee13de6.js @@ -0,0 +1 @@ +import{_ as r}from"./plugin-vue_export-helper.21dcd24c.js";const _={};function e(t,c){return null}var a=r(_,[["render",e]]);export{a as default}; diff --git a/assets/404.html.f166316b.js b/assets/404.html.f166316b.js new file mode 100644 index 00000000..fa2a247a --- /dev/null +++ b/assets/404.html.f166316b.js @@ -0,0 +1 @@ +const t={key:"v-3706649a",path:"/404.html",title:"",lang:"en-US",frontmatter:{layout:"404"},excerpt:"",headers:[],git:{},filePathRelative:null};export{t as data}; diff --git a/assets/Layout.cedff8e1.js b/assets/Layout.cedff8e1.js new file mode 100644 index 00000000..ea33e82b --- /dev/null +++ b/assets/Layout.cedff8e1.js @@ -0,0 +1 @@ +var Be=Object.defineProperty,Me=Object.defineProperties;var De=Object.getOwnPropertyDescriptors;var de=Object.getOwnPropertySymbols;var Ne=Object.prototype.hasOwnProperty,Ee=Object.prototype.propertyIsEnumerable;var ve=(l,t,e)=>t in l?Be(l,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):l[t]=e,X=(l,t)=>{for(var e in t||(t={}))Ne.call(t,e)&&ve(l,e,t[e]);if(de)for(var e of de(t))Ee.call(t,e)&&ve(l,e,t[e]);return l},Y=(l,t)=>Me(l,De(t));import{r as R,o as a,c,e as $,f as x,i as I,j as p,k as _e,h as n,F as D,l as A,m as w,a as g,t as T,n as G,p as J,q as C,w as B,s as pe,v as y,b as U,x as q,y as Ie,z as Pe,A as Re,B as Q,C as Z,D as V,E as fe,G as me,H as P,u as be,g as M,T as ge,I as O,J as Ae,K as j,L as K,M as He,N as Oe,O as ee,P as ke,Q as $e,d as ze,R as Le,S as Fe,U as W,V as te,W as We,X as Ue,Y as Ve,Z as je}from"./app.14588456.js";import{_ as Ke}from"./plugin-vue_export-helper.21dcd24c.js";const Ge={},qe={class:"theme-default-content custom"};function Xe(l,t){const e=R("Content");return a(),c("div",qe,[$(e)])}var Ye=Ke(Ge,[["render",Xe]]);const Je={key:0,class:"features"},Qe=x({setup(l){const t=I(),e=p(()=>_e(t.value.features)?t.value.features:[]);return(i,r)=>n(e).length?(a(),c("div",Je,[(a(!0),c(D,null,A(n(e),_=>(a(),c("div",{key:_.title,class:"feature"},[g("h2",null,T(_.title),1),g("p",null,T(_.details),1)]))),128))])):w("",!0)}}),Ze=["innerHTML"],et=["textContent"],tt=x({setup(l){const t=I(),e=p(()=>t.value.footer),i=p(()=>t.value.footerHtml);return(r,_)=>n(e)?(a(),c(D,{key:0},[n(i)?(a(),c("div",{key:0,class:"footer",innerHTML:n(e)},null,8,Ze)):(a(),c("div",{key:1,class:"footer",textContent:T(n(e))},null,8,et))],64)):w("",!0)}}),nt=["href","rel","target","aria-label"],at=x({inheritAttrs:!1}),E=x(Y(X({},at),{props:{item:{type:Object,required:!0}},setup(l){const t=l,e=G(),i=Re(),{item:r}=J(t),_=p(()=>q(r.value.link)),f=p(()=>Ie(r.value.link)||Pe(r.value.link)),h=p(()=>{if(!f.value){if(r.value.target)return r.value.target;if(_.value)return"_blank"}}),s=p(()=>h.value==="_blank"),o=p(()=>!_.value&&!f.value&&!s.value),u=p(()=>{if(!f.value){if(r.value.rel)return r.value.rel;if(s.value)return"noopener noreferrer"}}),d=p(()=>r.value.ariaLabel||r.value.text),v=p(()=>{const L=Object.keys(i.value.locales);return L.length?!L.some(m=>m===r.value.link):r.value.link!=="/"}),b=p(()=>v.value?e.path.startsWith(r.value.link):!1),k=p(()=>o.value?r.value.activeMatch?new RegExp(r.value.activeMatch).test(e.path):b.value:!1);return(L,m)=>{const S=R("RouterLink"),N=R("ExternalLinkIcon");return n(o)?(a(),C(S,pe({key:0,class:{"router-link-active":n(k)},to:n(r).link,"aria-label":n(d)},L.$attrs),{default:B(()=>[y(L.$slots,"before"),U(" "+T(n(r).text)+" ",1),y(L.$slots,"after")]),_:3},16,["class","to","aria-label"])):(a(),c("a",pe({key:1,class:"external-link",href:n(r).link,rel:n(u),target:n(h),"aria-label":n(d)},L.$attrs),[y(L.$slots,"before"),U(" "+T(n(r).text)+" ",1),n(s)?(a(),C(N,{key:0})):w("",!0),y(L.$slots,"after")],16,nt))}}})),st={class:"hero"},rt={key:0,id:"main-title"},ot={key:1,class:"description"},lt={key:2,class:"actions"},ut=x({setup(l){const t=I(),e=Q(),i=Z(),r=p(()=>i.value&&t.value.heroImageDark!==void 0?t.value.heroImageDark:t.value.heroImage),_=p(()=>t.value.heroText===null?null:t.value.heroText||e.value.title||"Hello"),f=p(()=>t.value.heroAlt||_.value||"hero"),h=p(()=>t.value.tagline===null?null:t.value.tagline||e.value.description||"Welcome to your VuePress site"),s=p(()=>_e(t.value.actions)?t.value.actions.map(({text:u,link:d,type:v="primary"})=>({text:u,link:d,type:v})):[]),o=()=>{if(!r.value)return null;const u=V("img",{src:fe(r.value),alt:f.value});return t.value.heroImageDark===void 0?u:V(me,()=>u)};return(u,d)=>(a(),c("header",st,[$(o),n(_)?(a(),c("h1",rt,T(n(_)),1)):w("",!0),n(h)?(a(),c("p",ot,T(n(h)),1)):w("",!0),n(s).length?(a(),c("p",lt,[(a(!0),c(D,null,A(n(s),v=>(a(),C(E,{key:v.text,class:P(["action-button",[v.type]]),item:v},null,8,["class","item"]))),128))])):w("",!0)]))}}),it={class:"home"},ct=x({setup(l){return(t,e)=>(a(),c("main",it,[$(ut),$(Qe),$(Ye),$(tt)]))}}),dt=x({setup(l){const t=be(),e=Q(),i=M(),r=Z(),_=p(()=>i.value.home||t.value),f=p(()=>e.value.title),h=p(()=>r.value&&i.value.logoDark!==void 0?i.value.logoDark:i.value.logo),s=()=>{if(!h.value)return null;const o=V("img",{class:"logo",src:fe(h.value),alt:f.value});return i.value.logoDark===void 0?o:V(me,()=>o)};return(o,u)=>{const d=R("RouterLink");return a(),C(d,{to:n(_)},{default:B(()=>[$(s),n(f)?(a(),c("span",{key:0,class:P(["site-name",{"can-hide":n(h)}])},T(n(f)),3)):w("",!0)]),_:1},8,["to"])}}}),ye=x({setup(l){const t=i=>{i.style.height=i.scrollHeight+"px"},e=i=>{i.style.height=""};return(i,r)=>(a(),C(ge,{name:"dropdown",onEnter:t,onAfterEnter:e,onBeforeLeave:t},{default:B(()=>[y(i.$slots,"default")]),_:3}))}}),vt=["aria-label"],pt={class:"title"},ht=g("span",{class:"arrow down"},null,-1),_t=["aria-label"],ft={class:"title"},mt={class:"navbar-dropdown"},bt={class:"navbar-dropdown-subtitle"},gt={key:1},kt={class:"navbar-dropdown-subitem-wrapper"},$t=x({props:{item:{type:Object,required:!0}},setup(l){const t=l,{item:e}=J(t),i=p(()=>e.value.ariaLabel||e.value.text),r=O(!1),_=G();Ae(()=>_.path,()=>{r.value=!1});const f=s=>{s.detail===0?r.value=!r.value:r.value=!1},h=(s,o)=>o[o.length-1]===s;return(s,o)=>(a(),c("div",{class:P(["navbar-dropdown-wrapper",{open:r.value}])},[g("button",{class:"navbar-dropdown-title",type:"button","aria-label":n(i),onClick:f},[g("span",pt,T(n(e).text),1),ht],8,vt),g("button",{class:"navbar-dropdown-title-mobile",type:"button","aria-label":n(i),onClick:o[0]||(o[0]=u=>r.value=!r.value)},[g("span",ft,T(n(e).text),1),g("span",{class:P(["arrow",r.value?"down":"right"])},null,2)],8,_t),$(ye,null,{default:B(()=>[j(g("ul",mt,[(a(!0),c(D,null,A(n(e).children,u=>(a(),c("li",{key:u.text,class:"navbar-dropdown-item"},[u.children?(a(),c(D,{key:0},[g("h4",bt,[u.link?(a(),C(E,{key:0,item:u,onFocusout:d=>h(u,n(e).children)&&u.children.length===0&&(r.value=!1)},null,8,["item","onFocusout"])):(a(),c("span",gt,T(u.text),1))]),g("ul",kt,[(a(!0),c(D,null,A(u.children,d=>(a(),c("li",{key:d.link,class:"navbar-dropdown-subitem"},[$(E,{item:d,onFocusout:v=>h(d,u.children)&&h(u,n(e).children)&&(r.value=!1)},null,8,["item","onFocusout"])]))),128))])],64)):(a(),C(E,{key:1,item:u,onFocusout:d=>h(u,n(e).children)&&(r.value=!1)},null,8,["item","onFocusout"]))]))),128))],512),[[K,r.value]])]),_:1})],2))}}),he=l=>decodeURI(l).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),Lt=(l,t)=>{if(t.hash===l)return!0;const e=he(t.path),i=he(l);return e===i},we=(l,t)=>l.link&&Lt(l.link,t)?!0:l.children?l.children.some(e=>we(e,t)):!1,xe=l=>!q(l)||/github\.com/.test(l)?"GitHub":/bitbucket\.org/.test(l)?"Bitbucket":/gitlab\.com/.test(l)?"GitLab":/gitee\.com/.test(l)?"Gitee":null,yt={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},wt=({docsRepo:l,editLinkPattern:t})=>{if(t)return t;const e=xe(l);return e!==null?yt[e]:null},xt=({docsRepo:l,docsBranch:t,docsDir:e,filePathRelative:i,editLinkPattern:r})=>{if(!i)return null;const _=wt({docsRepo:l,editLinkPattern:r});return _?_.replace(/:repo/,q(l)?l:`https://github.com/${l}`).replace(/:branch/,t).replace(/:path/,He(`${Oe(e)}/${i}`)):null},Ct={key:0,class:"navbar-items"},Ce=x({setup(l){const t=()=>{const o=ee(),u=be(),d=Q(),v=M();return p(()=>{var S,N;const b=Object.keys(d.value.locales);if(b.length<2)return[];const k=o.currentRoute.value.path,L=o.currentRoute.value.fullPath;return[{text:(S=v.value.selectLanguageText)!=null?S:"unknown language",ariaLabel:(N=v.value.selectLanguageAriaLabel)!=null?N:"unkown language",children:b.map(H=>{var se,re,oe,le,ue,ie;const z=(re=(se=d.value.locales)==null?void 0:se[H])!=null?re:{},ne=(le=(oe=v.value.locales)==null?void 0:oe[H])!=null?le:{},ae=`${z.lang}`,Te=(ue=ne.selectLanguageName)!=null?ue:ae;let F;if(ae===d.value.lang)F=L;else{const ce=k.replace(u.value,H);o.getRoutes().some(Se=>Se.path===ce)?F=ce:F=(ie=ne.home)!=null?ie:H}return{text:Te,link:F}})}]})},e=()=>{const o=M(),u=p(()=>o.value.repo),d=p(()=>u.value?xe(u.value):null),v=p(()=>u.value&&!q(u.value)?`https://github.com/${u.value}`:u.value),b=p(()=>v.value?o.value.repoLabel?o.value.repoLabel:d.value===null?"Source":d.value:null);return p(()=>!v.value||!b.value?[]:[{text:b.value,link:v.value}])},i=o=>ke(o)?$e(o):o.children?Y(X({},o),{children:o.children.map(i)}):o,_=(()=>{const o=M();return p(()=>(o.value.navbar||[]).map(i))})(),f=t(),h=e(),s=p(()=>[..._.value,...f.value,...h.value]);return(o,u)=>n(s).length?(a(),c("nav",Ct,[(a(!0),c(D,null,A(n(s),d=>(a(),c("div",{key:d.text,class:"navbar-item"},[d.children?(a(),C($t,{key:0,item:d},null,8,["item"])):(a(),C(E,{key:1,item:d},null,8,["item"]))]))),128))])):w("",!0)}}),Tt=["title"],St={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Bt=ze('',9),Mt=[Bt],Dt={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Nt=g("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),Et=[Nt],It=x({setup(l){const t=M(),e=Z(),i=()=>{e.value=!e.value};return(r,_)=>(a(),c("button",{class:"toggle-dark-button",title:n(t).toggleDarkMode,onClick:i},[j((a(),c("svg",St,Mt,512)),[[K,!n(e)]]),j((a(),c("svg",Dt,Et,512)),[[K,n(e)]])],8,Tt))}}),Pt=["title"],Rt=g("div",{class:"icon","aria-hidden":"true"},[g("span"),g("span"),g("span")],-1),At=[Rt],Ht=x({emits:["toggle"],setup(l){const t=M();return(e,i)=>(a(),c("div",{class:"toggle-sidebar-button",title:n(t).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:i[0]||(i[0]=r=>e.$emit("toggle"))},At,8,Pt))}}),Ot=x({emits:["toggle-sidebar"],setup(l){const t=M(),e=O(null),i=O(null),r=O(0),_=p(()=>r.value?{maxWidth:r.value+"px"}:{}),f=p(()=>t.value.darkMode);Le(()=>{const o=h(e.value,"paddingLeft")+h(e.value,"paddingRight"),u=()=>{var d;window.innerWidth<=719?r.value=0:r.value=e.value.offsetWidth-o-(((d=i.value)==null?void 0:d.offsetWidth)||0)};u(),window.addEventListener("resize",u,!1),window.addEventListener("orientationchange",u,!1)});function h(s,o){var v,b,k;const u=(k=(b=(v=s==null?void 0:s.ownerDocument)==null?void 0:v.defaultView)==null?void 0:b.getComputedStyle(s,null))==null?void 0:k[o],d=Number.parseInt(u,10);return Number.isNaN(d)?0:d}return(s,o)=>{const u=R("NavbarSearch");return a(),c("header",{ref_key:"navbar",ref:e,class:"navbar"},[$(Ht,{onToggle:o[0]||(o[0]=d=>s.$emit("toggle-sidebar"))}),g("span",{ref_key:"navbarBrand",ref:i},[$(dt)],512),g("div",{class:"navbar-items-wrapper",style:Fe(n(_))},[y(s.$slots,"before"),$(Ce,{class:"can-hide"}),y(s.$slots,"after"),n(f)?(a(),C(It,{key:0})):w("",!0),$(u)],4)],512)}}}),zt={class:"page-meta"},Ft={key:0,class:"meta-item edit-link"},Wt={key:1,class:"meta-item last-updated"},Ut={class:"meta-item-label"},Vt={class:"meta-item-info"},jt={key:2,class:"meta-item contributors"},Kt={class:"meta-item-label"},Gt={class:"meta-item-info"},qt=["title"],Xt=U(", "),Yt=x({setup(l){const t=()=>{const s=M(),o=W(),u=I();return p(()=>{var N,H,z;if(!((H=(N=u.value.editLink)!=null?N:s.value.editLink)!=null?H:!0))return null;const{repo:v,docsRepo:b=v,docsBranch:k="main",docsDir:L="",editLinkText:m}=s.value;if(!b)return null;const S=xt({docsRepo:b,docsBranch:k,docsDir:L,filePathRelative:o.value.filePathRelative,editLinkPattern:(z=u.value.editLinkPattern)!=null?z:s.value.editLinkPattern});return S?{text:m!=null?m:"Edit this page",link:S}:null})},e=()=>{const s=M(),o=W(),u=I();return p(()=>{var b,k,L,m;return!((k=(b=u.value.lastUpdated)!=null?b:s.value.lastUpdated)!=null?k:!0)||!((L=o.value.git)!=null&&L.updatedTime)?null:new Date((m=o.value.git)==null?void 0:m.updatedTime).toLocaleString()})},i=()=>{const s=M(),o=W(),u=I();return p(()=>{var v,b,k,L;return((b=(v=u.value.contributors)!=null?v:s.value.contributors)!=null?b:!0)&&(L=(k=o.value.git)==null?void 0:k.contributors)!=null?L:null})},r=M(),_=t(),f=e(),h=i();return(s,o)=>{const u=R("ClientOnly");return a(),c("footer",zt,[n(_)?(a(),c("div",Ft,[$(E,{class:"meta-item-label",item:n(_)},null,8,["item"])])):w("",!0),n(f)?(a(),c("div",Wt,[g("span",Ut,T(n(r).lastUpdatedText)+": ",1),$(u,null,{default:B(()=>[g("span",Vt,T(n(f)),1)]),_:1})])):w("",!0),n(h)&&n(h).length?(a(),c("div",jt,[g("span",Kt,T(n(r).contributorsText)+": ",1),g("span",Gt,[(a(!0),c(D,null,A(n(h),(d,v)=>(a(),c(D,{key:v},[g("span",{class:"contributor",title:`email: ${d.email}`},T(d.name),9,qt),v!==n(h).length-1?(a(),c(D,{key:0},[Xt],64)):w("",!0)],64))),128))])])):w("",!0)])}}}),Jt={key:0,class:"page-nav"},Qt={class:"inner"},Zt={key:0,class:"prev"},en={key:1,class:"next"},tn=x({setup(l){const t=s=>s===!1?null:ke(s)?$e(s):We(s)?s:!1,e=(s,o,u)=>{const d=s.findIndex(v=>v.link===o);if(d!==-1){const v=s[d+u];return v!=null&&v.link?v:null}for(const v of s)if(v.children){const b=e(v.children,o,u);if(b)return b}return null},i=I(),r=te(),_=G(),f=p(()=>{const s=t(i.value.prev);return s!==!1?s:e(r.value,_.path,-1)}),h=p(()=>{const s=t(i.value.next);return s!==!1?s:e(r.value,_.path,1)});return(s,o)=>n(f)||n(h)?(a(),c("nav",Jt,[g("p",Qt,[n(f)?(a(),c("span",Zt,[$(E,{item:n(f)},null,8,["item"])])):w("",!0),n(h)?(a(),c("span",en,[$(E,{item:n(h)},null,8,["item"])])):w("",!0)])])):w("",!0)}}),nn={class:"page"},an={class:"theme-default-content"},sn=x({setup(l){return(t,e)=>{const i=R("Content");return a(),c("main",nn,[y(t.$slots,"top"),g("div",an,[$(i)]),$(Yt),$(tn),y(t.$slots,"bottom")])}}}),rn={class:"sidebar-item-children"},on=x({props:{item:{type:Object,required:!0},depth:{type:Number,required:!1,default:0}},setup(l){const t=l,{item:e,depth:i}=J(t),r=G(),_=ee(),f=p(()=>we(e.value,r)),h=p(()=>({"sidebar-item":!0,"sidebar-heading":i.value===0,active:f.value,collapsible:e.value.collapsible})),s=O(!0),o=O(void 0);return e.value.collapsible&&(s.value=f.value,o.value=()=>{s.value=!s.value},_.afterEach(()=>{s.value=f.value})),(u,d)=>{var b;const v=R("SidebarItem",!0);return a(),c("li",null,[n(e).link?(a(),C(E,{key:0,class:P(n(h)),item:n(e)},null,8,["class","item"])):(a(),c("p",{key:1,tabindex:"0",class:P(n(h)),onClick:d[0]||(d[0]=(...k)=>o.value&&o.value(...k)),onKeydown:d[1]||(d[1]=Ue((...k)=>o.value&&o.value(...k),["enter"]))},[U(T(n(e).text)+" ",1),n(e).collapsible?(a(),c("span",{key:0,class:P(["arrow",s.value?"down":"right"])},null,2)):w("",!0)],34)),(b=n(e).children)!=null&&b.length?(a(),C(ye,{key:2},{default:B(()=>[j(g("ul",rn,[(a(!0),c(D,null,A(n(e).children,k=>(a(),C(v,{key:`${n(i)}${k.text}${k.link}`,item:k,depth:n(i)+1},null,8,["item","depth"]))),128))],512),[[K,s.value]])]),_:1})):w("",!0)])}}}),ln={key:0,class:"sidebar-items"},un=x({setup(l){const t=te();return(e,i)=>n(t).length?(a(),c("ul",ln,[(a(!0),c(D,null,A(n(t),r=>(a(),C(on,{key:r.link||r.text,item:r},null,8,["item"]))),128))])):w("",!0)}}),cn={class:"sidebar"},dn=x({setup(l){return(t,e)=>(a(),c("aside",cn,[$(Ce),y(t.$slots,"top"),$(un),y(t.$slots,"bottom")]))}}),_n=x({setup(l){const t=W(),e=I(),i=M(),r=p(()=>e.value.navbar!==!1&&i.value.navbar!==!1),_=te(),f=O(!1),h=m=>{f.value=typeof m=="boolean"?m:!f.value},s={x:0,y:0},o=m=>{s.x=m.changedTouches[0].clientX,s.y=m.changedTouches[0].clientY},u=m=>{const S=m.changedTouches[0].clientX-s.x,N=m.changedTouches[0].clientY-s.y;Math.abs(S)>Math.abs(N)&&Math.abs(S)>40&&(S>0&&s.x<=80?h(!0):h(!1))},d=p(()=>[{"no-navbar":!r.value,"no-sidebar":!_.value.length,"sidebar-open":f.value},e.value.pageClass]);let v;Le(()=>{v=ee().afterEach(()=>{h(!1)})}),Ve(()=>{v()});const b=je(),k=b.resolve,L=b.pending;return(m,S)=>(a(),c("div",{class:P(["theme-container",n(d)]),onTouchstart:o,onTouchend:u},[y(m.$slots,"navbar",{},()=>[n(r)?(a(),C(Ot,{key:0,onToggleSidebar:h},{before:B(()=>[y(m.$slots,"navbar-before")]),after:B(()=>[y(m.$slots,"navbar-after")]),_:3})):w("",!0)]),g("div",{class:"sidebar-mask",onClick:S[0]||(S[0]=N=>h(!1))}),y(m.$slots,"sidebar",{},()=>[$(dn,null,{top:B(()=>[y(m.$slots,"sidebar-top")]),bottom:B(()=>[y(m.$slots,"sidebar-bottom")]),_:3})]),y(m.$slots,"page",{},()=>[n(e).home?(a(),C(ct,{key:0})):(a(),C(ge,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:n(k),onBeforeLeave:n(L)},{default:B(()=>[(a(),C(sn,{key:n(t).path},{top:B(()=>[y(m.$slots,"page-top")]),bottom:B(()=>[y(m.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34))}});export{_n as default}; diff --git a/assets/api-cors.c4d5d3b7.png b/assets/api-cors.c4d5d3b7.png new file mode 100644 index 00000000..9ae5141f Binary files /dev/null and b/assets/api-cors.c4d5d3b7.png differ diff --git a/assets/api-decision-tree.cf47078a.png b/assets/api-decision-tree.cf47078a.png new file mode 100644 index 00000000..1f21e411 Binary files /dev/null and b/assets/api-decision-tree.cf47078a.png differ diff --git a/assets/api-defense.html.1cd6a815.js b/assets/api-defense.html.1cd6a815.js new file mode 100644 index 00000000..c5933e94 --- /dev/null +++ b/assets/api-defense.html.1cd6a815.js @@ -0,0 +1 @@ +const e={key:"v-5c6f2e35",path:"/advanced/api/api-defense.html",title:"7.2 Unprotected API Defense",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Implementation",slug:"implementation",children:[]},{level:2,title:"Cross-origin resource sharing (CORS)",slug:"cross-origin-resource-sharing-cors",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"advanced/api/api-defense.md"};export{e as data}; diff --git a/assets/api-defense.html.24b7de7a.js b/assets/api-defense.html.24b7de7a.js new file mode 100644 index 00000000..2716d6e5 --- /dev/null +++ b/assets/api-defense.html.24b7de7a.js @@ -0,0 +1 @@ +import{d as e}from"./app.14588456.js";import{_ as i}from"./plugin-vue_export-helper.21dcd24c.js";var t="/angular-security-training/assets/api-cors.c4d5d3b7.png",r="/angular-security-training/assets/api-decision-tree.cf47078a.png";const s={},a=e('

7.2 Unprotected API Defense

Implementation

HTML5's technologies : Same-origin policy (SOP), Content Security Policy (CSP), Cross-Origin Resource Sharing (CORS) Protection based on custom implementation: Filters, Interceptors, Annotations (JSR-250, spring-security, \u2026), Insecure Direct Objects Reference (IDOR) Hide template portions depending on user profile:

Cross-origin resource sharing (CORS)

Allows restricted resources on a web page to be requested from another domain outside the domain from which the first resource was served.

api-cors

Request headers:

Response headers:

api-decision-tree

',13);function o(n,l){return a}var p=i(s,[["render",o]]);export{p as default}; diff --git a/assets/api-overview.html.0c4ffcd6.js b/assets/api-overview.html.0c4ffcd6.js new file mode 100644 index 00000000..1c8055e5 --- /dev/null +++ b/assets/api-overview.html.0c4ffcd6.js @@ -0,0 +1 @@ +import{d as e}from"./app.14588456.js";import{_ as i}from"./plugin-vue_export-helper.21dcd24c.js";const t={},r=e('

7.1 Unprotected APIs Overview

Authorization

',4);function a(o,s){return r}var c=i(t,[["render",a]]);export{c as default}; diff --git a/assets/api-overview.html.8d09847e.js b/assets/api-overview.html.8d09847e.js new file mode 100644 index 00000000..12aad8a5 --- /dev/null +++ b/assets/api-overview.html.8d09847e.js @@ -0,0 +1 @@ +const e={key:"v-59269238",path:"/advanced/api/api-overview.html",title:"7.1 Unprotected APIs Overview",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Authorization",slug:"authorization",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"advanced/api/api-overview.md"};export{e as data}; diff --git a/assets/api-pw.html.700f3ec1.js b/assets/api-pw.html.700f3ec1.js new file mode 100644 index 00000000..94ad6efa --- /dev/null +++ b/assets/api-pw.html.700f3ec1.js @@ -0,0 +1 @@ +const e={key:"v-69cf0bd4",path:"/advanced/api/api-pw.html",title:"7.3 Unprotected API Practical Work",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Part 1",slug:"part-1",children:[]},{level:2,title:"Part 2",slug:"part-2",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"advanced/api/api-pw.md"};export{e as data}; diff --git a/assets/api-pw.html.dea250a0.js b/assets/api-pw.html.dea250a0.js new file mode 100644 index 00000000..37a34009 --- /dev/null +++ b/assets/api-pw.html.dea250a0.js @@ -0,0 +1,3 @@ +import{d as e}from"./app.14588456.js";import{_ as t}from"./pw-coding.b255a857.js";import{_ as o}from"./plugin-vue_export-helper.21dcd24c.js";const n={},s=e('

7.3 Unprotected API Practical Work

pw

Part 1

For this PW, we need to have an authentication process. So, we recommend implementing/use the JWT authentication before starting. You can get the JWT OAuth implementation from the previous PW-JWT-OAuth : git clone -b PW-JWT-OAuth TO_DO /secure-angular-training-app.git

1 - Protect your API - Add server-side protection for the following actions :

Hints :

2 - Protect the GUI - Adapt client-side according to server-side protection:

Hints :

Part 2

Configure CORS

1 - Protect your API against other domains:

CORS is effective only in case of cross-origin requests, to simulate a cross-origin request:

Hint : to set a header for a request, use a Headers object and pass it as a second param of the http#get method

let headers = new Headers({'Content-Type': 'application/json'});
+return this.http.get(_url_, {headers: headers}) 
+

Hint : Read about Single Origin Policy(SOP)

2 - Allow cross-origin requests

Hint :

`,25);function a(i,r){return s}var d=o(n,[["render",a]]);export{d as default}; diff --git a/assets/app.14588456.js b/assets/app.14588456.js new file mode 100644 index 00000000..1ea88edd --- /dev/null +++ b/assets/app.14588456.js @@ -0,0 +1,8 @@ +var ll=Object.defineProperty,al=Object.defineProperties;var cl=Object.getOwnPropertyDescriptors;var Eo=Object.getOwnPropertySymbols;var ul=Object.prototype.hasOwnProperty,fl=Object.prototype.propertyIsEnumerable;var wo=(e,t,n)=>t in e?ll(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,Xe=(e,t)=>{for(var n in t||(t={}))ul.call(t,n)&&wo(e,n,t[n]);if(Eo)for(var n of Eo(t))fl.call(t,n)&&wo(e,n,t[n]);return e},An=(e,t)=>al(e,cl(t));const go={};function Hr(e,t){const n=Object.create(null),r=e.split(",");for(let o=0;o!!n[o.toLowerCase()]:o=>!!n[o]}const dl="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",hl=Hr(dl);function Ts(e){return!!e||e===""}function Kn(e){if(X(e)){const t={};for(let n=0;n{if(n){const r=n.split(pl);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function gn(e){let t="";if(he(e))t=e;else if(X(e))for(let n=0;nhe(e)?e:e==null?"":X(e)||ye(e)&&(e.toString===Ss||!te(e.toString))?JSON.stringify(e,xs,2):String(e),xs=(e,t)=>t&&t.__v_isRef?xs(e,t.value):jt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,o])=>(n[`${r} =>`]=o,n),{})}:Cs(t)?{[`Set(${t.size})`]:[...t.values()]}:ye(t)&&!X(t)&&!Os(t)?String(t):t,de={},Ht=[],Ue=()=>{},_l=()=>!1,bl=/^on[^a-z]/,_n=e=>bl.test(e),jr=e=>e.startsWith("onUpdate:"),Pe=Object.assign,Fr=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},yl=Object.prototype.hasOwnProperty,se=(e,t)=>yl.call(e,t),X=Array.isArray,jt=e=>Jn(e)==="[object Map]",Cs=e=>Jn(e)==="[object Set]",te=e=>typeof e=="function",he=e=>typeof e=="string",Vr=e=>typeof e=="symbol",ye=e=>e!==null&&typeof e=="object",Rs=e=>ye(e)&&te(e.then)&&te(e.catch),Ss=Object.prototype.toString,Jn=e=>Ss.call(e),El=e=>Jn(e).slice(8,-1),Os=e=>Jn(e)==="[object Object]",$r=e=>he(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,rn=Hr(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Yn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},wl=/-(\w)/g,Ye=Yn(e=>e.replace(wl,(t,n)=>n?n.toUpperCase():"")),Al=/\B([A-Z])/g,St=Yn(e=>e.replace(Al,"-$1").toLowerCase()),Xn=Yn(e=>e.charAt(0).toUpperCase()+e.slice(1)),or=Yn(e=>e?`on${Xn(e)}`:""),fn=(e,t)=>!Object.is(e,t),sr=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Ls=e=>{const t=parseFloat(e);return isNaN(t)?e:t};let Ao;const Pl=()=>Ao||(Ao=typeof globalThis!="undefined"?globalThis:typeof self!="undefined"?self:typeof window!="undefined"?window:typeof global!="undefined"?global:{});let Fe;class Tl{constructor(t=!1){this.active=!0,this.effects=[],this.cleanups=[],!t&&Fe&&(this.parent=Fe,this.index=(Fe.scopes||(Fe.scopes=[])).push(this)-1)}run(t){if(this.active)try{return Fe=this,t()}finally{Fe=this.parent}}on(){Fe=this}off(){Fe=this.parent}stop(t){if(this.active){let n,r;for(n=0,r=this.effects.length;n{const t=new Set(e);return t.w=0,t.n=0,t},Is=e=>(e.w&pt)>0,Ds=e=>(e.n&pt)>0,Sl=({deps:e})=>{if(e.length)for(let t=0;t{const{deps:t}=e;if(t.length){let n=0;for(let r=0;r{(c==="length"||c>=r)&&l.push(a)});else switch(n!==void 0&&l.push(i.get(n)),t){case"add":X(e)?$r(n)&&l.push(i.get("length")):(l.push(i.get(Pt)),jt(e)&&l.push(i.get(yr)));break;case"delete":X(e)||(l.push(i.get(Pt)),jt(e)&&l.push(i.get(yr)));break;case"set":jt(e)&&l.push(i.get(Pt));break}if(l.length===1)l[0]&&Er(l[0]);else{const a=[];for(const c of l)c&&a.push(...c);Er(zr(a))}}function Er(e,t){for(const n of X(e)?e:[...e])(n!==Je||n.allowRecurse)&&(n.scheduler?n.scheduler():n.run())}const Ll=Hr("__proto__,__v_isRef,__isVue"),Ns=new Set(Object.getOwnPropertyNames(Symbol).map(e=>Symbol[e]).filter(Vr)),Il=Ur(),Dl=Ur(!1,!0),kl=Ur(!0),To=Ml();function Ml(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const r=ie(this);for(let s=0,i=this.length;s{e[t]=function(...n){Wt();const r=ie(this)[t].apply(this,n);return qt(),r}}),e}function Ur(e=!1,t=!1){return function(r,o,s){if(o==="__v_isReactive")return!e;if(o==="__v_isReadonly")return e;if(o==="__v_isShallow")return t;if(o==="__v_raw"&&s===(e?t?Ql:$s:t?Vs:Fs).get(r))return r;const i=X(r);if(!e&&i&&se(To,o))return Reflect.get(To,o,s);const l=Reflect.get(r,o,s);return(Vr(o)?Ns.has(o):Ll(o))||(e||Me(r,"get",o),t)?l:we(l)?!i||!$r(o)?l.value:l:ye(l)?e?Kr(l):Kt(l):l}}const Nl=Hs(),Hl=Hs(!0);function Hs(e=!1){return function(n,r,o,s){let i=n[r];if(dn(i)&&we(i)&&!we(o))return!1;if(!e&&!dn(o)&&(zs(o)||(o=ie(o),i=ie(i)),!X(n)&&we(i)&&!we(o)))return i.value=o,!0;const l=X(n)&&$r(r)?Number(r)e,Qn=e=>Reflect.getPrototypeOf(e);function Pn(e,t,n=!1,r=!1){e=e.__v_raw;const o=ie(e),s=ie(t);t!==s&&!n&&Me(o,"get",t),!n&&Me(o,"get",s);const{has:i}=Qn(o),l=r?Wr:n?Yr:hn;if(i.call(o,t))return l(e.get(t));if(i.call(o,s))return l(e.get(s));e!==o&&e.get(t)}function Tn(e,t=!1){const n=this.__v_raw,r=ie(n),o=ie(e);return e!==o&&!t&&Me(r,"has",e),!t&&Me(r,"has",o),e===o?n.has(e):n.has(e)||n.has(o)}function xn(e,t=!1){return e=e.__v_raw,!t&&Me(ie(e),"iterate",Pt),Reflect.get(e,"size",e)}function xo(e){e=ie(e);const t=ie(this);return Qn(t).has.call(t,e)||(t.add(e),tt(t,"add",e,e)),this}function Co(e,t){t=ie(t);const n=ie(this),{has:r,get:o}=Qn(n);let s=r.call(n,e);s||(e=ie(e),s=r.call(n,e));const i=o.call(n,e);return n.set(e,t),s?fn(t,i)&&tt(n,"set",e,t):tt(n,"add",e,t),this}function Ro(e){const t=ie(this),{has:n,get:r}=Qn(t);let o=n.call(t,e);o||(e=ie(e),o=n.call(t,e)),r&&r.call(t,e);const s=t.delete(e);return o&&tt(t,"delete",e,void 0),s}function So(){const e=ie(this),t=e.size!==0,n=e.clear();return t&&tt(e,"clear",void 0,void 0),n}function Cn(e,t){return function(r,o){const s=this,i=s.__v_raw,l=ie(i),a=t?Wr:e?Yr:hn;return!e&&Me(l,"iterate",Pt),i.forEach((c,f)=>r.call(o,a(c),a(f),s))}}function Rn(e,t,n){return function(...r){const o=this.__v_raw,s=ie(o),i=jt(s),l=e==="entries"||e===Symbol.iterator&&i,a=e==="keys"&&i,c=o[e](...r),f=n?Wr:t?Yr:hn;return!t&&Me(s,"iterate",a?yr:Pt),{next(){const{value:p,done:d}=c.next();return d?{value:p,done:d}:{value:l?[f(p[0]),f(p[1])]:f(p),done:d}},[Symbol.iterator](){return this}}}}function ot(e){return function(...t){return e==="delete"?!1:this}}function Bl(){const e={get(s){return Pn(this,s)},get size(){return xn(this)},has:Tn,add:xo,set:Co,delete:Ro,clear:So,forEach:Cn(!1,!1)},t={get(s){return Pn(this,s,!1,!0)},get size(){return xn(this)},has:Tn,add:xo,set:Co,delete:Ro,clear:So,forEach:Cn(!1,!0)},n={get(s){return Pn(this,s,!0)},get size(){return xn(this,!0)},has(s){return Tn.call(this,s,!0)},add:ot("add"),set:ot("set"),delete:ot("delete"),clear:ot("clear"),forEach:Cn(!0,!1)},r={get(s){return Pn(this,s,!0,!0)},get size(){return xn(this,!0)},has(s){return Tn.call(this,s,!0)},add:ot("add"),set:ot("set"),delete:ot("delete"),clear:ot("clear"),forEach:Cn(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(s=>{e[s]=Rn(s,!1,!1),n[s]=Rn(s,!0,!1),t[s]=Rn(s,!1,!0),r[s]=Rn(s,!0,!0)}),[e,n,t,r]}const[Ul,Wl,ql,Kl]=Bl();function qr(e,t){const n=t?e?Kl:ql:e?Wl:Ul;return(r,o,s)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?r:Reflect.get(se(n,o)&&o in r?n:r,o,s)}const Jl={get:qr(!1,!1)},Yl={get:qr(!1,!0)},Xl={get:qr(!0,!1)},Fs=new WeakMap,Vs=new WeakMap,$s=new WeakMap,Ql=new WeakMap;function Zl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Gl(e){return e.__v_skip||!Object.isExtensible(e)?0:Zl(El(e))}function Kt(e){return dn(e)?e:Jr(e,!1,js,Jl,Fs)}function ea(e){return Jr(e,!1,zl,Yl,Vs)}function Kr(e){return Jr(e,!0,$l,Xl,$s)}function Jr(e,t,n,r,o){if(!ye(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=o.get(e);if(s)return s;const i=Gl(e);if(i===0)return e;const l=new Proxy(e,i===2?r:n);return o.set(e,l),l}function Ft(e){return dn(e)?Ft(e.__v_raw):!!(e&&e.__v_isReactive)}function dn(e){return!!(e&&e.__v_isReadonly)}function zs(e){return!!(e&&e.__v_isShallow)}function Bs(e){return Ft(e)||dn(e)}function ie(e){const t=e&&e.__v_raw;return t?ie(t):e}function Us(e){return kn(e,"__v_skip",!0),e}const hn=e=>ye(e)?Kt(e):e,Yr=e=>ye(e)?Kr(e):e;function Ws(e){ht&&Je&&(e=ie(e),Ms(e.dep||(e.dep=zr())))}function qs(e,t){e=ie(e),e.dep&&Er(e.dep)}function we(e){return!!(e&&e.__v_isRef===!0)}function Ce(e){return Js(e,!1)}function Ks(e){return Js(e,!0)}function Js(e,t){return we(e)?e:new ta(e,t)}class ta{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:ie(t),this._value=n?t:hn(t)}get value(){return Ws(this),this._value}set value(t){t=this.__v_isShallow?t:ie(t),fn(t,this._rawValue)&&(this._rawValue=t,this._value=this.__v_isShallow?t:hn(t),qs(this))}}function Tt(e){return we(e)?e.value:e}const na={get:(e,t,n)=>Tt(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return we(o)&&!we(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function Ys(e){return Ft(e)?e:new Proxy(e,na)}function zd(e){const t=X(e)?new Array(e.length):{};for(const n in e)t[n]=oa(e,n);return t}class ra{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}}function oa(e,t,n){const r=e[t];return we(r)?r:new ra(e,t,n)}class sa{constructor(t,n,r,o){this._setter=n,this.dep=void 0,this.__v_isRef=!0,this._dirty=!0,this.effect=new Br(t,()=>{this._dirty||(this._dirty=!0,qs(this))}),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=r}get value(){const t=ie(this);return Ws(t),(t._dirty||!t._cacheable)&&(t._dirty=!1,t._value=t.effect.run()),t._value}set value(t){this._setter(t)}}function ia(e,t,n=!1){let r,o;const s=te(e);return s?(r=e,o=Ue):(r=e.get,o=e.set),new sa(r,o,s||!o,n)}Promise.resolve();function mt(e,t,n,r){let o;try{o=r?e(...r):e()}catch(s){bn(s,t,n)}return o}function Ve(e,t,n,r){if(te(e)){const s=mt(e,t,n,r);return s&&Rs(s)&&s.catch(i=>{bn(i,t,n)}),s}const o=[];for(let s=0;s>>1;mn(De[r])Ze&&De.splice(t,1)}function Zs(e,t,n,r){X(e)?n.push(...e):(!t||!t.includes(e,e.allowRecurse?r+1:r))&&n.push(e),Qs()}function ua(e){Zs(e,nn,on,kt)}function fa(e){Zs(e,ct,sn,Mt)}function Gr(e,t=null){if(on.length){for(Ar=t,nn=[...new Set(on)],on.length=0,kt=0;ktmn(n)-mn(r)),Mt=0;Mte.id==null?1/0:e.id;function Gs(e){wr=!1,Mn=!0,Gr(e),De.sort((n,r)=>mn(n)-mn(r));const t=Ue;try{for(Ze=0;ZeE.trim()):p&&(o=n.map(Ls))}let l,a=r[l=or(t)]||r[l=or(Ye(t))];!a&&s&&(a=r[l=or(St(t))]),a&&Ve(a,e,6,o);const c=r[l+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ve(c,e,6,o)}}function ei(e,t,n=!1){const r=t.emitsCache,o=r.get(e);if(o!==void 0)return o;const s=e.emits;let i={},l=!1;if(!te(e)){const a=c=>{const f=ei(c,t,!0);f&&(l=!0,Pe(i,f))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!s&&!l?(r.set(e,null),null):(X(s)?s.forEach(a=>i[a]=null):Pe(i,s),r.set(e,i),i)}function eo(e,t){return!e||!_n(t)?!1:(t=t.slice(2).replace(/Once$/,""),se(e,t[0].toLowerCase()+t.slice(1))||se(e,St(t))||se(e,t))}let ke=null,ti=null;function Hn(e){const t=ke;return ke=e,ti=e&&e.type.__scopeId||null,t}function ha(e,t=ke,n){if(!t||e._n)return e;const r=(...o)=>{r._d&&Vo(-1);const s=Hn(t),i=e(...o);return Hn(s),r._d&&Vo(1),i};return r._n=!0,r._c=!0,r._d=!0,r}function ir(e){const{type:t,vnode:n,proxy:r,withProxy:o,props:s,propsOptions:[i],slots:l,attrs:a,emit:c,render:f,renderCache:p,data:d,setupState:E,ctx:h,inheritAttrs:b}=e;let v,g;const A=Hn(e);try{if(n.shapeFlag&4){const O=o||r;v=Be(f.call(O,O,p,s,E,d,h)),g=a}else{const O=t;v=Be(O.length>1?O(s,{attrs:a,slots:l,emit:c}):O(s,null)),g=t.props?a:ma(a)}}catch(O){an.length=0,bn(O,e,1),v=_e($e)}let R=v;if(g&&b!==!1){const O=Object.keys(g),{shapeFlag:j}=R;O.length&&j&7&&(i&&O.some(jr)&&(g=pa(g,i)),R=Vt(R,g))}return n.dirs&&(R.dirs=R.dirs?R.dirs.concat(n.dirs):n.dirs),n.transition&&(R.transition=n.transition),v=R,Hn(A),v}const ma=e=>{let t;for(const n in e)(n==="class"||n==="style"||_n(n))&&((t||(t={}))[n]=e[n]);return t},pa=(e,t)=>{const n={};for(const r in e)(!jr(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function va(e,t,n){const{props:r,children:o,component:s}=e,{props:i,children:l,patchFlag:a}=t,c=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return r?Oo(r,i,c):!!i;if(a&8){const f=t.dynamicProps;for(let p=0;pe.__isSuspense;function ni(e,t){t&&t.pendingBranch?X(e)?t.effects.push(...e):t.effects.push(e):fa(e)}function xt(e,t){if(Ee){let n=Ee.provides;const r=Ee.parent&&Ee.parent.provides;r===n&&(n=Ee.provides=Object.create(r)),n[e]=t}}function Ae(e,t,n=!1){const r=Ee||ke;if(r){const o=r.parent==null?r.vnode.appContext&&r.vnode.appContext.provides:r.parent.provides;if(o&&e in o)return o[e];if(arguments.length>1)return n&&te(t)?t.call(r.proxy):t}}const Lo={};function et(e,t,n){return ri(e,t,n)}function ri(e,t,{immediate:n,deep:r,flush:o,onTrack:s,onTrigger:i}=de){const l=Ee;let a,c=!1,f=!1;if(we(e)?(a=()=>e.value,c=zs(e)):Ft(e)?(a=()=>e,r=!0):X(e)?(f=!0,c=e.some(Ft),a=()=>e.map(g=>{if(we(g))return g.value;if(Ft(g))return At(g);if(te(g))return mt(g,l,2)})):te(e)?t?a=()=>mt(e,l,2):a=()=>{if(!(l&&l.isUnmounted))return p&&p(),Ve(e,l,3,[d])}:a=Ue,t&&r){const g=a;a=()=>At(g())}let p,d=g=>{p=v.onStop=()=>{mt(g,l,4)}};if(zt)return d=Ue,t?n&&Ve(t,l,3,[a(),f?[]:void 0,d]):a(),Ue;let E=f?[]:Lo;const h=()=>{if(!!v.active)if(t){const g=v.run();(r||c||(f?g.some((A,R)=>fn(A,E[R])):fn(g,E)))&&(p&&p(),Ve(t,l,3,[g,E===Lo?void 0:E,d]),E=g)}else v.run()};h.allowRecurse=!!t;let b;o==="sync"?b=h:o==="post"?b=()=>Se(h,l&&l.suspense):b=()=>{!l||l.isMounted?ua(h):h()};const v=new Br(a,b);return t?n?h():E=v.run():o==="post"?Se(v.run.bind(v),l&&l.suspense):v.run(),()=>{v.stop(),l&&l.scope&&Fr(l.scope.effects,v)}}function ba(e,t,n){const r=this.proxy,o=he(e)?e.includes(".")?oi(r,e):()=>r[e]:e.bind(r,r);let s;te(t)?s=t:(s=t.handler,n=t);const i=Ee;$t(this);const l=ri(o,s.bind(r),n);return i?$t(i):Rt(),l}function oi(e,t){const n=t.split(".");return()=>{let r=e;for(let o=0;o{At(n,t)});else if(Os(e))for(const n in e)At(e[n],t);return e}function ya(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return rt(()=>{e.isMounted=!0}),to(()=>{e.isUnmounting=!0}),e}const He=[Function,Array],Ea={name:"BaseTransition",props:{mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:He,onEnter:He,onAfterEnter:He,onEnterCancelled:He,onBeforeLeave:He,onLeave:He,onAfterLeave:He,onLeaveCancelled:He,onBeforeAppear:He,onAppear:He,onAfterAppear:He,onAppearCancelled:He},setup(e,{slots:t}){const n=xi(),r=ya();let o;return()=>{const s=t.default&&li(t.default(),!0);if(!s||!s.length)return;const i=ie(e),{mode:l}=i,a=s[0];if(r.isLeaving)return lr(a);const c=Io(a);if(!c)return lr(a);const f=Pr(c,i,r,n);Tr(c,f);const p=n.subTree,d=p&&Io(p);let E=!1;const{getTransitionKey:h}=c.type;if(h){const b=h();o===void 0?o=b:b!==o&&(o=b,E=!0)}if(d&&d.type!==$e&&(!Et(c,d)||E)){const b=Pr(d,i,r,n);if(Tr(d,b),l==="out-in")return r.isLeaving=!0,b.afterLeave=()=>{r.isLeaving=!1,n.update()},lr(a);l==="in-out"&&c.type!==$e&&(b.delayLeave=(v,g,A)=>{const R=ii(r,d);R[String(d.key)]=d,v._leaveCb=()=>{g(),v._leaveCb=void 0,delete f.delayedLeave},f.delayedLeave=A})}return a}}},si=Ea;function ii(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function Pr(e,t,n,r){const{appear:o,mode:s,persisted:i=!1,onBeforeEnter:l,onEnter:a,onAfterEnter:c,onEnterCancelled:f,onBeforeLeave:p,onLeave:d,onAfterLeave:E,onLeaveCancelled:h,onBeforeAppear:b,onAppear:v,onAfterAppear:g,onAppearCancelled:A}=t,R=String(e.key),O=ii(n,e),j=(x,w)=>{x&&Ve(x,r,9,w)},B={mode:s,persisted:i,beforeEnter(x){let w=l;if(!n.isMounted)if(o)w=b||l;else return;x._leaveCb&&x._leaveCb(!0);const J=O[R];J&&Et(e,J)&&J.el._leaveCb&&J.el._leaveCb(),j(w,[x])},enter(x){let w=a,J=c,U=f;if(!n.isMounted)if(o)w=v||a,J=g||c,U=A||f;else return;let Y=!1;const y=x._enterCb=N=>{Y||(Y=!0,N?j(U,[x]):j(J,[x]),B.delayedLeave&&B.delayedLeave(),x._enterCb=void 0)};w?(w(x,y),w.length<=1&&y()):y()},leave(x,w){const J=String(e.key);if(x._enterCb&&x._enterCb(!0),n.isUnmounting)return w();j(p,[x]);let U=!1;const Y=x._leaveCb=y=>{U||(U=!0,w(),y?j(h,[x]):j(E,[x]),x._leaveCb=void 0,O[J]===e&&delete O[J])};O[J]=e,d?(d(x,Y),d.length<=1&&Y()):Y()},clone(x){return Pr(x,t,n,r)}};return B}function lr(e){if(yn(e))return e=Vt(e),e.children=null,e}function Io(e){return yn(e)?e.children?e.children[0]:void 0:e}function Tr(e,t){e.shapeFlag&6&&e.component?Tr(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function li(e,t=!1){let n=[],r=0;for(let o=0;o1)for(let o=0;o!!e.type.__asyncLoader;function Z(e){te(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:o=200,timeout:s,suspensible:i=!0,onError:l}=e;let a=null,c,f=0;const p=()=>(f++,a=null,d()),d=()=>{let E;return a||(E=a=t().catch(h=>{if(h=h instanceof Error?h:new Error(String(h)),l)return new Promise((b,v)=>{l(h,()=>b(p()),()=>v(h),f+1)});throw h}).then(h=>E!==a&&a?a:(h&&(h.__esModule||h[Symbol.toStringTag]==="Module")&&(h=h.default),c=h,h)))};return We({name:"AsyncComponentWrapper",__asyncLoader:d,get __asyncResolved(){return c},setup(){const E=Ee;if(c)return()=>ar(c,E);const h=A=>{a=null,bn(A,E,13,!r)};if(i&&E.suspense||zt)return d().then(A=>()=>ar(A,E)).catch(A=>(h(A),()=>r?_e(r,{error:A}):null));const b=Ce(!1),v=Ce(),g=Ce(!!o);return o&&setTimeout(()=>{g.value=!1},o),s!=null&&setTimeout(()=>{if(!b.value&&!v.value){const A=new Error(`Async component timed out after ${s}ms.`);h(A),v.value=A}},s),d().then(()=>{b.value=!0,E.parent&&yn(E.parent.vnode)&&Zr(E.parent.update)}).catch(A=>{h(A),v.value=A}),()=>{if(b.value&&c)return ar(c,E);if(v.value&&r)return _e(r,{error:v.value});if(n&&!g.value)return _e(n)}}})}function ar(e,{vnode:{ref:t,props:n,children:r}}){const o=_e(e,n,r);return o.ref=t,o}const yn=e=>e.type.__isKeepAlive;function wa(e,t){ai(e,"a",t)}function Aa(e,t){ai(e,"da",t)}function ai(e,t,n=Ee){const r=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(Zn(t,r,n),n){let o=n.parent;for(;o&&o.parent;)yn(o.parent.vnode)&&Pa(r,t,n,o),o=o.parent}}function Pa(e,t,n,r){const o=Zn(t,e,r,!0);no(()=>{Fr(r[t],o)},n)}function Zn(e,t,n=Ee,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...i)=>{if(n.isUnmounted)return;Wt(),$t(n);const l=Ve(t,n,e,i);return Rt(),qt(),l});return r?o.unshift(s):o.push(s),s}}const nt=e=>(t,n=Ee)=>(!zt||e==="sp")&&Zn(e,t,n),Ta=nt("bm"),rt=nt("m"),xa=nt("bu"),Ca=nt("u"),to=nt("bum"),no=nt("um"),Ra=nt("sp"),Sa=nt("rtg"),Oa=nt("rtc");function La(e,t=Ee){Zn("ec",e,t)}let xr=!0;function Ia(e){const t=ui(e),n=e.proxy,r=e.ctx;xr=!1,t.beforeCreate&&Do(t.beforeCreate,e,"bc");const{data:o,computed:s,methods:i,watch:l,provide:a,inject:c,created:f,beforeMount:p,mounted:d,beforeUpdate:E,updated:h,activated:b,deactivated:v,beforeDestroy:g,beforeUnmount:A,destroyed:R,unmounted:O,render:j,renderTracked:B,renderTriggered:x,errorCaptured:w,serverPrefetch:J,expose:U,inheritAttrs:Y,components:y,directives:N,filters:K}=t;if(c&&Da(c,r,null,e.appContext.config.unwrapInjectedRef),i)for(const ee in i){const re=i[ee];te(re)&&(r[ee]=re.bind(n))}if(o){const ee=o.call(n,n);ye(ee)&&(e.data=Kt(ee))}if(xr=!0,s)for(const ee in s){const re=s[ee],be=te(re)?re.bind(n,n):te(re.get)?re.get.bind(n,n):Ue,Te=!te(re)&&te(re.set)?re.set.bind(n):Ue,Le=ve({get:be,set:Te});Object.defineProperty(r,ee,{enumerable:!0,configurable:!0,get:()=>Le.value,set:Ne=>Le.value=Ne})}if(l)for(const ee in l)ci(l[ee],r,n,ee);if(a){const ee=te(a)?a.call(n):a;Reflect.ownKeys(ee).forEach(re=>{xt(re,ee[re])})}f&&Do(f,e,"c");function z(ee,re){X(re)?re.forEach(be=>ee(be.bind(n))):re&&ee(re.bind(n))}if(z(Ta,p),z(rt,d),z(xa,E),z(Ca,h),z(wa,b),z(Aa,v),z(La,w),z(Oa,B),z(Sa,x),z(to,A),z(no,O),z(Ra,J),X(U))if(U.length){const ee=e.exposed||(e.exposed={});U.forEach(re=>{Object.defineProperty(ee,re,{get:()=>n[re],set:be=>n[re]=be})})}else e.exposed||(e.exposed={});j&&e.render===Ue&&(e.render=j),Y!=null&&(e.inheritAttrs=Y),y&&(e.components=y),N&&(e.directives=N)}function Da(e,t,n=Ue,r=!1){X(e)&&(e=Cr(e));for(const o in e){const s=e[o];let i;ye(s)?"default"in s?i=Ae(s.from||o,s.default,!0):i=Ae(s.from||o):i=Ae(s),we(i)&&r?Object.defineProperty(t,o,{enumerable:!0,configurable:!0,get:()=>i.value,set:l=>i.value=l}):t[o]=i}}function Do(e,t,n){Ve(X(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function ci(e,t,n,r){const o=r.includes(".")?oi(n,r):()=>n[r];if(he(e)){const s=t[e];te(s)&&et(o,s)}else if(te(e))et(o,e.bind(n));else if(ye(e))if(X(e))e.forEach(s=>ci(s,t,n,r));else{const s=te(e.handler)?e.handler.bind(n):t[e.handler];te(s)&&et(o,s,e)}}function ui(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,l=s.get(t);let a;return l?a=l:!o.length&&!n&&!r?a=t:(a={},o.length&&o.forEach(c=>Fn(a,c,i,!0)),Fn(a,t,i)),s.set(t,a),a}function Fn(e,t,n,r=!1){const{mixins:o,extends:s}=t;s&&Fn(e,s,n,!0),o&&o.forEach(i=>Fn(e,i,n,!0));for(const i in t)if(!(r&&i==="expose")){const l=ka[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const ka={data:ko,props:_t,emits:_t,methods:_t,computed:_t,beforeCreate:xe,created:xe,beforeMount:xe,mounted:xe,beforeUpdate:xe,updated:xe,beforeDestroy:xe,beforeUnmount:xe,destroyed:xe,unmounted:xe,activated:xe,deactivated:xe,errorCaptured:xe,serverPrefetch:xe,components:_t,directives:_t,watch:Na,provide:ko,inject:Ma};function ko(e,t){return t?e?function(){return Pe(te(e)?e.call(this,this):e,te(t)?t.call(this,this):t)}:t:e}function Ma(e,t){return _t(Cr(e),Cr(t))}function Cr(e){if(X(e)){const t={};for(let n=0;n0)&&!(i&16)){if(i&8){const f=e.vnode.dynamicProps;for(let p=0;p{a=!0;const[d,E]=di(p,t,!0);Pe(i,d),E&&l.push(...E)};!n&&t.mixins.length&&t.mixins.forEach(f),e.extends&&f(e.extends),e.mixins&&e.mixins.forEach(f)}if(!s&&!a)return r.set(e,Ht),Ht;if(X(s))for(let f=0;f-1,E[1]=b<0||h-1||se(E,"default"))&&l.push(p)}}}const c=[i,l];return r.set(e,c),c}function Mo(e){return e[0]!=="$"}function No(e){const t=e&&e.toString().match(/^\s*function (\w+)/);return t?t[1]:e===null?"null":""}function Ho(e,t){return No(e)===No(t)}function jo(e,t){return X(t)?t.findIndex(n=>Ho(n,e)):te(t)&&Ho(t,e)?0:-1}const hi=e=>e[0]==="_"||e==="$stable",ro=e=>X(e)?e.map(Be):[Be(e)],Fa=(e,t,n)=>{const r=ha((...o)=>ro(t(...o)),n);return r._c=!1,r},mi=(e,t,n)=>{const r=e._ctx;for(const o in e){if(hi(o))continue;const s=e[o];if(te(s))t[o]=Fa(o,s,r);else if(s!=null){const i=ro(s);t[o]=()=>i}}},pi=(e,t)=>{const n=ro(t);e.slots.default=()=>n},Va=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=ie(t),kn(t,"_",n)):mi(t,e.slots={})}else e.slots={},t&&pi(e,t);kn(e.slots,er,1)},$a=(e,t,n)=>{const{vnode:r,slots:o}=e;let s=!0,i=de;if(r.shapeFlag&32){const l=t._;l?n&&l===1?s=!1:(Pe(o,t),!n&&l===1&&delete o._):(s=!t.$stable,mi(t,o)),i=t}else t&&(pi(e,t),i={default:1});if(s)for(const l in o)!hi(l)&&!(l in i)&&delete o[l]};function Bd(e,t){const n=ke;if(n===null)return e;const r=n.proxy,o=e.dirs||(e.dirs=[]);for(let s=0;sVn(d,t&&(X(t)?t[E]:t),n,r,o));return}if(jn(r)&&!o)return;const s=r.shapeFlag&4?io(r.component)||r.component.proxy:r.el,i=o?null:s,{i:l,r:a}=e,c=t&&t.r,f=l.refs===de?l.refs={}:l.refs,p=l.setupState;if(c!=null&&c!==a&&(he(c)?(f[c]=null,se(p,c)&&(p[c]=null)):we(c)&&(c.value=null)),te(a))mt(a,l,12,[i,f]);else{const d=he(a),E=we(a);if(d||E){const h=()=>{if(e.f){const b=d?f[a]:a.value;o?X(b)&&Fr(b,s):X(b)?b.includes(s)||b.push(s):d?f[a]=[s]:(a.value=[s],e.k&&(f[e.k]=a.value))}else d?(f[a]=i,se(p,a)&&(p[a]=i)):we(a)&&(a.value=i,e.k&&(f[e.k]=i))};i?(h.id=-1,Se(h,n)):h()}}}let st=!1;const Sn=e=>/svg/.test(e.namespaceURI)&&e.tagName!=="foreignObject",cr=e=>e.nodeType===8;function Ua(e){const{mt:t,p:n,o:{patchProp:r,nextSibling:o,parentNode:s,remove:i,insert:l,createComment:a}}=e,c=(v,g)=>{if(!g.hasChildNodes()){n(null,v,g),Nn();return}st=!1,f(g.firstChild,v,null,null,null),Nn(),st&&console.error("Hydration completed but contains mismatches.")},f=(v,g,A,R,O,j=!1)=>{const B=cr(v)&&v.data==="[",x=()=>h(v,g,A,R,O,B),{type:w,ref:J,shapeFlag:U}=g,Y=v.nodeType;g.el=v;let y=null;switch(w){case pn:Y!==3?y=x():(v.data!==g.children&&(st=!0,v.data=g.children),y=o(v));break;case $e:Y!==8||B?y=x():y=o(v);break;case ln:if(Y!==1)y=x();else{y=v;const N=!g.children.length;for(let K=0;K{j=j||!!g.dynamicChildren;const{type:B,props:x,patchFlag:w,shapeFlag:J,dirs:U}=g,Y=B==="input"&&U||B==="option";if(Y||w!==-1){if(U&&Ke(g,null,A,"created"),x)if(Y||!j||w&48)for(const N in x)(Y&&N.endsWith("value")||_n(N)&&!rn(N))&&r(v,N,null,x[N],!1,void 0,A);else x.onClick&&r(v,"onClick",null,x.onClick,!1,void 0,A);let y;if((y=x&&x.onVnodeBeforeMount)&&je(y,A,g),U&&Ke(g,null,A,"beforeMount"),((y=x&&x.onVnodeMounted)||U)&&ni(()=>{y&&je(y,A,g),U&&Ke(g,null,A,"mounted")},R),J&16&&!(x&&(x.innerHTML||x.textContent))){let N=d(v.firstChild,g,v,A,R,O,j);for(;N;){st=!0;const K=N;N=N.nextSibling,i(K)}}else J&8&&v.textContent!==g.children&&(st=!0,v.textContent=g.children)}return v.nextSibling},d=(v,g,A,R,O,j,B)=>{B=B||!!g.dynamicChildren;const x=g.children,w=x.length;for(let J=0;J{const{slotScopeIds:B}=g;B&&(O=O?O.concat(B):B);const x=s(v),w=d(o(v),g,x,A,R,O,j);return w&&cr(w)&&w.data==="]"?o(g.anchor=w):(st=!0,l(g.anchor=a("]"),x,w),w)},h=(v,g,A,R,O,j)=>{if(st=!0,g.el=null,j){const w=b(v);for(;;){const J=o(v);if(J&&J!==w)i(J);else break}}const B=o(v),x=s(v);return i(v),n(null,g,x,B,A,R,Sn(x),O),B},b=v=>{let g=0;for(;v;)if(v=o(v),v&&cr(v)&&(v.data==="["&&g++,v.data==="]")){if(g===0)return o(v);g--}return v};return[c,f]}const Se=ni;function Wa(e){return qa(e,Ua)}function qa(e,t){const n=Pl();n.__VUE__=!0;const{insert:r,remove:o,patchProp:s,createElement:i,createText:l,createComment:a,setText:c,setElementText:f,parentNode:p,nextSibling:d,setScopeId:E=Ue,cloneNode:h,insertStaticContent:b}=e,v=(u,m,_,C=null,T=null,I=null,H=!1,D=null,M=!!m.dynamicChildren)=>{if(u===m)return;u&&!Et(u,m)&&(C=V(u),Re(u,T,I,!0),u=null),m.patchFlag===-2&&(M=!1,m.dynamicChildren=null);const{type:S,ref:W,shapeFlag:$}=m;switch(S){case pn:g(u,m,_,C);break;case $e:A(u,m,_,C);break;case ln:u==null&&R(m,_,C,H);break;case Oe:N(u,m,_,C,T,I,H,D,M);break;default:$&1?B(u,m,_,C,T,I,H,D,M):$&6?K(u,m,_,C,T,I,H,D,M):($&64||$&128)&&S.process(u,m,_,C,T,I,H,D,M,ce)}W!=null&&T&&Vn(W,u&&u.ref,I,m||u,!m)},g=(u,m,_,C)=>{if(u==null)r(m.el=l(m.children),_,C);else{const T=m.el=u.el;m.children!==u.children&&c(T,m.children)}},A=(u,m,_,C)=>{u==null?r(m.el=a(m.children||""),_,C):m.el=u.el},R=(u,m,_,C)=>{[u.el,u.anchor]=b(u.children,m,_,C,u.el,u.anchor)},O=({el:u,anchor:m},_,C)=>{let T;for(;u&&u!==m;)T=d(u),r(u,_,C),u=T;r(m,_,C)},j=({el:u,anchor:m})=>{let _;for(;u&&u!==m;)_=d(u),o(u),u=_;o(m)},B=(u,m,_,C,T,I,H,D,M)=>{H=H||m.type==="svg",u==null?x(m,_,C,T,I,H,D,M):U(u,m,T,I,H,D,M)},x=(u,m,_,C,T,I,H,D)=>{let M,S;const{type:W,props:$,shapeFlag:q,transition:Q,patchFlag:oe,dirs:pe}=u;if(u.el&&h!==void 0&&oe===-1)M=u.el=h(u.el);else{if(M=u.el=i(u.type,I,$&&$.is,$),q&8?f(M,u.children):q&16&&J(u.children,M,null,C,T,I&&W!=="foreignObject",H,D),pe&&Ke(u,null,C,"created"),$){for(const me in $)me!=="value"&&!rn(me)&&s(M,me,null,$[me],I,u.children,C,T,k);"value"in $&&s(M,"value",null,$.value),(S=$.onVnodeBeforeMount)&&je(S,C,u)}w(M,u,u.scopeId,H,C)}pe&&Ke(u,null,C,"beforeMount");const ue=(!T||T&&!T.pendingBranch)&&Q&&!Q.persisted;ue&&Q.beforeEnter(M),r(M,m,_),((S=$&&$.onVnodeMounted)||ue||pe)&&Se(()=>{S&&je(S,C,u),ue&&Q.enter(M),pe&&Ke(u,null,C,"mounted")},T)},w=(u,m,_,C,T)=>{if(_&&E(u,_),C)for(let I=0;I{for(let S=M;S{const D=m.el=u.el;let{patchFlag:M,dynamicChildren:S,dirs:W}=m;M|=u.patchFlag&16;const $=u.props||de,q=m.props||de;let Q;_&&vt(_,!1),(Q=q.onVnodeBeforeUpdate)&&je(Q,_,m,u),W&&Ke(m,u,_,"beforeUpdate"),_&&vt(_,!0);const oe=T&&m.type!=="foreignObject";if(S?Y(u.dynamicChildren,S,D,_,C,oe,I):H||be(u,m,D,null,_,C,oe,I,!1),M>0){if(M&16)y(D,m,$,q,_,C,T);else if(M&2&&$.class!==q.class&&s(D,"class",null,q.class,T),M&4&&s(D,"style",$.style,q.style,T),M&8){const pe=m.dynamicProps;for(let ue=0;ue{Q&&je(Q,_,m,u),W&&Ke(m,u,_,"updated")},C)},Y=(u,m,_,C,T,I,H)=>{for(let D=0;D{if(_!==C){for(const D in C){if(rn(D))continue;const M=C[D],S=_[D];M!==S&&D!=="value"&&s(u,D,S,M,H,m.children,T,I,k)}if(_!==de)for(const D in _)!rn(D)&&!(D in C)&&s(u,D,_[D],null,H,m.children,T,I,k);"value"in C&&s(u,"value",_.value,C.value)}},N=(u,m,_,C,T,I,H,D,M)=>{const S=m.el=u?u.el:l(""),W=m.anchor=u?u.anchor:l("");let{patchFlag:$,dynamicChildren:q,slotScopeIds:Q}=m;Q&&(D=D?D.concat(Q):Q),u==null?(r(S,_,C),r(W,_,C),J(m.children,_,W,T,I,H,D,M)):$>0&&$&64&&q&&u.dynamicChildren?(Y(u.dynamicChildren,q,_,T,I,H,D),(m.key!=null||T&&m===T.subTree)&&gi(u,m,!0)):be(u,m,_,W,T,I,H,D,M)},K=(u,m,_,C,T,I,H,D,M)=>{m.slotScopeIds=D,u==null?m.shapeFlag&512?T.ctx.activate(m,_,C,H,M):le(m,_,C,T,I,H,M):z(u,m,M)},le=(u,m,_,C,T,I,H)=>{const D=u.component=sc(u,C,T);if(yn(u)&&(D.ctx.renderer=ce),ic(D),D.asyncDep){if(T&&T.registerDep(D,ee),!u.el){const M=D.subTree=_e($e);A(null,M,m,_)}return}ee(D,u,m,_,T,I,H)},z=(u,m,_)=>{const C=m.component=u.component;if(va(u,m,_))if(C.asyncDep&&!C.asyncResolved){re(C,m,_);return}else C.next=m,ca(C.update),C.update();else m.component=u.component,m.el=u.el,C.vnode=m},ee=(u,m,_,C,T,I,H)=>{const D=()=>{if(u.isMounted){let{next:W,bu:$,u:q,parent:Q,vnode:oe}=u,pe=W,ue;vt(u,!1),W?(W.el=oe.el,re(u,W,H)):W=oe,$&&sr($),(ue=W.props&&W.props.onVnodeBeforeUpdate)&&je(ue,Q,W,oe),vt(u,!0);const me=ir(u),ze=u.subTree;u.subTree=me,v(ze,me,p(ze.el),V(ze),u,T,I),W.el=me.el,pe===null&&ga(u,me.el),q&&Se(q,T),(ue=W.props&&W.props.onVnodeUpdated)&&Se(()=>je(ue,Q,W,oe),T)}else{let W;const{el:$,props:q}=m,{bm:Q,m:oe,parent:pe}=u,ue=jn(m);if(vt(u,!1),Q&&sr(Q),!ue&&(W=q&&q.onVnodeBeforeMount)&&je(W,pe,m),vt(u,!0),$&&G){const me=()=>{u.subTree=ir(u),G($,u.subTree,u,T,null)};ue?m.type.__asyncLoader().then(()=>!u.isUnmounted&&me()):me()}else{const me=u.subTree=ir(u);v(null,me,_,C,u,T,I),m.el=me.el}if(oe&&Se(oe,T),!ue&&(W=q&&q.onVnodeMounted)){const me=m;Se(()=>je(W,pe,me),T)}m.shapeFlag&256&&u.a&&Se(u.a,T),u.isMounted=!0,m=_=C=null}},M=u.effect=new Br(D,()=>Zr(u.update),u.scope),S=u.update=M.run.bind(M);S.id=u.uid,vt(u,!0),S()},re=(u,m,_)=>{m.component=u;const C=u.vnode.props;u.vnode=m,u.next=null,ja(u,m.props,C,_),$a(u,m.children,_),Wt(),Gr(void 0,u.update),qt()},be=(u,m,_,C,T,I,H,D,M=!1)=>{const S=u&&u.children,W=u?u.shapeFlag:0,$=m.children,{patchFlag:q,shapeFlag:Q}=m;if(q>0){if(q&128){Le(S,$,_,C,T,I,H,D,M);return}else if(q&256){Te(S,$,_,C,T,I,H,D,M);return}}Q&8?(W&16&&k(S,T,I),$!==S&&f(_,$)):W&16?Q&16?Le(S,$,_,C,T,I,H,D,M):k(S,T,I,!0):(W&8&&f(_,""),Q&16&&J($,_,C,T,I,H,D,M))},Te=(u,m,_,C,T,I,H,D,M)=>{u=u||Ht,m=m||Ht;const S=u.length,W=m.length,$=Math.min(S,W);let q;for(q=0;q<$;q++){const Q=m[q]=M?ut(m[q]):Be(m[q]);v(u[q],Q,_,null,T,I,H,D,M)}S>W?k(u,T,I,!0,!1,$):J(m,_,C,T,I,H,D,M,$)},Le=(u,m,_,C,T,I,H,D,M)=>{let S=0;const W=m.length;let $=u.length-1,q=W-1;for(;S<=$&&S<=q;){const Q=u[S],oe=m[S]=M?ut(m[S]):Be(m[S]);if(Et(Q,oe))v(Q,oe,_,null,T,I,H,D,M);else break;S++}for(;S<=$&&S<=q;){const Q=u[$],oe=m[q]=M?ut(m[q]):Be(m[q]);if(Et(Q,oe))v(Q,oe,_,null,T,I,H,D,M);else break;$--,q--}if(S>$){if(S<=q){const Q=q+1,oe=Qq)for(;S<=$;)Re(u[S],T,I,!0),S++;else{const Q=S,oe=S,pe=new Map;for(S=oe;S<=q;S++){const Ie=m[S]=M?ut(m[S]):Be(m[S]);Ie.key!=null&&pe.set(Ie.key,S)}let ue,me=0;const ze=q-oe+1;let Ot=!1,_o=0;const Xt=new Array(ze);for(S=0;S=ze){Re(Ie,T,I,!0);continue}let qe;if(Ie.key!=null)qe=pe.get(Ie.key);else for(ue=oe;ue<=q;ue++)if(Xt[ue-oe]===0&&Et(Ie,m[ue])){qe=ue;break}qe===void 0?Re(Ie,T,I,!0):(Xt[qe-oe]=S+1,qe>=_o?_o=qe:Ot=!0,v(Ie,m[qe],_,null,T,I,H,D,M),me++)}const bo=Ot?Ka(Xt):Ht;for(ue=bo.length-1,S=ze-1;S>=0;S--){const Ie=oe+S,qe=m[Ie],yo=Ie+1{const{el:I,type:H,transition:D,children:M,shapeFlag:S}=u;if(S&6){Ne(u.component.subTree,m,_,C);return}if(S&128){u.suspense.move(m,_,C);return}if(S&64){H.move(u,m,_,ce);return}if(H===Oe){r(I,m,_);for(let $=0;$D.enter(I),T);else{const{leave:$,delayLeave:q,afterLeave:Q}=D,oe=()=>r(I,m,_),pe=()=>{$(I,()=>{oe(),Q&&Q()})};q?q(I,oe,pe):pe()}else r(I,m,_)},Re=(u,m,_,C=!1,T=!1)=>{const{type:I,props:H,ref:D,children:M,dynamicChildren:S,shapeFlag:W,patchFlag:$,dirs:q}=u;if(D!=null&&Vn(D,null,_,u,!0),W&256){m.ctx.deactivate(u);return}const Q=W&1&&q,oe=!jn(u);let pe;if(oe&&(pe=H&&H.onVnodeBeforeUnmount)&&je(pe,m,u),W&6)F(u.component,_,C);else{if(W&128){u.suspense.unmount(_,C);return}Q&&Ke(u,null,m,"beforeUnmount"),W&64?u.type.remove(u,m,_,T,ce,C):S&&(I!==Oe||$>0&&$&64)?k(S,m,_,!1,!0):(I===Oe&&$&384||!T&&W&16)&&k(M,m,_),C&&Yt(u)}(oe&&(pe=H&&H.onVnodeUnmounted)||Q)&&Se(()=>{pe&&je(pe,m,u),Q&&Ke(u,null,m,"unmounted")},_)},Yt=u=>{const{type:m,el:_,anchor:C,transition:T}=u;if(m===Oe){P(_,C);return}if(m===ln){j(u);return}const I=()=>{o(_),T&&!T.persisted&&T.afterLeave&&T.afterLeave()};if(u.shapeFlag&1&&T&&!T.persisted){const{leave:H,delayLeave:D}=T,M=()=>H(_,I);D?D(u.el,I,M):M()}else I()},P=(u,m)=>{let _;for(;u!==m;)_=d(u),o(u),u=_;o(m)},F=(u,m,_)=>{const{bum:C,scope:T,update:I,subTree:H,um:D}=u;C&&sr(C),T.stop(),I&&(I.active=!1,Re(H,u,m,_)),D&&Se(D,m),Se(()=>{u.isUnmounted=!0},m),m&&m.pendingBranch&&!m.isUnmounted&&u.asyncDep&&!u.asyncResolved&&u.suspenseId===m.pendingId&&(m.deps--,m.deps===0&&m.resolve())},k=(u,m,_,C=!1,T=!1,I=0)=>{for(let H=I;Hu.shapeFlag&6?V(u.component.subTree):u.shapeFlag&128?u.suspense.next():d(u.anchor||u.el),ae=(u,m,_)=>{u==null?m._vnode&&Re(m._vnode,null,null,!0):v(m._vnode||null,u,m,null,null,null,_),Nn(),m._vnode=u},ce={p:v,um:Re,m:Ne,r:Yt,mt:le,mc:J,pc:be,pbc:Y,n:V,o:e};let ne,G;return t&&([ne,G]=t(ce)),{render:ae,hydrate:ne,createApp:Ba(ae,ne)}}function vt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function gi(e,t,n=!1){const r=e.children,o=t.children;if(X(r)&&X(o))for(let s=0;s>1,e[n[l]]0&&(t[r]=n[s-1]),n[s]=r)}}for(s=n.length,i=n[s-1];s-- >0;)n[s]=i,i=t[i];return n}const Ja=e=>e.__isTeleport,_i="components";function Ya(e,t){return Qa(_i,e,!0,t)||e}const Xa=Symbol();function Qa(e,t,n=!0,r=!1){const o=ke||Ee;if(o){const s=o.type;if(e===_i){const l=uc(s);if(l&&(l===t||l===Ye(t)||l===Xn(Ye(t))))return s}const i=Fo(o[e]||s[e],t)||Fo(o.appContext[e],t);return!i&&r?s:i}}function Fo(e,t){return e&&(e[t]||e[Ye(t)]||e[Xn(Ye(t))])}const Oe=Symbol(void 0),pn=Symbol(void 0),$e=Symbol(void 0),ln=Symbol(void 0),an=[];let Ct=null;function Gn(e=!1){an.push(Ct=e?null:[])}function Za(){an.pop(),Ct=an[an.length-1]||null}let $n=1;function Vo(e){$n+=e}function bi(e){return e.dynamicChildren=$n>0?Ct||Ht:null,Za(),$n>0&&Ct&&Ct.push(e),e}function yi(e,t,n,r,o,s){return bi(Ai(e,t,n,r,o,s,!0))}function Ei(e,t,n,r,o){return bi(_e(e,t,n,r,o,!0))}function zn(e){return e?e.__v_isVNode===!0:!1}function Et(e,t){return e.type===t.type&&e.key===t.key}const er="__vInternal",wi=({key:e})=>e!=null?e:null,Ln=({ref:e,ref_key:t,ref_for:n})=>e!=null?he(e)||we(e)||te(e)?{i:ke,r:e,k:t,f:!!n}:e:null;function Ai(e,t=null,n=null,r=0,o=null,s=e===Oe?0:1,i=!1,l=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&wi(t),ref:t&&Ln(t),scopeId:ti,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:o,dynamicChildren:null,appContext:null};return l?(so(a,n),s&128&&e.normalize(a)):n&&(a.shapeFlag|=he(n)?8:16),$n>0&&!i&&Ct&&(a.patchFlag>0||s&6)&&a.patchFlag!==32&&Ct.push(a),a}const _e=Ga;function Ga(e,t=null,n=null,r=0,o=null,s=!1){if((!e||e===Xa)&&(e=$e),zn(e)){const l=Vt(e,t,!0);return n&&so(l,n),l}if(fc(e)&&(e=e.__vccOpts),t){t=ec(t);let{class:l,style:a}=t;l&&!he(l)&&(t.class=gn(l)),ye(a)&&(Bs(a)&&!X(a)&&(a=Pe({},a)),t.style=Kn(a))}const i=he(e)?1:_a(e)?128:Ja(e)?64:ye(e)?4:te(e)?2:0;return Ai(e,t,n,r,o,i,s,!0)}function ec(e){return e?Bs(e)||er in e?Pe({},e):e:null}function Vt(e,t,n=!1){const{props:r,ref:o,patchFlag:s,children:i}=e,l=t?tc(r||{},t):r;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&wi(l),ref:t&&t.ref?n&&o?X(o)?o.concat(Ln(t)):[o,Ln(t)]:Ln(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:i,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Oe?s===-1?16:s|16:s,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Vt(e.ssContent),ssFallback:e.ssFallback&&Vt(e.ssFallback),el:e.el,anchor:e.anchor}}function oo(e=" ",t=0){return _e(pn,null,e,t)}function Ud(e,t){const n=_e(ln,null,e);return n.staticCount=t,n}function Wd(e="",t=!1){return t?(Gn(),Ei($e,null,e)):_e($e,null,e)}function Be(e){return e==null||typeof e=="boolean"?_e($e):X(e)?_e(Oe,null,e.slice()):typeof e=="object"?ut(e):_e(pn,null,String(e))}function ut(e){return e.el===null||e.memo?e:Vt(e)}function so(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(X(t))n=16;else if(typeof t=="object")if(r&65){const o=t.default;o&&(o._c&&(o._d=!1),so(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!(er in t)?t._ctx=ke:o===3&&ke&&(ke.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else te(t)?(t={default:t,_ctx:ke},n=32):(t=String(t),r&64?(n=16,t=[oo(t)]):n=8);e.children=t,e.shapeFlag|=n}function tc(...e){const t={};for(let n=0;nt(i,l,void 0,s&&s[l]));else{const i=Object.keys(e);o=new Array(i.length);for(let l=0,a=i.length;lzn(t)?!(t.type===$e||t.type===Oe&&!Ti(t.children)):!0)?e:null}const Sr=e=>e?Ci(e)?io(e)||e.proxy:Sr(e.parent):null,Bn=Pe(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Sr(e.parent),$root:e=>Sr(e.root),$emit:e=>e.emit,$options:e=>ui(e),$forceUpdate:e=>()=>Zr(e.update),$nextTick:e=>Qr.bind(e.proxy),$watch:e=>ba.bind(e)}),nc={get({_:e},t){const{ctx:n,setupState:r,data:o,props:s,accessCache:i,type:l,appContext:a}=e;let c;if(t[0]!=="$"){const E=i[t];if(E!==void 0)switch(E){case 1:return r[t];case 2:return o[t];case 4:return n[t];case 3:return s[t]}else{if(r!==de&&se(r,t))return i[t]=1,r[t];if(o!==de&&se(o,t))return i[t]=2,o[t];if((c=e.propsOptions[0])&&se(c,t))return i[t]=3,s[t];if(n!==de&&se(n,t))return i[t]=4,n[t];xr&&(i[t]=0)}}const f=Bn[t];let p,d;if(f)return t==="$attrs"&&Me(e,"get",t),f(e);if((p=l.__cssModules)&&(p=p[t]))return p;if(n!==de&&se(n,t))return i[t]=4,n[t];if(d=a.config.globalProperties,se(d,t))return d[t]},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;return o!==de&&se(o,t)?(o[t]=n,!0):r!==de&&se(r,t)?(r[t]=n,!0):se(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,propsOptions:s}},i){let l;return!!n[i]||e!==de&&se(e,i)||t!==de&&se(t,i)||(l=s[0])&&se(l,i)||se(r,i)||se(Bn,i)||se(o.config.globalProperties,i)},defineProperty(e,t,n){return n.get!=null?this.set(e,t,n.get(),null):n.value!=null&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}},rc=vi();let oc=0;function sc(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||rc,s={uid:oc++,vnode:e,type:r,parent:t,appContext:o,root:null,next:null,subTree:null,effect:null,update:null,scope:new Tl(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(o.provides),accessCache:null,renderCache:[],components:null,directives:null,propsOptions:di(r,o),emitsOptions:ei(r,o),emit:null,emitted:null,propsDefaults:de,inheritAttrs:r.inheritAttrs,ctx:de,data:de,props:de,attrs:de,slots:de,refs:de,setupState:de,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=da.bind(null,s),e.ce&&e.ce(s),s}let Ee=null;const xi=()=>Ee||ke,$t=e=>{Ee=e,e.scope.on()},Rt=()=>{Ee&&Ee.scope.off(),Ee=null};function Ci(e){return e.vnode.shapeFlag&4}let zt=!1;function ic(e,t=!1){zt=t;const{props:n,children:r}=e.vnode,o=Ci(e);Ha(e,n,o,t),Va(e,r);const s=o?lc(e,t):void 0;return zt=!1,s}function lc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=Us(new Proxy(e.ctx,nc));const{setup:r}=n;if(r){const o=e.setupContext=r.length>1?cc(e):null;$t(e),Wt();const s=mt(r,e,0,[e.props,o]);if(qt(),Rt(),Rs(s)){if(s.then(Rt,Rt),t)return s.then(i=>{$o(e,i,t)}).catch(i=>{bn(i,e,0)});e.asyncDep=s}else $o(e,s,t)}else Ri(e,t)}function $o(e,t,n){te(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ye(t)&&(e.setupState=Ys(t)),Ri(e,n)}let zo;function Ri(e,t,n){const r=e.type;if(!e.render){if(!t&&zo&&!r.render){const o=r.template;if(o){const{isCustomElement:s,compilerOptions:i}=e.appContext.config,{delimiters:l,compilerOptions:a}=r,c=Pe(Pe({isCustomElement:s,delimiters:l},i),a);r.render=zo(o,c)}}e.render=r.render||Ue}$t(e),Wt(),Ia(e),qt(),Rt()}function ac(e){return new Proxy(e.attrs,{get(t,n){return Me(e,"get","$attrs"),t[n]}})}function cc(e){const t=r=>{e.exposed=r||{}};let n;return{get attrs(){return n||(n=ac(e))},slots:e.slots,emit:e.emit,expose:t}}function io(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Ys(Us(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Bn)return Bn[n](e)}}))}function uc(e){return te(e)&&e.displayName||e.name}function fc(e){return te(e)&&"__vccOpts"in e}const ve=(e,t)=>ia(e,t,zt);function ge(e,t,n){const r=arguments.length;return r===2?ye(t)&&!X(t)?zn(t)?_e(e,null,[t]):_e(e,t):_e(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&zn(n)&&(n=[n]),_e(e,t,n))}const dc="3.2.31",hc="http://www.w3.org/2000/svg",wt=typeof document!="undefined"?document:null,Bo=wt&&wt.createElement("template"),mc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t?wt.createElementNS(hc,e):wt.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&o.setAttribute("multiple",r.multiple),o},createText:e=>wt.createTextNode(e),createComment:e=>wt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>wt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},cloneNode(e){const t=e.cloneNode(!0);return"_value"in e&&(t._value=e._value),t},insertStaticContent(e,t,n,r,o,s){const i=n?n.previousSibling:t.lastChild;if(o&&(o===s||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===s||!(o=o.nextSibling)););else{Bo.innerHTML=r?`${e}`:e;const l=Bo.content;if(r){const a=l.firstChild;for(;a.firstChild;)l.appendChild(a.firstChild);l.removeChild(a)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}};function pc(e,t,n){const r=e._vtc;r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}function vc(e,t,n){const r=e.style,o=he(n);if(n&&!o){for(const s in n)Or(r,s,n[s]);if(t&&!he(t))for(const s in t)n[s]==null&&Or(r,s,"")}else{const s=r.display;o?t!==n&&(r.cssText=n):t&&e.removeAttribute("style"),"_vod"in e&&(r.display=s)}}const Uo=/\s*!important$/;function Or(e,t,n){if(X(n))n.forEach(r=>Or(e,t,r));else if(t.startsWith("--"))e.setProperty(t,n);else{const r=gc(e,t);Uo.test(n)?e.setProperty(St(r),n.replace(Uo,""),"important"):e[r]=n}}const Wo=["Webkit","Moz","ms"],ur={};function gc(e,t){const n=ur[t];if(n)return n;let r=Ye(t);if(r!=="filter"&&r in e)return ur[t]=r;r=Xn(r);for(let o=0;odocument.createEvent("Event").timeStamp&&(Un=()=>performance.now());const e=navigator.userAgent.match(/firefox\/(\d+)/i);Si=!!(e&&Number(e[1])<=53)}let Lr=0;const yc=Promise.resolve(),Ec=()=>{Lr=0},wc=()=>Lr||(yc.then(Ec),Lr=Un());function Ac(e,t,n,r){e.addEventListener(t,n,r)}function Pc(e,t,n,r){e.removeEventListener(t,n,r)}function Tc(e,t,n,r,o=null){const s=e._vei||(e._vei={}),i=s[t];if(r&&i)i.value=r;else{const[l,a]=xc(t);if(r){const c=s[t]=Cc(r,o);Ac(e,l,c,a)}else i&&(Pc(e,l,i,a),s[t]=void 0)}}const Ko=/(?:Once|Passive|Capture)$/;function xc(e){let t;if(Ko.test(e)){t={};let n;for(;n=e.match(Ko);)e=e.slice(0,e.length-n[0].length),t[n[0].toLowerCase()]=!0}return[St(e.slice(2)),t]}function Cc(e,t){const n=r=>{const o=r.timeStamp||Un();(Si||o>=n.attached-1)&&Ve(Rc(r,n.value),t,5,[r])};return n.value=e,n.attached=wc(),n}function Rc(e,t){if(X(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>o=>!o._stopped&&r&&r(o))}else return t}const Jo=/^on[a-z]/,Sc=(e,t,n,r,o=!1,s,i,l,a)=>{t==="class"?pc(e,r,o):t==="style"?vc(e,n,r):_n(t)?jr(t)||Tc(e,t,n,r,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Oc(e,t,r,o))?bc(e,t,r,s,i,l,a):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),_c(e,t,r,o))};function Oc(e,t,n,r){return r?!!(t==="innerHTML"||t==="textContent"||t in e&&Jo.test(t)&&te(n)):t==="spellcheck"||t==="draggable"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA"||Jo.test(t)&&he(n)?!1:t in e}const it="transition",Qt="animation",lo=(e,{slots:t})=>ge(si,Lc(e),t);lo.displayName="Transition";const Oi={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};lo.props=Pe({},si.props,Oi);const gt=(e,t=[])=>{X(e)?e.forEach(n=>n(...t)):e&&e(...t)},Yo=e=>e?X(e)?e.some(t=>t.length>1):e.length>1:!1;function Lc(e){const t={};for(const y in e)y in Oi||(t[y]=e[y]);if(e.css===!1)return t;const{name:n="v",type:r,duration:o,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:a=s,appearActiveClass:c=i,appearToClass:f=l,leaveFromClass:p=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:E=`${n}-leave-to`}=e,h=Ic(o),b=h&&h[0],v=h&&h[1],{onBeforeEnter:g,onEnter:A,onEnterCancelled:R,onLeave:O,onLeaveCancelled:j,onBeforeAppear:B=g,onAppear:x=A,onAppearCancelled:w=R}=t,J=(y,N,K)=>{Lt(y,N?f:l),Lt(y,N?c:i),K&&K()},U=(y,N)=>{Lt(y,E),Lt(y,d),N&&N()},Y=y=>(N,K)=>{const le=y?x:A,z=()=>J(N,y,K);gt(le,[N,z]),Xo(()=>{Lt(N,y?a:s),lt(N,y?f:l),Yo(le)||Qo(N,r,b,z)})};return Pe(t,{onBeforeEnter(y){gt(g,[y]),lt(y,s),lt(y,i)},onBeforeAppear(y){gt(B,[y]),lt(y,a),lt(y,c)},onEnter:Y(!1),onAppear:Y(!0),onLeave(y,N){const K=()=>U(y,N);lt(y,p),Mc(),lt(y,d),Xo(()=>{Lt(y,p),lt(y,E),Yo(O)||Qo(y,r,v,K)}),gt(O,[y,K])},onEnterCancelled(y){J(y,!1),gt(R,[y])},onAppearCancelled(y){J(y,!0),gt(w,[y])},onLeaveCancelled(y){U(y),gt(j,[y])}})}function Ic(e){if(e==null)return null;if(ye(e))return[fr(e.enter),fr(e.leave)];{const t=fr(e);return[t,t]}}function fr(e){return Ls(e)}function lt(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e._vtc||(e._vtc=new Set)).add(t)}function Lt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const{_vtc:n}=e;n&&(n.delete(t),n.size||(e._vtc=void 0))}function Xo(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Dc=0;function Qo(e,t,n,r){const o=e._endId=++Dc,s=()=>{o===e._endId&&r()};if(n)return setTimeout(s,n);const{type:i,timeout:l,propCount:a}=kc(e,t);if(!i)return r();const c=i+"end";let f=0;const p=()=>{e.removeEventListener(c,d),s()},d=E=>{E.target===e&&++f>=a&&p()};setTimeout(()=>{f(n[h]||"").split(", "),o=r(it+"Delay"),s=r(it+"Duration"),i=Zo(o,s),l=r(Qt+"Delay"),a=r(Qt+"Duration"),c=Zo(l,a);let f=null,p=0,d=0;t===it?i>0&&(f=it,p=i,d=s.length):t===Qt?c>0&&(f=Qt,p=c,d=a.length):(p=Math.max(i,c),f=p>0?i>c?it:Qt:null,d=f?f===it?s.length:a.length:0);const E=f===it&&/\b(transform|all)(,|$)/.test(n[it+"Property"]);return{type:f,timeout:p,propCount:d,hasTransform:E}}function Zo(e,t){for(;e.lengthGo(n)+Go(e[r])))}function Go(e){return Number(e.slice(0,-1).replace(",","."))*1e3}function Mc(){return document.body.offsetHeight}const Nc={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Kd=(e,t)=>n=>{if(!("key"in n))return;const r=St(n.key);if(t.some(o=>o===r||Nc[o]===r))return e(n)},Jd={beforeMount(e,{value:t},{transition:n}){e._vod=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Zt(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),Zt(e,!0),r.enter(e)):r.leave(e,()=>{Zt(e,!1)}):Zt(e,t))},beforeUnmount(e,{value:t}){Zt(e,t)}};function Zt(e,t){e.style.display=t?e._vod:"none"}const Hc=Pe({patchProp:Sc},mc);let dr,es=!1;function jc(){return dr=es?dr:Wa(Hc),es=!0,dr}const Fc=(...e)=>{const t=jc().createApp(...e),{mount:n}=t;return t.mount=r=>{const o=Vc(r);if(o)return n(o,!0,o instanceof SVGElement)},t};function Vc(e){return he(e)?document.querySelector(e):e}/*! + * vue-router v4.0.13 + * (c) 2022 Eduardo San Martin Morote + * @license MIT + */const Li=typeof Symbol=="function"&&typeof Symbol.toStringTag=="symbol",Jt=e=>Li?Symbol(e):"_vr_"+e,$c=Jt("rvlm"),ts=Jt("rvd"),tr=Jt("r"),ao=Jt("rl"),Ir=Jt("rvl"),Nt=typeof window!="undefined";function zc(e){return e.__esModule||Li&&e[Symbol.toStringTag]==="Module"}const fe=Object.assign;function hr(e,t){const n={};for(const r in t){const o=t[r];n[r]=Array.isArray(o)?o.map(e):e(o)}return n}const cn=()=>{},Bc=/\/$/,Uc=e=>e.replace(Bc,"");function mr(e,t,n="/"){let r,o={},s="",i="";const l=t.indexOf("?"),a=t.indexOf("#",l>-1?l:0);return l>-1&&(r=t.slice(0,l),s=t.slice(l+1,a>-1?a:t.length),o=e(s)),a>-1&&(r=r||t.slice(0,a),i=t.slice(a,t.length)),r=Jc(r!=null?r:t,n),{fullPath:r+(s&&"?")+s+i,path:r,query:o,hash:i}}function Wc(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function ns(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function qc(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&Bt(t.matched[r],n.matched[o])&&Ii(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function Bt(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Ii(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!Kc(e[n],t[n]))return!1;return!0}function Kc(e,t){return Array.isArray(e)?rs(e,t):Array.isArray(t)?rs(t,e):e===t}function rs(e,t){return Array.isArray(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function Jc(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/");let o=n.length-1,s,i;for(s=0;s({left:window.pageXOffset,top:window.pageYOffset});function Gc(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),o=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=Zc(o,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.pageXOffset,t.top!=null?t.top:window.pageYOffset)}function os(e,t){return(history.state?history.state.position-t:-1)+e}const Dr=new Map;function eu(e,t){Dr.set(e,t)}function tu(e){const t=Dr.get(e);return Dr.delete(e),t}let nu=()=>location.protocol+"//"+location.host;function Di(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf("#");if(s>-1){let l=o.includes(e.slice(s))?e.slice(s).length:1,a=o.slice(l);return a[0]!=="/"&&(a="/"+a),ns(a,"")}return ns(n,e)+r+o}function ru(e,t,n,r){let o=[],s=[],i=null;const l=({state:d})=>{const E=Di(e,location),h=n.value,b=t.value;let v=0;if(d){if(n.value=E,t.value=d,i&&i===h){i=null;return}v=b?d.position-b.position:0}else r(E);o.forEach(g=>{g(n.value,h,{delta:v,type:vn.pop,direction:v?v>0?un.forward:un.back:un.unknown})})};function a(){i=n.value}function c(d){o.push(d);const E=()=>{const h=o.indexOf(d);h>-1&&o.splice(h,1)};return s.push(E),E}function f(){const{history:d}=window;!d.state||d.replaceState(fe({},d.state,{scroll:nr()}),"")}function p(){for(const d of s)d();s=[],window.removeEventListener("popstate",l),window.removeEventListener("beforeunload",f)}return window.addEventListener("popstate",l),window.addEventListener("beforeunload",f),{pauseListeners:a,listen:c,destroy:p}}function ss(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?nr():null}}function ou(e){const{history:t,location:n}=window,r={value:Di(e,n)},o={value:t.state};o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function s(a,c,f){const p=e.indexOf("#"),d=p>-1?(n.host&&document.querySelector("base")?e:e.slice(p))+a:nu()+e+a;try{t[f?"replaceState":"pushState"](c,"",d),o.value=c}catch(E){console.error(E),n[f?"replace":"assign"](d)}}function i(a,c){const f=fe({},t.state,ss(o.value.back,a,o.value.forward,!0),c,{position:o.value.position});s(a,f,!0),r.value=a}function l(a,c){const f=fe({},o.value,t.state,{forward:a,scroll:nr()});s(f.current,f,!0);const p=fe({},ss(r.value,a,null),{position:f.position+1},c);s(a,p,!1),r.value=a}return{location:r,state:o,push:l,replace:i}}function su(e){e=Yc(e);const t=ou(e),n=ru(e,t.state,t.location,t.replace);function r(s,i=!0){i||n.pauseListeners(),history.go(s)}const o=fe({location:"",base:e,go:r,createHref:Qc.bind(null,e)},t,n);return Object.defineProperty(o,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,"state",{enumerable:!0,get:()=>t.state.value}),o}function iu(e){return typeof e=="string"||e&&typeof e=="object"}function ki(e){return typeof e=="string"||typeof e=="symbol"}const Qe={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},Mi=Jt("nf");var is;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(is||(is={}));function Ut(e,t){return fe(new Error,{type:e,[Mi]:!0},t)}function at(e,t){return e instanceof Error&&Mi in e&&(t==null||!!(e.type&t))}const ls="[^/]+?",lu={sensitive:!1,strict:!1,start:!0,end:!0},au=/[.+*?^${}()[\]/\\]/g;function cu(e,t){const n=fe({},lu,t),r=[];let o=n.start?"^":"";const s=[];for(const c of e){const f=c.length?[]:[90];n.strict&&!c.length&&(o+="/");for(let p=0;pt.length?t.length===1&&t[0]===40+40?1:-1:0}function fu(e,t){let n=0;const r=e.score,o=t.score;for(;n1&&(a==="*"||a==="+")&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),s.push({type:1,value:c,regexp:f,repeatable:a==="*"||a==="+",optional:a==="*"||a==="?"})):t("Invalid state to consume buffer"),c="")}function d(){c+=a}for(;l{i(A)}:cn}function i(f){if(ki(f)){const p=r.get(f);p&&(r.delete(f),n.splice(n.indexOf(p),1),p.children.forEach(i),p.alias.forEach(i))}else{const p=n.indexOf(f);p>-1&&(n.splice(p,1),f.record.name&&r.delete(f.record.name),f.children.forEach(i),f.alias.forEach(i))}}function l(){return n}function a(f){let p=0;for(;p=0&&(f.record.path!==n[p].record.path||!Ni(f,n[p]));)p++;n.splice(p,0,f),f.record.name&&!as(f)&&r.set(f.record.name,f)}function c(f,p){let d,E={},h,b;if("name"in f&&f.name){if(d=r.get(f.name),!d)throw Ut(1,{location:f});b=d.record.name,E=fe(gu(p.params,d.keys.filter(A=>!A.optional).map(A=>A.name)),f.params),h=d.stringify(E)}else if("path"in f)h=f.path,d=n.find(A=>A.re.test(h)),d&&(E=d.parse(h),b=d.record.name);else{if(d=p.name?r.get(p.name):n.find(A=>A.re.test(p.path)),!d)throw Ut(1,{location:f,currentLocation:p});b=d.record.name,E=fe({},p.params,f.params),h=d.stringify(E)}const v=[];let g=d;for(;g;)v.unshift(g.record),g=g.parent;return{name:b,path:h,params:E,matched:v,meta:yu(v)}}return e.forEach(f=>s(f)),{addRoute:s,resolve:c,removeRoute:i,getRoutes:l,getRecordMatcher:o}}function gu(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function _u(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:bu(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||{}:{default:e.component}}}function bu(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="boolean"?n:n[r];return t}function as(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function yu(e){return e.reduce((t,n)=>fe(t,n.meta),{})}function cs(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function Ni(e,t){return t.children.some(n=>n===e||Ni(e,n))}const Hi=/#/g,Eu=/&/g,wu=/\//g,Au=/=/g,Pu=/\?/g,ji=/\+/g,Tu=/%5B/g,xu=/%5D/g,Fi=/%5E/g,Cu=/%60/g,Vi=/%7B/g,Ru=/%7C/g,$i=/%7D/g,Su=/%20/g;function co(e){return encodeURI(""+e).replace(Ru,"|").replace(Tu,"[").replace(xu,"]")}function Ou(e){return co(e).replace(Vi,"{").replace($i,"}").replace(Fi,"^")}function kr(e){return co(e).replace(ji,"%2B").replace(Su,"+").replace(Hi,"%23").replace(Eu,"%26").replace(Cu,"`").replace(Vi,"{").replace($i,"}").replace(Fi,"^")}function Lu(e){return kr(e).replace(Au,"%3D")}function Iu(e){return co(e).replace(Hi,"%23").replace(Pu,"%3F")}function Du(e){return e==null?"":Iu(e).replace(wu,"%2F")}function Wn(e){try{return decodeURIComponent(""+e)}catch{}return""+e}function ku(e){const t={};if(e===""||e==="?")return t;const r=(e[0]==="?"?e.slice(1):e).split("&");for(let o=0;os&&kr(s)):[r&&kr(r)]).forEach(s=>{s!==void 0&&(t+=(t.length?"&":"")+n,s!=null&&(t+="="+s))})}return t}function Mu(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=Array.isArray(r)?r.map(o=>o==null?null:""+o):r==null?r:""+r)}return t}function Gt(){let e=[];function t(r){return e.push(r),()=>{const o=e.indexOf(r);o>-1&&e.splice(o,1)}}function n(){e=[]}return{add:t,list:()=>e,reset:n}}function ft(e,t,n,r,o){const s=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise((i,l)=>{const a=p=>{p===!1?l(Ut(4,{from:n,to:t})):p instanceof Error?l(p):iu(p)?l(Ut(2,{from:t,to:p})):(s&&r.enterCallbacks[o]===s&&typeof p=="function"&&s.push(p),i())},c=e.call(r&&r.instances[o],t,n,a);let f=Promise.resolve(c);e.length<3&&(f=f.then(a)),f.catch(p=>l(p))})}function pr(e,t,n,r){const o=[];for(const s of e)for(const i in s.components){let l=s.components[i];if(!(t!=="beforeRouteEnter"&&!s.instances[i]))if(Nu(l)){const c=(l.__vccOpts||l)[t];c&&o.push(ft(c,n,r,s,i))}else{let a=l();o.push(()=>a.then(c=>{if(!c)return Promise.reject(new Error(`Couldn't resolve component "${i}" at "${s.path}"`));const f=zc(c)?c.default:c;s.components[i]=f;const d=(f.__vccOpts||f)[t];return d&&ft(d,n,r,s,i)()}))}}return o}function Nu(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function fs(e){const t=Ae(tr),n=Ae(ao),r=ve(()=>t.resolve(Tt(e.to))),o=ve(()=>{const{matched:a}=r.value,{length:c}=a,f=a[c-1],p=n.matched;if(!f||!p.length)return-1;const d=p.findIndex(Bt.bind(null,f));if(d>-1)return d;const E=ds(a[c-2]);return c>1&&ds(f)===E&&p[p.length-1].path!==E?p.findIndex(Bt.bind(null,a[c-2])):d}),s=ve(()=>o.value>-1&&Vu(n.params,r.value.params)),i=ve(()=>o.value>-1&&o.value===n.matched.length-1&&Ii(n.params,r.value.params));function l(a={}){return Fu(a)?t[Tt(e.replace)?"replace":"push"](Tt(e.to)).catch(cn):Promise.resolve()}return{route:r,href:ve(()=>r.value.href),isActive:s,isExactActive:i,navigate:l}}const Hu=We({name:"RouterLink",props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:fs,setup(e,{slots:t}){const n=Kt(fs(e)),{options:r}=Ae(tr),o=ve(()=>({[hs(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[hs(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const s=t.default&&t.default(n);return e.custom?s:ge("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},s)}}}),ju=Hu;function Fu(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Vu(e,t){for(const n in t){const r=t[n],o=e[n];if(typeof r=="string"){if(r!==o)return!1}else if(!Array.isArray(o)||o.length!==r.length||r.some((s,i)=>s!==o[i]))return!1}return!0}function ds(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const hs=(e,t,n)=>e!=null?e:t!=null?t:n,$u=We({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},setup(e,{attrs:t,slots:n}){const r=Ae(Ir),o=ve(()=>e.route||r.value),s=Ae(ts,0),i=ve(()=>o.value.matched[s]);xt(ts,s+1),xt($c,i),xt(Ir,o);const l=Ce();return et(()=>[l.value,i.value,e.name],([a,c,f],[p,d,E])=>{c&&(c.instances[f]=a,d&&d!==c&&a&&a===p&&(c.leaveGuards.size||(c.leaveGuards=d.leaveGuards),c.updateGuards.size||(c.updateGuards=d.updateGuards))),a&&c&&(!d||!Bt(c,d)||!p)&&(c.enterCallbacks[f]||[]).forEach(h=>h(a))},{flush:"post"}),()=>{const a=o.value,c=i.value,f=c&&c.components[e.name],p=e.name;if(!f)return ms(n.default,{Component:f,route:a});const d=c.props[e.name],E=d?d===!0?a.params:typeof d=="function"?d(a):d:null,b=ge(f,fe({},E,t,{onVnodeUnmounted:v=>{v.component.isUnmounted&&(c.instances[p]=null)},ref:l}));return ms(n.default,{Component:b,route:a})||b}}});function ms(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const zi=$u;function zu(e){const t=vu(e.routes,e),n=e.parseQuery||ku,r=e.stringifyQuery||us,o=e.history,s=Gt(),i=Gt(),l=Gt(),a=Ks(Qe);let c=Qe;Nt&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const f=hr.bind(null,P=>""+P),p=hr.bind(null,Du),d=hr.bind(null,Wn);function E(P,F){let k,V;return ki(P)?(k=t.getRecordMatcher(P),V=F):V=P,t.addRoute(V,k)}function h(P){const F=t.getRecordMatcher(P);F&&t.removeRoute(F)}function b(){return t.getRoutes().map(P=>P.record)}function v(P){return!!t.getRecordMatcher(P)}function g(P,F){if(F=fe({},F||a.value),typeof P=="string"){const G=mr(n,P,F.path),u=t.resolve({path:G.path},F),m=o.createHref(G.fullPath);return fe(G,u,{params:d(u.params),hash:Wn(G.hash),redirectedFrom:void 0,href:m})}let k;if("path"in P)k=fe({},P,{path:mr(n,P.path,F.path).path});else{const G=fe({},P.params);for(const u in G)G[u]==null&&delete G[u];k=fe({},P,{params:p(P.params)}),F.params=p(F.params)}const V=t.resolve(k,F),ae=P.hash||"";V.params=f(d(V.params));const ce=Wc(r,fe({},P,{hash:Ou(ae),path:V.path})),ne=o.createHref(ce);return fe({fullPath:ce,hash:ae,query:r===us?Mu(P.query):P.query||{}},V,{redirectedFrom:void 0,href:ne})}function A(P){return typeof P=="string"?mr(n,P,a.value.path):fe({},P)}function R(P,F){if(c!==P)return Ut(8,{from:F,to:P})}function O(P){return x(P)}function j(P){return O(fe(A(P),{replace:!0}))}function B(P){const F=P.matched[P.matched.length-1];if(F&&F.redirect){const{redirect:k}=F;let V=typeof k=="function"?k(P):k;return typeof V=="string"&&(V=V.includes("?")||V.includes("#")?V=A(V):{path:V},V.params={}),fe({query:P.query,hash:P.hash,params:P.params},V)}}function x(P,F){const k=c=g(P),V=a.value,ae=P.state,ce=P.force,ne=P.replace===!0,G=B(k);if(G)return x(fe(A(G),{state:ae,force:ce,replace:ne}),F||k);const u=k;u.redirectedFrom=F;let m;return!ce&&qc(r,V,k)&&(m=Ut(16,{to:u,from:V}),Te(V,V,!0,!1)),(m?Promise.resolve(m):J(u,V)).catch(_=>at(_)?at(_,2)?_:be(_):ee(_,u,V)).then(_=>{if(_){if(at(_,2))return x(fe(A(_.to),{state:ae,force:ce,replace:ne}),F||u)}else _=Y(u,V,!0,ne,ae);return U(u,V,_),_})}function w(P,F){const k=R(P,F);return k?Promise.reject(k):Promise.resolve()}function J(P,F){let k;const[V,ae,ce]=Bu(P,F);k=pr(V.reverse(),"beforeRouteLeave",P,F);for(const G of V)G.leaveGuards.forEach(u=>{k.push(ft(u,P,F))});const ne=w.bind(null,P,F);return k.push(ne),It(k).then(()=>{k=[];for(const G of s.list())k.push(ft(G,P,F));return k.push(ne),It(k)}).then(()=>{k=pr(ae,"beforeRouteUpdate",P,F);for(const G of ae)G.updateGuards.forEach(u=>{k.push(ft(u,P,F))});return k.push(ne),It(k)}).then(()=>{k=[];for(const G of P.matched)if(G.beforeEnter&&!F.matched.includes(G))if(Array.isArray(G.beforeEnter))for(const u of G.beforeEnter)k.push(ft(u,P,F));else k.push(ft(G.beforeEnter,P,F));return k.push(ne),It(k)}).then(()=>(P.matched.forEach(G=>G.enterCallbacks={}),k=pr(ce,"beforeRouteEnter",P,F),k.push(ne),It(k))).then(()=>{k=[];for(const G of i.list())k.push(ft(G,P,F));return k.push(ne),It(k)}).catch(G=>at(G,8)?G:Promise.reject(G))}function U(P,F,k){for(const V of l.list())V(P,F,k)}function Y(P,F,k,V,ae){const ce=R(P,F);if(ce)return ce;const ne=F===Qe,G=Nt?history.state:{};k&&(V||ne?o.replace(P.fullPath,fe({scroll:ne&&G&&G.scroll},ae)):o.push(P.fullPath,ae)),a.value=P,Te(P,F,k,ne),be()}let y;function N(){y=o.listen((P,F,k)=>{const V=g(P),ae=B(V);if(ae){x(fe(ae,{replace:!0}),V).catch(cn);return}c=V;const ce=a.value;Nt&&eu(os(ce.fullPath,k.delta),nr()),J(V,ce).catch(ne=>at(ne,12)?ne:at(ne,2)?(x(ne.to,V).then(G=>{at(G,20)&&!k.delta&&k.type===vn.pop&&o.go(-1,!1)}).catch(cn),Promise.reject()):(k.delta&&o.go(-k.delta,!1),ee(ne,V,ce))).then(ne=>{ne=ne||Y(V,ce,!1),ne&&(k.delta?o.go(-k.delta,!1):k.type===vn.pop&&at(ne,20)&&o.go(-1,!1)),U(V,ce,ne)}).catch(cn)})}let K=Gt(),le=Gt(),z;function ee(P,F,k){be(P);const V=le.list();return V.length?V.forEach(ae=>ae(P,F,k)):console.error(P),Promise.reject(P)}function re(){return z&&a.value!==Qe?Promise.resolve():new Promise((P,F)=>{K.add([P,F])})}function be(P){return z||(z=!P,N(),K.list().forEach(([F,k])=>P?k(P):F()),K.reset()),P}function Te(P,F,k,V){const{scrollBehavior:ae}=e;if(!Nt||!ae)return Promise.resolve();const ce=!k&&tu(os(P.fullPath,0))||(V||!k)&&history.state&&history.state.scroll||null;return Qr().then(()=>ae(P,F,ce)).then(ne=>ne&&Gc(ne)).catch(ne=>ee(ne,P,F))}const Le=P=>o.go(P);let Ne;const Re=new Set;return{currentRoute:a,addRoute:E,removeRoute:h,hasRoute:v,getRoutes:b,resolve:g,options:e,push:O,replace:j,go:Le,back:()=>Le(-1),forward:()=>Le(1),beforeEach:s.add,beforeResolve:i.add,afterEach:l.add,onError:le.add,isReady:re,install(P){const F=this;P.component("RouterLink",ju),P.component("RouterView",zi),P.config.globalProperties.$router=F,Object.defineProperty(P.config.globalProperties,"$route",{enumerable:!0,get:()=>Tt(a)}),Nt&&!Ne&&a.value===Qe&&(Ne=!0,O(o.location).catch(ae=>{}));const k={};for(const ae in Qe)k[ae]=ve(()=>a.value[ae]);P.provide(tr,F),P.provide(ao,Kt(k)),P.provide(Ir,a);const V=P.unmount;Re.add(P),P.unmount=function(){Re.delete(P),Re.size<1&&(c=Qe,y&&y(),a.value=Qe,Ne=!1,z=!1),V()}}}}function It(e){return e.reduce((t,n)=>t.then(()=>n()),Promise.resolve())}function Bu(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let i=0;iBt(c,l))?r.push(l):n.push(l));const a=e.matched[i];a&&(t.matched.find(c=>Bt(c,a))||o.push(a))}return[n,r,o]}function uo(){return Ae(tr)}function fo(){return Ae(ao)}const Uu=We({setup(e,t){const n=Ce(!1);return rt(()=>{n.value=!0}),()=>{var r,o;return n.value?(o=(r=t.slots).default)===null||o===void 0?void 0:o.call(r):null}}}),Wu="modulepreload",ps={},qu="/angular-security-training/",L=function(t,n){return!n||n.length===0?t():Promise.all(n.map(r=>{if(r=`${qu}${r}`,r in ps)return;ps[r]=!0;const o=r.endsWith(".css"),s=o?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${r}"]${s}`))return;const i=document.createElement("link");if(i.rel=o?"stylesheet":Wu,o||(i.as="script",i.crossOrigin=""),i.href=r,document.head.appendChild(i),o)return new Promise((l,a)=>{i.addEventListener("load",l),i.addEventListener("error",()=>a(new Error(`Unable to preload CSS for ${r}`)))})})).then(()=>t())},Bi={"v-8daa1a0e":Z(()=>L(()=>import("./index.html.504c7800.js"),["assets/index.html.504c7800.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-638c1d18":Z(()=>L(()=>import("./index.html.83bca83e.js"),["assets/index.html.83bca83e.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-4a3aeb3f":Z(()=>L(()=>import("./index.html.ed792a20.js"),["assets/index.html.ed792a20.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-5e4edb7a":Z(()=>L(()=>import("./index.html.ca66f9e9.js"),["assets/index.html.ca66f9e9.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-0b15ef8c":Z(()=>L(()=>import("./csp-angular.html.5650697a.js"),["assets/csp-angular.html.5650697a.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-1f53b9ee":Z(()=>L(()=>import("./csp-defense.html.b1f2da71.js"),["assets/csp-defense.html.b1f2da71.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-6d19a24b":Z(()=>L(()=>import("./csp-overview.html.d5a381b9.js"),["assets/csp-overview.html.d5a381b9.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-3617bebd":Z(()=>L(()=>import("./csp-pw.html.8a2f6807.js"),["assets/csp-pw.html.8a2f6807.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-712e14fc":Z(()=>L(()=>import("./index.html.cf9cc2fc.js"),["assets/index.html.cf9cc2fc.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-6096668b":Z(()=>L(()=>import("./index.html.1e04ca27.js"),["assets/index.html.1e04ca27.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-5c6f2e35":Z(()=>L(()=>import("./api-defense.html.24b7de7a.js"),["assets/api-defense.html.24b7de7a.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-59269238":Z(()=>L(()=>import("./api-overview.html.0c4ffcd6.js"),["assets/api-overview.html.0c4ffcd6.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-69cf0bd4":Z(()=>L(()=>import("./api-pw.html.dea250a0.js"),["assets/api-pw.html.dea250a0.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-c8cf272e":Z(()=>L(()=>import("./xssi-angular.html.c5b67aff.js"),["assets/xssi-angular.html.c5b67aff.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-a053926a":Z(()=>L(()=>import("./xssi-defense.html.bbace4b9.js"),["assets/xssi-defense.html.bbace4b9.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-26327de4":Z(()=>L(()=>import("./xssi-overview.html.27376c5a.js"),["assets/xssi-overview.html.27376c5a.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-3a702880":Z(()=>L(()=>import("./xssi-pw.html.5edd46e5.js"),["assets/xssi-pw.html.5edd46e5.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-33ffe414":Z(()=>L(()=>import("./csrf-angular.html.90dda192.js"),["assets/csrf-angular.html.90dda192.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-483dae76":Z(()=>L(()=>import("./csrf-defense.html.d4edf311.js"),["assets/csrf-defense.html.d4edf311.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-768c5bde":Z(()=>L(()=>import("./csrf-detection.html.14617e37.js"),["assets/csrf-detection.html.14617e37.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-616e3ec3":Z(()=>L(()=>import("./csrf-overview.html.b112ad93.js"),["assets/csrf-overview.html.b112ad93.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-894dd596":Z(()=>L(()=>import("./csrf-pw.html.d35c37d8.js"),["assets/csrf-pw.html.d35c37d8.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-6cd88d9e":Z(()=>L(()=>import("./jwt-best-current-practices.html.8b518b9f.js"),["assets/jwt-best-current-practices.html.8b518b9f.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-ef5efe92":Z(()=>L(()=>import("./jwt-known-threats.html.b6ff4752.js"),["assets/jwt-known-threats.html.b6ff4752.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-107f7b53":Z(()=>L(()=>import("./jwt-overview.html.c09eb65b.js"),["assets/jwt-overview.html.c09eb65b.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-6cf6fdc5":Z(()=>L(()=>import("./jwt-pw.html.f3d2e3b8.js"),["assets/jwt-pw.html.f3d2e3b8.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-746e588b":Z(()=>L(()=>import("./jwt-storage.html.334fb3d7.js"),["assets/jwt-storage.html.334fb3d7.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-34b20ccd":Z(()=>L(()=>import("./jwt-workflow.html.a1cefe45.js"),["assets/jwt-workflow.html.a1cefe45.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-78a07c70":Z(()=>L(()=>import("./sca-angular.html.49664ab7.js"),["assets/sca-angular.html.49664ab7.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-e643725c":Z(()=>L(()=>import("./sca-defense.html.717c2a99.js"),["assets/sca-defense.html.717c2a99.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-6395c36d":Z(()=>L(()=>import("./sca-detection.html.cf220935.js"),["assets/sca-detection.html.cf220935.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-9e3e9c32":Z(()=>L(()=>import("./sca-overview.html.2fb3be4b.js"),["assets/sca-overview.html.2fb3be4b.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-61d9b359":Z(()=>L(()=>import("./sca-pw.html.2633f907.js"),["assets/sca-pw.html.2633f907.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-388c7174":Z(()=>L(()=>import("./ssti-angular.html.2b676577.js"),["assets/ssti-angular.html.2b676577.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-4cca3bd6":Z(()=>L(()=>import("./ssti-defense.html.0ca420bf.js"),["assets/ssti-defense.html.0ca420bf.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-2319453a":Z(()=>L(()=>import("./ssti-overview.html.57095fcf.js"),["assets/ssti-overview.html.57095fcf.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-da4c55bc":Z(()=>L(()=>import("./xss-angular.html.f7e2e4fb.js"),["assets/xss-angular.html.f7e2e4fb.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-b1d0c0f8":Z(()=>L(()=>import("./xss-defense.html.36a13770.js"),["assets/xss-defense.html.36a13770.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-564890c2":Z(()=>L(()=>import("./xss-detection.html.d354b8ce.js"),["assets/xss-detection.html.d354b8ce.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-445b2116":Z(()=>L(()=>import("./xss-overview.html.3f6088f9.js"),["assets/xss-overview.html.3f6088f9.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-bfd79932":Z(()=>L(()=>import("./xss-pw.html.98d06295.js"),["assets/xss-pw.html.98d06295.js","assets/pw-coding.b255a857.js","assets/plugin-vue_export-helper.21dcd24c.js"])),"v-3706649a":Z(()=>L(()=>import("./404.html.bee13de6.js"),["assets/404.html.bee13de6.js","assets/plugin-vue_export-helper.21dcd24c.js"]))},Ku={"v-8daa1a0e":()=>L(()=>import("./index.html.f3382a31.js"),[]).then(({data:e})=>e),"v-638c1d18":()=>L(()=>import("./index.html.790af791.js"),[]).then(({data:e})=>e),"v-4a3aeb3f":()=>L(()=>import("./index.html.c56b5b5b.js"),[]).then(({data:e})=>e),"v-5e4edb7a":()=>L(()=>import("./index.html.083cdbcc.js"),[]).then(({data:e})=>e),"v-0b15ef8c":()=>L(()=>import("./csp-angular.html.18184a38.js"),[]).then(({data:e})=>e),"v-1f53b9ee":()=>L(()=>import("./csp-defense.html.37265fe3.js"),[]).then(({data:e})=>e),"v-6d19a24b":()=>L(()=>import("./csp-overview.html.f6add21f.js"),[]).then(({data:e})=>e),"v-3617bebd":()=>L(()=>import("./csp-pw.html.093d9e16.js"),[]).then(({data:e})=>e),"v-712e14fc":()=>L(()=>import("./index.html.a1dfb7e4.js"),[]).then(({data:e})=>e),"v-6096668b":()=>L(()=>import("./index.html.1a163877.js"),[]).then(({data:e})=>e),"v-5c6f2e35":()=>L(()=>import("./api-defense.html.1cd6a815.js"),[]).then(({data:e})=>e),"v-59269238":()=>L(()=>import("./api-overview.html.8d09847e.js"),[]).then(({data:e})=>e),"v-69cf0bd4":()=>L(()=>import("./api-pw.html.700f3ec1.js"),[]).then(({data:e})=>e),"v-c8cf272e":()=>L(()=>import("./xssi-angular.html.ec608184.js"),[]).then(({data:e})=>e),"v-a053926a":()=>L(()=>import("./xssi-defense.html.bd633333.js"),[]).then(({data:e})=>e),"v-26327de4":()=>L(()=>import("./xssi-overview.html.0a527ba5.js"),[]).then(({data:e})=>e),"v-3a702880":()=>L(()=>import("./xssi-pw.html.194b3eb1.js"),[]).then(({data:e})=>e),"v-33ffe414":()=>L(()=>import("./csrf-angular.html.82bd6cc8.js"),[]).then(({data:e})=>e),"v-483dae76":()=>L(()=>import("./csrf-defense.html.30ae6842.js"),[]).then(({data:e})=>e),"v-768c5bde":()=>L(()=>import("./csrf-detection.html.91492d93.js"),[]).then(({data:e})=>e),"v-616e3ec3":()=>L(()=>import("./csrf-overview.html.e215b1cd.js"),[]).then(({data:e})=>e),"v-894dd596":()=>L(()=>import("./csrf-pw.html.a6ff6a71.js"),[]).then(({data:e})=>e),"v-6cd88d9e":()=>L(()=>import("./jwt-best-current-practices.html.d6ad8e5d.js"),[]).then(({data:e})=>e),"v-ef5efe92":()=>L(()=>import("./jwt-known-threats.html.93bab53d.js"),[]).then(({data:e})=>e),"v-107f7b53":()=>L(()=>import("./jwt-overview.html.1078459c.js"),[]).then(({data:e})=>e),"v-6cf6fdc5":()=>L(()=>import("./jwt-pw.html.ea24f37d.js"),[]).then(({data:e})=>e),"v-746e588b":()=>L(()=>import("./jwt-storage.html.bbba3dd7.js"),[]).then(({data:e})=>e),"v-34b20ccd":()=>L(()=>import("./jwt-workflow.html.b57bcda8.js"),[]).then(({data:e})=>e),"v-78a07c70":()=>L(()=>import("./sca-angular.html.5fe1713d.js"),[]).then(({data:e})=>e),"v-e643725c":()=>L(()=>import("./sca-defense.html.5897e386.js"),[]).then(({data:e})=>e),"v-6395c36d":()=>L(()=>import("./sca-detection.html.9589332e.js"),[]).then(({data:e})=>e),"v-9e3e9c32":()=>L(()=>import("./sca-overview.html.1322b6ee.js"),[]).then(({data:e})=>e),"v-61d9b359":()=>L(()=>import("./sca-pw.html.ac121278.js"),[]).then(({data:e})=>e),"v-388c7174":()=>L(()=>import("./ssti-angular.html.27a6efaa.js"),[]).then(({data:e})=>e),"v-4cca3bd6":()=>L(()=>import("./ssti-defense.html.b6c596e0.js"),[]).then(({data:e})=>e),"v-2319453a":()=>L(()=>import("./ssti-overview.html.3fd3f546.js"),[]).then(({data:e})=>e),"v-da4c55bc":()=>L(()=>import("./xss-angular.html.b761e829.js"),[]).then(({data:e})=>e),"v-b1d0c0f8":()=>L(()=>import("./xss-defense.html.b142f013.js"),[]).then(({data:e})=>e),"v-564890c2":()=>L(()=>import("./xss-detection.html.45c7d4d3.js"),[]).then(({data:e})=>e),"v-445b2116":()=>L(()=>import("./xss-overview.html.6b99eb98.js"),[]).then(({data:e})=>e),"v-bfd79932":()=>L(()=>import("./xss-pw.html.c4025753.js"),[]).then(({data:e})=>e),"v-3706649a":()=>L(()=>import("./404.html.f166316b.js"),[]).then(({data:e})=>e)},Ui=Ce(Ku),Wi=Kr({key:"",path:"",title:"",lang:"",frontmatter:{},excerpt:"",headers:[]}),Ge=Ce(Wi),En=()=>Ge;go.webpackHot&&(__VUE_HMR_RUNTIME__.updatePageData=e=>{Ui.value[e.key]=()=>Promise.resolve(e),e.key===Ge.value.key&&(Ge.value=e)});const qi=Symbol(""),Ju=()=>{const e=Ae(qi);if(!e)throw new Error("usePageFrontmatter() is called without provider.");return e},Ki=Symbol(""),Yu=()=>{const e=Ae(Ki);if(!e)throw new Error("usePageHead() is called without provider.");return e},Xu=Symbol(""),Ji=Symbol(""),Qu=()=>{const e=Ae(Ji);if(!e)throw new Error("usePageLang() is called without provider.");return e},ho=Symbol(""),Zu=()=>{const e=Ae(ho);if(!e)throw new Error("useRouteLocale() is called without provider.");return e},Gu={base:"/angular-security-training/",lang:"en-US",title:"Angular Security Training",description:"Learn how to prevent common threats in your Angular web application !",head:[["link",{rel:"icon",href:"/logo.svg"}],["meta",{name:"apple-mobile-web-app-capable",content:"yes"}],["meta",{name:"apple-mobile-web-app-status-bar-style",content:"black"}]],locales:{}},dt=Ce(Gu),ef=()=>dt;go.webpackHot&&(__VUE_HMR_RUNTIME__.updateSiteData=e=>{dt.value=e});const Yi=Symbol(""),Yd=()=>{const e=Ae(Yi);if(!e)throw new Error("useSiteLocaleData() is called without provider.");return e},tf=Symbol(""),mo=e=>{let t;e.pageKey?t=e.pageKey:t=En().value.key;const n=Bi[t];return n?ge(n):ge("div","404 Not Found")};mo.displayName="Content";mo.props={pageKey:{type:String,required:!1}};const nf={"404":Z(()=>L(()=>import("./404.c6b298bb.js"),[])),Layout:Z(()=>L(()=>import("./Layout.cedff8e1.js"),["assets/Layout.cedff8e1.js","assets/plugin-vue_export-helper.21dcd24c.js"]))},rf=([e,t,n])=>e==="meta"&&t.name?`${e}.${t.name}`:["title","base"].includes(e)?e:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,t,n]),of=e=>{const t=new Set,n=[];return e.forEach(r=>{const o=rf(r);t.has(o)||(t.add(o),n.push(r))}),n},sf=e=>/^(https?:)?\/\//.test(e),Xd=e=>/^mailto:/.test(e),Qd=e=>/^tel:/.test(e),Xi=e=>Object.prototype.toString.call(e)==="[object Object]",lf=e=>e.replace(/\/$/,""),af=e=>e.replace(/^\//,""),Qi=(e,t)=>{const n=Object.keys(e).sort((r,o)=>{const s=o.split("/").length-r.split("/").length;return s!==0?s:o.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},vs=We({name:"Vuepress",setup(){const e=En(),t=ve(()=>{let n;if(e.value.path){const r=e.value.frontmatter.layout;he(r)?n=r:n="Layout"}else n="404";return nf[n]||Ya(n,!1)});return()=>ge(t.value)}}),rr=e=>e,po=e=>e,cf=e=>sf(e)?e:`${ef().value.base}${af(e)}`,bt=Kt({resolvePageData:async e=>{const t=Ui.value[e],n=await(t==null?void 0:t());return n!=null?n:Wi},resolvePageFrontmatter:e=>e.frontmatter,resolvePageHead:(e,t,n)=>{const r=he(t.description)?t.description:n.description,o=[...X(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return of(o)},resolvePageHeadTitle:(e,t)=>`${e.title?`${e.title} | `:""}${t.title}`,resolvePageLang:e=>e.lang||"en",resolveRouteLocale:(e,t)=>Qi(e,t),resolveSiteLocaleData:(e,t)=>Xe(Xe({},e),e.locales[t])});const uf=ge("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[ge("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),ge("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),ff=We({name:"ExternalLinkIcon",props:{locales:{type:Object,required:!1,default:()=>({})}},setup(e){const t=Zu(),n=ve(()=>{var r;return(r=e.locales[t.value])!==null&&r!==void 0?r:{openInNewWindow:"open in new window"}});return()=>ge("span",[uf,ge("span",{class:"external-link-icon-sr-only"},n.value.openInNewWindow)])}}),df={"/":{openInNewWindow:"open in new window"}};var hf=rr(({app:e})=>{e.component("ExternalLinkIcon",ge(ff,{locales:df}))});/*! medium-zoom 1.0.6 | MIT License | https://github.com/francoischalifour/medium-zoom */var yt=Object.assign||function(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:{},r=window.Promise||function(y){function N(){}y(N,N)},o=function(y){var N=y.target;if(N===J){h();return}R.indexOf(N)!==-1&&b({target:N})},s=function(){if(!(j||!w.original)){var y=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(B-y)>x.scrollOffset&&setTimeout(h,150)}},i=function(y){var N=y.key||y.keyCode;(N==="Escape"||N==="Esc"||N===27)&&h()},l=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},N=y;if(y.background&&(J.style.background=y.background),y.container&&y.container instanceof Object&&(N.container=yt({},x.container,y.container)),y.template){var K=In(y.template)?y.template:document.querySelector(y.template);N.template=K}return x=yt({},x,N),R.forEach(function(le){le.dispatchEvent(Dt("medium-zoom:update",{detail:{zoom:U}}))}),U},a=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return e(yt({},x,y))},c=function(){for(var y=arguments.length,N=Array(y),K=0;K0?N.reduce(function(z,ee){return[].concat(z,_s(ee))},[]):R;return le.forEach(function(z){z.classList.remove("medium-zoom-image"),z.dispatchEvent(Dt("medium-zoom:detach",{detail:{zoom:U}}))}),R=R.filter(function(z){return le.indexOf(z)===-1}),U},p=function(y,N){var K=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return R.forEach(function(le){le.addEventListener("medium-zoom:"+y,N,K)}),O.push({type:"medium-zoom:"+y,listener:N,options:K}),U},d=function(y,N){var K=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return R.forEach(function(le){le.removeEventListener("medium-zoom:"+y,N,K)}),O=O.filter(function(le){return!(le.type==="medium-zoom:"+y&&le.listener.toString()===N.toString())}),U},E=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},N=y.target,K=function(){var z={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},ee=void 0,re=void 0;if(x.container)if(x.container instanceof Object)z=yt({},z,x.container),ee=z.width-z.left-z.right-x.margin*2,re=z.height-z.top-z.bottom-x.margin*2;else{var be=In(x.container)?x.container:document.querySelector(x.container),Te=be.getBoundingClientRect(),Le=Te.width,Ne=Te.height,Re=Te.left,Yt=Te.top;z=yt({},z,{width:Le,height:Ne,left:Re,top:Yt})}ee=ee||z.width-x.margin*2,re=re||z.height-x.margin*2;var P=w.zoomedHd||w.original,F=gs(P)?ee:P.naturalWidth||ee,k=gs(P)?re:P.naturalHeight||re,V=P.getBoundingClientRect(),ae=V.top,ce=V.left,ne=V.width,G=V.height,u=Math.min(F,ee)/ne,m=Math.min(k,re)/G,_=Math.min(u,m),C=(-ce+(ee-ne)/2+x.margin+z.left)/_,T=(-ae+(re-G)/2+x.margin+z.top)/_,I="scale("+_+") translate3d("+C+"px, "+T+"px, 0)";w.zoomed.style.transform=I,w.zoomedHd&&(w.zoomedHd.style.transform=I)};return new r(function(le){if(N&&R.indexOf(N)===-1){le(U);return}var z=function Le(){j=!1,w.zoomed.removeEventListener("transitionend",Le),w.original.dispatchEvent(Dt("medium-zoom:opened",{detail:{zoom:U}})),le(U)};if(w.zoomed){le(U);return}if(N)w.original=N;else if(R.length>0){var ee=R;w.original=ee[0]}else{le(U);return}if(w.original.dispatchEvent(Dt("medium-zoom:open",{detail:{zoom:U}})),B=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,j=!0,w.zoomed=vf(w.original),document.body.appendChild(J),x.template){var re=In(x.template)?x.template:document.querySelector(x.template);w.template=document.createElement("div"),w.template.appendChild(re.content.cloneNode(!0)),document.body.appendChild(w.template)}if(document.body.appendChild(w.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),w.original.classList.add("medium-zoom-image--hidden"),w.zoomed.classList.add("medium-zoom-image--opened"),w.zoomed.addEventListener("click",h),w.zoomed.addEventListener("transitionend",z),w.original.getAttribute("data-zoom-src")){w.zoomedHd=w.zoomed.cloneNode(),w.zoomedHd.removeAttribute("srcset"),w.zoomedHd.removeAttribute("sizes"),w.zoomedHd.src=w.zoomed.getAttribute("data-zoom-src"),w.zoomedHd.onerror=function(){clearInterval(be),console.warn("Unable to reach the zoom image target "+w.zoomedHd.src),w.zoomedHd=null,K()};var be=setInterval(function(){w.zoomedHd.complete&&(clearInterval(be),w.zoomedHd.classList.add("medium-zoom-image--opened"),w.zoomedHd.addEventListener("click",h),document.body.appendChild(w.zoomedHd),K())},10)}else if(w.original.hasAttribute("srcset")){w.zoomedHd=w.zoomed.cloneNode(),w.zoomedHd.removeAttribute("sizes"),w.zoomedHd.removeAttribute("loading");var Te=w.zoomedHd.addEventListener("load",function(){w.zoomedHd.removeEventListener("load",Te),w.zoomedHd.classList.add("medium-zoom-image--opened"),w.zoomedHd.addEventListener("click",h),document.body.appendChild(w.zoomedHd),K()})}else K()})},h=function(){return new r(function(y){if(j||!w.original){y(U);return}var N=function K(){w.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(w.zoomed),w.zoomedHd&&document.body.removeChild(w.zoomedHd),document.body.removeChild(J),w.zoomed.classList.remove("medium-zoom-image--opened"),w.template&&document.body.removeChild(w.template),j=!1,w.zoomed.removeEventListener("transitionend",K),w.original.dispatchEvent(Dt("medium-zoom:closed",{detail:{zoom:U}})),w.original=null,w.zoomed=null,w.zoomedHd=null,w.template=null,y(U)};j=!0,document.body.classList.remove("medium-zoom--opened"),w.zoomed.style.transform="",w.zoomedHd&&(w.zoomedHd.style.transform=""),w.template&&(w.template.style.transition="opacity 150ms",w.template.style.opacity=0),w.original.dispatchEvent(Dt("medium-zoom:close",{detail:{zoom:U}})),w.zoomed.addEventListener("transitionend",N)})},b=function(){var y=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},N=y.target;return w.original?h():E({target:N})},v=function(){return x},g=function(){return R},A=function(){return w.original},R=[],O=[],j=!1,B=0,x=n,w={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?x=t:(t||typeof t=="string")&&c(t),x=yt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},x);var J=pf(x.background);document.addEventListener("click",o),document.addEventListener("keyup",i),document.addEventListener("scroll",s),window.addEventListener("resize",h);var U={open:E,close:h,toggle:b,update:l,clone:a,attach:c,detach:f,on:p,off:d,getOptions:v,getImages:g,getZoomedImage:A};return U};function _f(e,t){t===void 0&&(t={});var n=t.insertAt;if(!(!e||typeof document=="undefined")){var r=document.head||document.getElementsByTagName("head")[0],o=document.createElement("style");o.type="text/css",n==="top"&&r.firstChild?r.insertBefore(o,r.firstChild):r.appendChild(o),o.styleSheet?o.styleSheet.cssText=e:o.appendChild(document.createTextNode(e))}}var bf=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";_f(bf);var yf=gf;const Ef=Symbol("mediumZoom");const wf=".theme-default-content > img, .theme-default-content :not(a) > img",Af={},Pf=300;var Tf=rr(({app:e,router:t})=>{const n=yf(Af);n.refresh=(r=wf)=>{n.detach(),n.attach(r)},e.provide(Ef,n),t.afterEach(()=>{setTimeout(()=>n.refresh(),Pf)})});const xf={logo:"/logo.svg",sidebar:["/prerequisites/","/introduction/","/common-threats/",{title:"jwt",path:"/common-threats/jwt/",collapsable:!0,children:["/common-threats/jwt/jwt-overview.md","/common-threats/jwt/jwt-workflow.md","/common-threats/jwt/jwt-storage.md","/common-threats/jwt/jwt-known-threats.md","/common-threats/jwt/jwt-best-current-practices.md","/common-threats/jwt/jwt-pw.md"]},{title:"csrf",path:"/common-threats/csrf/",collapsable:!0,children:["/common-threats/csrf/csrf-overview.md","/common-threats/csrf/csrf-detection.md","/common-threats/csrf/csrf-defense.md","/common-threats/csrf/csrf-angular.md","/common-threats/csrf/csrf-pw.md"]},{title:"xss",path:"/common-threats/xss/",collapsable:!0,children:["/common-threats/xss/xss-overview.md","/common-threats/xss/xss-detection.md","/common-threats/xss/xss-defense.md","/common-threats/xss/xss-angular.md","/common-threats/xss/xss-pw.md"]},{title:"csp",path:"/csp/",collapsable:!0,children:["/csp/csp-overview.md","/csp/csp-defense.md","/csp/csp-angular.md","/csp/csp-pw.md"]},{title:"ssti",path:"/common-threats/ssti/",collapsable:!0,children:["/common-threats/ssti/ssti-overview.md","/common-threats/ssti/ssti-angular.md"]},{title:"third-party",path:"/common-threats/sca/",collapsable:!0,children:["/common-threats/sca/sca-overview.md","/common-threats/sca/sca-detection.md","/common-threats/sca/sca-defense.md","/common-threats/sca/sca-angular.md","/common-threats/sca/sca-pw.md"]},"/advanced/",{title:"api",path:"/advanced/api/",collapsable:!0,children:["/advanced/api/api-overview.md","/advanced/api/api-defense.md","/advanced/api/api-pw.md"]},{title:"xssi",path:"/advanced/xssi/",collapsable:!0,children:["/advanced/xssi/xssi-overview.md","/advanced/xssi/xssi-defense.md","/advanced/xssi/xssi-angular.md","/advanced/xssi/xssi-pw.md"]},"/continuous-prevention/"],locales:{"/":{selectLanguageName:"English"}},navbar:[],darkMode:!0,repo:null,selectLanguageText:"Languages",selectLanguageAriaLabel:"Select language",sidebarDepth:2,editLink:!0,editLinkText:"Edit this page",lastUpdated:!0,lastUpdatedText:"Last Updated",contributors:!0,contributorsText:"Contributors",notFound:["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],backToHome:"Take me home",openInNewWindow:"open in new window",toggleDarkMode:"toggle dark mode",toggleSidebar:"toggle sidebar"},Zi=Ce(xf),Cf=()=>Zi;go.webpackHot&&(__VUE_HMR_RUNTIME__.updateThemeData=e=>{Zi.value=e});const Gi=Symbol(""),Rf=()=>{const e=Ae(Gi);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},Sf=(e,t)=>{var n;return Xe(Xe({},e),(n=e.locales)===null||n===void 0?void 0:n[t])};var Of=rr(({app:e})=>{const t=Cf(),n=e._context.provides[ho],r=ve(()=>Sf(t.value,n.value));e.provide(Gi,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})});const Lf=We({props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup(e){return(t,n)=>(Gn(),yi("span",{class:gn(["badge",e.type]),style:Kn({verticalAlign:e.vertical})},[Pi(t.$slots,"default",{},()=>[oo(gl(e.text),1)])],6))}});var If=We({name:"CodeGroup",setup(e,{slots:t}){const n=Ce(-1),r=Ce([]),o=(l=n.value)=>{l{l>0?n.value=l-1:n.value=r.value.length-1,r.value[n.value].focus()},i=(l,a)=>{l.key===" "||l.key==="Enter"?(l.preventDefault(),n.value=a):l.key==="ArrowRight"?(l.preventDefault(),o(a)):l.key==="ArrowLeft"&&(l.preventDefault(),s(a))};return()=>{var l;const a=(((l=t.default)===null||l===void 0?void 0:l.call(t))||[]).filter(c=>c.type.name==="CodeGroupItem").map(c=>(c.props===null&&(c.props={}),c));return a.length===0?null:(n.value<0||n.value>a.length-1?(n.value=a.findIndex(c=>c.props.active===""||c.props.active===!0),n.value===-1&&(n.value=0)):a.forEach((c,f)=>{c.props.active=f===n.value}),ge("div",{class:"code-group"},[ge("div",{class:"code-group__nav"},ge("ul",{class:"code-group__ul"},a.map((c,f)=>{const p=f===n.value;return ge("li",{class:"code-group__li"},ge("button",{ref:d=>{d&&(r.value[f]=d)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":p},ariaPressed:p,ariaExpanded:p,onClick:()=>n.value=f,onKeydown:d=>i(d,f)},c.props.title))}))),a]))}}});const Df=["aria-selected"],kf=We({name:"CodeGroupItem"}),Mf=We(An(Xe({},kf),{props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup(e){return(t,n)=>(Gn(),yi("div",{class:gn(["code-group-item",{"code-group-item__active":e.active}]),"aria-selected":e.active},[Pi(t.$slots,"default")],10,Df))}}));function el(e){return Cl()?(Rl(e),!0):!1}const wn=typeof window!="undefined",Nf=e=>typeof e=="string",vr=()=>{};function Hf(e,t){function n(...r){e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})}return n}const jf=e=>e();var bs=Object.getOwnPropertySymbols,Ff=Object.prototype.hasOwnProperty,Vf=Object.prototype.propertyIsEnumerable,$f=(e,t)=>{var n={};for(var r in e)Ff.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&bs)for(var r of bs(e))t.indexOf(r)<0&&Vf.call(e,r)&&(n[r]=e[r]);return n};function zf(e,t,n={}){const r=n,{eventFilter:o=jf}=r,s=$f(r,["eventFilter"]);return et(e,Hf(o,t),s)}function Bf(e,t=!0){xi()?rt(e):t?e():Qr(e)}const qn=wn?window:void 0;wn&&window.document;wn&&window.navigator;wn&&window.location;function Uf(...e){let t,n,r,o;if(Nf(e[0])?([n,r,o]=e,t=qn):[t,n,r,o]=e,!t)return vr;let s=vr;const i=et(()=>Tt(t),a=>{s(),a&&(a.addEventListener(n,r,o),s=()=>{a.removeEventListener(n,r,o),s=vr})},{immediate:!0,flush:"post"}),l=()=>{i(),s()};return el(l),l}function Wf(e,t={}){const{window:n=qn}=t;let r;const o=Ce(!1),s=()=>{!n||(r||(r=n.matchMedia(e)),o.value=r.matches)};return Bf(()=>{s(),r&&("addEventListener"in r?r.addEventListener("change",s):r.addListener(s),el(()=>{"removeEventListener"in s?r.removeEventListener("change",s):r.removeListener(s)}))}),o}const Mr=typeof globalThis!="undefined"?globalThis:typeof window!="undefined"?window:typeof global!="undefined"?global:typeof self!="undefined"?self:{},Nr="__vueuse_ssr_handlers__";Mr[Nr]=Mr[Nr]||{};const qf=Mr[Nr];function Kf(e,t){return qf[e]||t}function Jf(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"||Array.isArray(e)?"object":Number.isNaN(e)?"any":"number"}const Yf={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))}};function Xf(e,t,n,r={}){var o;const{flush:s="pre",deep:i=!0,listenToStorageChanges:l=!0,writeDefaults:a=!0,shallow:c,window:f=qn,eventFilter:p,onError:d=A=>{console.error(A)}}=r,E=Tt(t),h=Jf(E),b=(c?Ks:Ce)(t),v=(o=r.serializer)!=null?o:Yf[h];if(!n)try{n=Kf("getDefaultStorage",()=>{var A;return(A=qn)==null?void 0:A.localStorage})()}catch(A){d(A)}function g(A){if(!(!n||A&&A.key!==e))try{const R=A?A.newValue:n.getItem(e);R==null?(b.value=E,a&&E!==null&&n.setItem(e,v.write(E))):typeof R!="string"?b.value=R:b.value=v.read(R)}catch(R){d(R)}}return g(),f&&l&&Uf(f,"storage",A=>setTimeout(()=>g(A),0)),n&&zf(b,()=>{try{b.value==null?n.removeItem(e):n.setItem(e,v.write(b.value))}catch(A){d(A)}},{flush:s,deep:i,eventFilter:p}),b}function Qf(e){return Wf("(prefers-color-scheme: dark)",e)}var ys,Es;wn&&(window==null?void 0:window.navigator)&&((ys=window==null?void 0:window.navigator)==null?void 0:ys.platform)&&/iP(ad|hone|od)/.test((Es=window==null?void 0:window.navigator)==null?void 0:Es.platform);var Zf=Object.defineProperty,ws=Object.getOwnPropertySymbols,Gf=Object.prototype.hasOwnProperty,ed=Object.prototype.propertyIsEnumerable,As=(e,t,n)=>t in e?Zf(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,td=(e,t)=>{for(var n in t||(t={}))Gf.call(t,n)&&As(e,n,t[n]);if(ws)for(var n of ws(t))ed.call(t,n)&&As(e,n,t[n]);return e};const nd={top:0,left:0,bottom:0,right:0,height:0,width:0};td({text:""},nd);const tl=Symbol(""),Zd=()=>{const e=Ae(tl);if(!e)throw new Error("useDarkMode() is called without provider.");return e},rd=()=>{const e=sl(),t=Qf(),n=Xf("vuepress-color-scheme","auto"),r=ve({get(){return e.value.darkMode?n.value==="auto"?t.value:n.value==="dark":!1},set(o){o===t.value?n.value="auto":n.value=o?"dark":"light"}});xt(tl,r),od(r)},od=e=>{const t=(n=e.value)=>{const r=window==null?void 0:window.document.querySelector("html");r==null||r.classList.toggle("dark",n)};rt(()=>{et(e,t,{immediate:!0})}),no(()=>t())},nl=(...e)=>{const n=uo().resolve(...e),r=n.matched[n.matched.length-1];if(!(r!=null&&r.redirect))return n;const{redirect:o}=r,s=te(o)?o(n):o,i=he(s)?{path:s}:s;return nl(Xe({hash:n.hash,query:n.query,params:n.params},i))},sd=e=>{const t=nl(e);return{text:t.meta.title||e,link:t.name==="404"?e:t.fullPath}};let gr=null,en=null;const id={wait:()=>gr,pending:()=>{gr=new Promise(e=>en=e)},resolve:()=>{en==null||en(),gr=null,en=null}},ld=()=>id,rl=Symbol("sidebarItems"),Gd=()=>{const e=Ae(rl);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},ad=()=>{const e=sl(),t=Ju(),n=ve(()=>cd(t.value,e.value));xt(rl,n)},cd=(e,t)=>{var n,r,o,s;const i=(r=(n=e.sidebar)!==null&&n!==void 0?n:t.sidebar)!==null&&r!==void 0?r:"auto",l=(s=(o=e.sidebarDepth)!==null&&o!==void 0?o:t.sidebarDepth)!==null&&s!==void 0?s:2;return e.home||i===!1?[]:i==="auto"?fd(l):X(i)?ol(i,l):Xi(i)?dd(i,l):[]},ud=(e,t)=>({text:e.title,link:`#${e.slug}`,children:vo(e.children,t)}),vo=(e,t)=>t>0?e.map(n=>ud(n,t-1)):[],fd=e=>{const t=En();return[{text:t.value.title,children:vo(t.value.headers,e)}]},ol=(e,t)=>{const n=fo(),r=En(),o=s=>{var i;let l;if(he(s)?l=sd(s):l=s,l.children)return An(Xe({},l),{children:l.children.map(a=>o(a))});if(l.link===n.path){const a=((i=r.value.headers[0])===null||i===void 0?void 0:i.level)===1?r.value.headers[0].children:r.value.headers;return An(Xe({},l),{children:vo(a,t)})}return l};return e.map(s=>o(s))},dd=(e,t)=>{var n;const r=fo(),o=Qi(e,r.path),s=(n=e[o])!==null&&n!==void 0?n:[];return ol(s,t)},sl=()=>Rf();var hd=rr(({app:e,router:t})=>{e.component("Badge",Lf),e.component("CodeGroup",If),e.component("CodeGroupItem",Mf),e.component("NavbarSearch",()=>{const r=e.component("Docsearch")||e.component("SearchBox");return r?ge(r):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...r)=>(await ld().wait(),n(...r))});const md=[hf,Tf,Of,hd];function il(e,t,n){var r,o,s;t===void 0&&(t=50),n===void 0&&(n={});var i=(r=n.isImmediate)!=null&&r,l=(o=n.callback)!=null&&o,a=n.maxWait,c=Date.now(),f=[];function p(){if(a!==void 0){var E=Date.now()-c;if(E+t>=a)return a-E}return t}var d=function(){var E=[].slice.call(arguments),h=this;return new Promise(function(b,v){var g=i&&s===void 0;if(s!==void 0&&clearTimeout(s),s=setTimeout(function(){if(s=void 0,c=Date.now(),!i){var R=e.apply(h,E);l&&l(R),f.forEach(function(O){return(0,O.resolve)(R)}),f=[]}},p()),g){var A=e.apply(h,E);return l&&l(A),b(A)}f.push({resolve:b,reject:v})})};return d.cancel=function(E){s!==void 0&&clearTimeout(s),f.forEach(function(h){return(0,h.reject)(E)}),f=[]},d}const Ps=()=>window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,pd=()=>window.scrollTo({top:0,behavior:"smooth"});const vd=We({name:"BackToTop",setup(){const e=Ce(0),t=ve(()=>e.value>300),n=il(()=>{e.value=Ps()},100);rt(()=>{e.value=Ps(),window.addEventListener("scroll",()=>n())});const r=ge("div",{class:"back-to-top",onClick:pd});return()=>ge(lo,{name:"back-to-top"},()=>t.value?r:null)}}),gd=[vd],_d=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const o=uo(),s=En(),l=il(()=>{var a,c,f,p;const d=Array.from(document.querySelectorAll(e)),h=Array.from(document.querySelectorAll(t)).filter(R=>d.some(O=>O.hash===R.hash)),b=Math.max(window.pageYOffset,document.documentElement.scrollTop,document.body.scrollTop),v=window.innerHeight+b,g=Math.max(document.documentElement.scrollHeight,document.body.scrollHeight),A=Math.abs(g-v)=((c=(a=O.parentElement)===null||a===void 0?void 0:a.offsetTop)!==null&&c!==void 0?c:0)-r,w=!j||b<((p=(f=j.parentElement)===null||f===void 0?void 0:f.offsetTop)!==null&&p!==void 0?p:0)-r;if(!(B||x&&w))continue;const U=decodeURIComponent(o.currentRoute.value.hash),Y=decodeURIComponent(O.hash);if(U===Y)return;if(A){for(let y=R+1;y{l(),window.addEventListener("scroll",l)}),to(()=>{window.removeEventListener("scroll",l)}),et(()=>s.value.path,l)},bd=async(e,...t)=>{const{scrollBehavior:n}=e.options;e.options.scrollBehavior=void 0,await e.replace(...t).finally(()=>e.options.scrollBehavior=n)},yd="a.sidebar-item",Ed=".header-anchor",wd=300,Ad=5;var Pd=po(()=>{_d({headerLinkSelector:yd,headerAnchorSelector:Ed,delay:wd,offset:Ad})}),Td=typeof globalThis!="undefined"?globalThis:typeof window!="undefined"?window:typeof global!="undefined"?global:typeof self!="undefined"?self:{},Dn={exports:{}};/* NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT */(function(e,t){(function(n,r){e.exports=r()})(Td,function(){var n={};n.version="0.2.0";var r=n.settings={minimum:.08,easing:"ease",positionUsing:"",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,showSpinner:!0,barSelector:'[role="bar"]',spinnerSelector:'[role="spinner"]',parent:"body",template:'
'};n.configure=function(h){var b,v;for(b in h)v=h[b],v!==void 0&&h.hasOwnProperty(b)&&(r[b]=v);return this},n.status=null,n.set=function(h){var b=n.isStarted();h=o(h,r.minimum,1),n.status=h===1?null:h;var v=n.render(!b),g=v.querySelector(r.barSelector),A=r.speed,R=r.easing;return v.offsetWidth,l(function(O){r.positionUsing===""&&(r.positionUsing=n.getPositioningCSS()),a(g,i(h,A,R)),h===1?(a(v,{transition:"none",opacity:1}),v.offsetWidth,setTimeout(function(){a(v,{transition:"all "+A+"ms linear",opacity:0}),setTimeout(function(){n.remove(),O()},A)},A)):setTimeout(O,A)}),this},n.isStarted=function(){return typeof n.status=="number"},n.start=function(){n.status||n.set(0);var h=function(){setTimeout(function(){!n.status||(n.trickle(),h())},r.trickleSpeed)};return r.trickle&&h(),this},n.done=function(h){return!h&&!n.status?this:n.inc(.3+.5*Math.random()).set(1)},n.inc=function(h){var b=n.status;return b?(typeof h!="number"&&(h=(1-b)*o(Math.random()*b,.1,.95)),b=o(b+h,0,.994),n.set(b)):n.start()},n.trickle=function(){return n.inc(Math.random()*r.trickleRate)},function(){var h=0,b=0;n.promise=function(v){return!v||v.state()==="resolved"?this:(b===0&&n.start(),h++,b++,v.always(function(){b--,b===0?(h=0,n.done()):n.set((h-b)/h)}),this)}}(),n.render=function(h){if(n.isRendered())return document.getElementById("nprogress");f(document.documentElement,"nprogress-busy");var b=document.createElement("div");b.id="nprogress",b.innerHTML=r.template;var v=b.querySelector(r.barSelector),g=h?"-100":s(n.status||0),A=document.querySelector(r.parent),R;return a(v,{transition:"all 0 linear",transform:"translate3d("+g+"%,0,0)"}),r.showSpinner||(R=b.querySelector(r.spinnerSelector),R&&E(R)),A!=document.body&&f(A,"nprogress-custom-parent"),A.appendChild(b),b},n.remove=function(){p(document.documentElement,"nprogress-busy"),p(document.querySelector(r.parent),"nprogress-custom-parent");var h=document.getElementById("nprogress");h&&E(h)},n.isRendered=function(){return!!document.getElementById("nprogress")},n.getPositioningCSS=function(){var h=document.body.style,b="WebkitTransform"in h?"Webkit":"MozTransform"in h?"Moz":"msTransform"in h?"ms":"OTransform"in h?"O":"";return b+"Perspective"in h?"translate3d":b+"Transform"in h?"translate":"margin"};function o(h,b,v){return hv?v:h}function s(h){return(-1+h)*100}function i(h,b,v){var g;return r.positionUsing==="translate3d"?g={transform:"translate3d("+s(h)+"%,0,0)"}:r.positionUsing==="translate"?g={transform:"translate("+s(h)+"%,0)"}:g={"margin-left":s(h)+"%"},g.transition="all "+b+"ms "+v,g}var l=function(){var h=[];function b(){var v=h.shift();v&&v(b)}return function(v){h.push(v),h.length==1&&b()}}(),a=function(){var h=["Webkit","O","Moz","ms"],b={};function v(O){return O.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(j,B){return B.toUpperCase()})}function g(O){var j=document.body.style;if(O in j)return O;for(var B=h.length,x=O.charAt(0).toUpperCase()+O.slice(1),w;B--;)if(w=h[B]+x,w in j)return w;return O}function A(O){return O=v(O),b[O]||(b[O]=g(O))}function R(O,j,B){j=A(j),O.style[j]=B}return function(O,j){var B=arguments,x,w;if(B.length==2)for(x in j)w=j[x],w!==void 0&&j.hasOwnProperty(x)&&R(O,x,w);else R(O,B[1],B[2])}}();function c(h,b){var v=typeof h=="string"?h:d(h);return v.indexOf(" "+b+" ")>=0}function f(h,b){var v=d(h),g=v+b;c(v,b)||(h.className=g.substring(1))}function p(h,b){var v=d(h),g;!c(h,b)||(g=v.replace(" "+b+" "," "),h.className=g.substring(1,g.length-1))}function d(h){return(" "+(h.className||"")+" ").replace(/\s+/gi," ")}function E(h){h&&h.parentNode&&h.parentNode.removeChild(h)}return n})})(Dn);const xd=()=>{rt(()=>{const e=uo(),t=new Set;t.add(e.currentRoute.value.path),Dn.exports.configure({showSpinner:!1}),e.beforeEach(n=>{t.has(n.path)||Dn.exports.start()}),e.afterEach(n=>{t.add(n.path),Dn.exports.done()})})};var Cd=po(()=>{xd()}),Rd=po(()=>{rd(),ad()});const Sd=[Pd,Cd,Rd],Od=[["v-8daa1a0e","/",{title:""},["/index.html","/README.md"]],["v-638c1d18","/advanced/",{title:"3- Advanced Threats"},["/advanced/index.html","/advanced/README.md"]],["v-4a3aeb3f","/common-threats/",{title:"2- Common Threats"},["/common-threats/index.html","/common-threats/README.md"]],["v-5e4edb7a","/continuous-prevention/",{title:"4. Global recommendations"},["/continuous-prevention/index.html","/continuous-prevention/README.md"]],["v-0b15ef8c","/csp/csp-angular.html",{title:"4.3 CSP in Angular"},["/csp/csp-angular","/csp/csp-angular.md"]],["v-1f53b9ee","/csp/csp-defense.html",{title:"4.2 CSP Defense"},["/csp/csp-defense","/csp/csp-defense.md"]],["v-6d19a24b","/csp/csp-overview.html",{title:"4.1 CSP Overview"},["/csp/csp-overview","/csp/csp-overview.md"]],["v-3617bebd","/csp/csp-pw.html",{title:"4.4 CSP Practical Work"},["/csp/csp-pw","/csp/csp-pw.md"]],["v-712e14fc","/introduction/",{title:"1 Introduction"},["/introduction/index.html","/introduction/README.md"]],["v-6096668b","/prerequisites/",{title:"Prerequisites"},["/prerequisites/index.html","/prerequisites/README.md"]],["v-5c6f2e35","/advanced/api/api-defense.html",{title:"7.2 Unprotected API Defense"},["/advanced/api/api-defense","/advanced/api/api-defense.md"]],["v-59269238","/advanced/api/api-overview.html",{title:"7.1 Unprotected APIs Overview"},["/advanced/api/api-overview","/advanced/api/api-overview.md"]],["v-69cf0bd4","/advanced/api/api-pw.html",{title:"7.3 Unprotected API Practical Work"},["/advanced/api/api-pw","/advanced/api/api-pw.md"]],["v-c8cf272e","/advanced/xssi/xssi-angular.html",{title:"8.3 XSSI protection in Angular"},["/advanced/xssi/xssi-angular","/advanced/xssi/xssi-angular.md"]],["v-a053926a","/advanced/xssi/xssi-defense.html",{title:"8.2 XSSI Defense"},["/advanced/xssi/xssi-defense","/advanced/xssi/xssi-defense.md"]],["v-26327de4","/advanced/xssi/xssi-overview.html",{title:"8.1 XSSI Overview"},["/advanced/xssi/xssi-overview","/advanced/xssi/xssi-overview.md"]],["v-3a702880","/advanced/xssi/xssi-pw.html",{title:"8.4 XSSI Practical Work"},["/advanced/xssi/xssi-pw","/advanced/xssi/xssi-pw.md"]],["v-33ffe414","/common-threats/csrf/csrf-angular.html",{title:"2.4 CSRF Protection in Angular"},["/common-threats/csrf/csrf-angular","/common-threats/csrf/csrf-angular.md"]],["v-483dae76","/common-threats/csrf/csrf-defense.html",{title:"2.3 CSRF Defense Best Practices"},["/common-threats/csrf/csrf-defense","/common-threats/csrf/csrf-defense.md"]],["v-768c5bde","/common-threats/csrf/csrf-detection.html",{title:"2.2 CSRF Detection"},["/common-threats/csrf/csrf-detection","/common-threats/csrf/csrf-detection.md"]],["v-616e3ec3","/common-threats/csrf/csrf-overview.html",{title:"2.1 CSRF Overview"},["/common-threats/csrf/csrf-overview","/common-threats/csrf/csrf-overview.md"]],["v-894dd596","/common-threats/csrf/csrf-pw.html",{title:"2.5 CSRF Practical Work"},["/common-threats/csrf/csrf-pw","/common-threats/csrf/csrf-pw.md"]],["v-6cd88d9e","/common-threats/jwt/jwt-best-current-practices.html",{title:"1.5 JWT Best Current Practices"},["/common-threats/jwt/jwt-best-current-practices","/common-threats/jwt/jwt-best-current-practices.md"]],["v-ef5efe92","/common-threats/jwt/jwt-known-threats.html",{title:"1.4 JWT Known Threats"},["/common-threats/jwt/jwt-known-threats","/common-threats/jwt/jwt-known-threats.md"]],["v-107f7b53","/common-threats/jwt/jwt-overview.html",{title:"1.1 JWT Overview"},["/common-threats/jwt/jwt-overview","/common-threats/jwt/jwt-overview.md"]],["v-6cf6fdc5","/common-threats/jwt/jwt-pw.html",{title:"1.6 JWT Practical Work"},["/common-threats/jwt/jwt-pw","/common-threats/jwt/jwt-pw.md"]],["v-746e588b","/common-threats/jwt/jwt-storage.html",{title:"1.3 JWT Storage"},["/common-threats/jwt/jwt-storage","/common-threats/jwt/jwt-storage.md"]],["v-34b20ccd","/common-threats/jwt/jwt-workflow.html",{title:"1.2 JWT Workflow"},["/common-threats/jwt/jwt-workflow","/common-threats/jwt/jwt-workflow.md"]],["v-78a07c70","/common-threats/sca/sca-angular.html",{title:"6.4 Vulnerable Components Protection in Angular"},["/common-threats/sca/sca-angular","/common-threats/sca/sca-angular.md"]],["v-e643725c","/common-threats/sca/sca-defense.html",{title:"6.3 Vulnerable Component Defense"},["/common-threats/sca/sca-defense","/common-threats/sca/sca-defense.md"]],["v-6395c36d","/common-threats/sca/sca-detection.html",{title:"6.2 Vulnerable Components Detection"},["/common-threats/sca/sca-detection","/common-threats/sca/sca-detection.md"]],["v-9e3e9c32","/common-threats/sca/sca-overview.html",{title:"6.1 Vulnerable Components Overview"},["/common-threats/sca/sca-overview","/common-threats/sca/sca-overview.md"]],["v-61d9b359","/common-threats/sca/sca-pw.html",{title:"6.5 Vulnerable Components Practical Work"},["/common-threats/sca/sca-pw","/common-threats/sca/sca-pw.md"]],["v-388c7174","/common-threats/ssti/ssti-angular.html",{title:"5.3 SSTI Protection in Angular"},["/common-threats/ssti/ssti-angular","/common-threats/ssti/ssti-angular.md"]],["v-4cca3bd6","/common-threats/ssti/ssti-defense.html",{title:"5.2 SSTI Defense"},["/common-threats/ssti/ssti-defense","/common-threats/ssti/ssti-defense.md"]],["v-2319453a","/common-threats/ssti/ssti-overview.html",{title:"5.1 SSTI Overview"},["/common-threats/ssti/ssti-overview","/common-threats/ssti/ssti-overview.md"]],["v-da4c55bc","/common-threats/xss/xss-angular.html",{title:"3.4 XSS Protection in Angular"},["/common-threats/xss/xss-angular","/common-threats/xss/xss-angular.md"]],["v-b1d0c0f8","/common-threats/xss/xss-defense.html",{title:"3.3 XSS Defense Best Practices"},["/common-threats/xss/xss-defense","/common-threats/xss/xss-defense.md"]],["v-564890c2","/common-threats/xss/xss-detection.html",{title:"3.2 XSS Detection"},["/common-threats/xss/xss-detection","/common-threats/xss/xss-detection.md"]],["v-445b2116","/common-threats/xss/xss-overview.html",{title:"3.1 XSS Overview"},["/common-threats/xss/xss-overview","/common-threats/xss/xss-overview.md"]],["v-bfd79932","/common-threats/xss/xss-pw.html",{title:"3.5 XSS Practical Work"},["/common-threats/xss/xss-pw","/common-threats/xss/xss-pw.md"]],["v-3706649a","/404.html",{title:""},["/404"]]],Ld=Od.reduce((e,[t,n,r,o])=>(e.push({name:t,path:n,component:vs,meta:r},...o.map(s=>({path:s,redirect:n}))),e),[{name:"404",path:"/:catchAll(.*)",component:vs}]),Id=su,Dd=()=>{const e=zu({history:Id(lf(dt.value.base)),routes:Ld,scrollBehavior:(t,n,r)=>r||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{var r;(t.path!==n.path||n===Qe)&&([Ge.value]=await Promise.all([bt.resolvePageData(t.name),(r=Bi[t.name])===null||r===void 0?void 0:r.__asyncLoader()]))}),e},kd=e=>{e.component("ClientOnly",Uu),e.component("Content",mo)},Md=(e,t)=>{const n=ve(()=>bt.resolveRouteLocale(dt.value.locales,t.currentRoute.value.path)),r=ve(()=>bt.resolveSiteLocaleData(dt.value,n.value)),o=ve(()=>bt.resolvePageFrontmatter(Ge.value)),s=ve(()=>bt.resolvePageHeadTitle(Ge.value,r.value)),i=ve(()=>bt.resolvePageHead(s.value,o.value,r.value)),l=ve(()=>bt.resolvePageLang(Ge.value));return e.provide(ho,n),e.provide(Yi,r),e.provide(qi,o),e.provide(Xu,s),e.provide(Ki,i),e.provide(Ji,l),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>o.value},$head:{get:()=>i.value},$headTitle:{get:()=>s.value},$lang:{get:()=>l.value},$page:{get:()=>Ge.value},$routeLocale:{get:()=>n.value},$site:{get:()=>dt.value},$siteLocale:{get:()=>r.value},$withBase:{get:()=>cf}}),{pageData:Ge,pageFrontmatter:o,pageHead:i,pageHeadTitle:s,pageLang:l,routeLocale:n,siteData:dt,siteLocaleData:r}},Nd=()=>{const e=fo(),t=Yu(),n=Qu(),r=Ce([]),o=()=>{t.value.forEach(i=>{const l=Hd(i);l&&r.value.push(l)})},s=()=>{document.documentElement.lang=n.value,r.value.forEach(i=>{i.parentNode===document.head&&document.head.removeChild(i)}),r.value.splice(0,r.value.length),t.value.forEach(i=>{const l=jd(i);l!==null&&(document.head.appendChild(l),r.value.push(l))})};xt(tf,s),rt(()=>{o(),s(),et(()=>e.path,()=>s())})},Hd=([e,t,n=""])=>{const r=Object.entries(t).map(([l,a])=>he(a)?`[${l}="${a}"]`:a===!0?`[${l}]`:"").join(""),o=`head > ${e}${r}`;return Array.from(document.querySelectorAll(o)).find(l=>l.innerText===n)||null},jd=([e,t,n])=>{if(!he(e))return null;const r=document.createElement(e);return Xi(t)&&Object.entries(t).forEach(([o,s])=>{he(s)?r.setAttribute(o,s):s===!0&&r.setAttribute(o,"")}),he(n)&&r.appendChild(document.createTextNode(n)),r},Fd=Fc,Vd=async()=>{const e=Fd({name:"VuepressApp",setup(){Nd();for(const n of Sd)n();return()=>[ge(zi),...gd.map(n=>ge(n))]}}),t=Dd();kd(e),Md(e,t);for(const n of md)await n({app:e,router:t,siteData:dt});return e.use(t),{app:e,router:t}};Vd().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{ef as A,Yd as B,Zd as C,ge as D,cf as E,Oe as F,Uu as G,gn as H,Ce as I,et as J,Bd as K,Jd as L,af as M,lf as N,uo as O,he as P,sd as Q,rt as R,Kn as S,lo as T,En as U,Gd as V,Xi as W,Kd as X,no as Y,ld as Z,Ai as a,oo as b,yi as c,Vd as createVueApp,Ud as d,_e as e,We as f,sl as g,Tt as h,Ju as i,ve as j,X as k,qd as l,Wd as m,fo as n,Gn as o,zd as p,Ei as q,Ya as r,tc as s,gl as t,Zu as u,Pi as v,ha as w,sf as x,Xd as y,Qd as z}; diff --git a/assets/back-to-top.8efcbe56.svg b/assets/back-to-top.8efcbe56.svg new file mode 100644 index 00000000..83236781 --- /dev/null +++ b/assets/back-to-top.8efcbe56.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/cookie-to-header.3a500906.png b/assets/cookie-to-header.3a500906.png new file mode 100644 index 00000000..ea1039c0 Binary files /dev/null and b/assets/cookie-to-header.3a500906.png differ diff --git a/assets/csp-angular.html.18184a38.js b/assets/csp-angular.html.18184a38.js new file mode 100644 index 00000000..3bd218f4 --- /dev/null +++ b/assets/csp-angular.html.18184a38.js @@ -0,0 +1 @@ +const e={key:"v-0b15ef8c",path:"/csp/csp-angular.html",title:"4.3 CSP in Angular",lang:"en-US",frontmatter:{},excerpt:"",headers:[],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"csp/csp-angular.md"};export{e as data}; diff --git a/assets/csp-angular.html.5650697a.js b/assets/csp-angular.html.5650697a.js new file mode 100644 index 00000000..040445a9 --- /dev/null +++ b/assets/csp-angular.html.5650697a.js @@ -0,0 +1,18 @@ +import{r as o,o as r,c as p,a as s,e,F as c,d as t,b as n}from"./app.14588456.js";import{_ as i}from"./plugin-vue_export-helper.21dcd24c.js";const l={},u=t(`

4.3 CSP in Angular

Angular suggests completing their built-in XSS prevention with the following:

  • Use Content-Security-Policy HTTP header: We recommend you to use strict CSP instead of allow-list CSP as it is more effective and does not need customization. You can choose to use Nonce-based strict CSP or Hash-based strict CSP.
Content-Security-Policy:
+  script-src 'nonce-{RANDOM}' 'strict-dynamic';
+  object-src 'none';
+  base-uri 'none';
+
Content-Security-Policy:
+  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
+  object-src 'none';
+  base-uri 'none';
+

Note that using these headers requires you to refactor code that is not compatible with CSP.

`,6),d=n("To learn further please check this page: "),k={href:"https://web.dev/strict-csp/",target:"_blank",rel:"noopener noreferrer"},b=n("web.dev/strict-csp/"),g=n("Use "),y={href:"https://w3c.github.io/trusted-types/dist/spec/#introduction",target:"_blank",rel:"noopener noreferrer"},h=n("Trusted Types"),m=n(" (an API that allows applications to lock down DOM-XSS injection sinks to only accept non-spoofable, typed values in place of strings) by configuring your web server to emit HTTP headers with one of the following Angular policies:"),f=t(`
  • angular This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy.

    Content-Security-Policy: 
    +    trusted-types angular; 
    +    require-trusted-types-for 'script';
    +
  • angular#unsafe-bypass - This policy is used for applications that use any of the methods in Angular DomSanitizer that bypass security, such as bypassSecurityTrustHtml. Any application that uses these methods must enable this policy.

    Content-Security-Policy: 
    +    trusted-types angular angular#unsafe-bypass; 
    +    require-trusted-types-for 'script';
    +
  • angular#unsafe-jit This policy is used by the JIT compiler. You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the platform browser dynamic.

    Content-Security-Policy: 
    +    trusted-types angular angular#unsafe-jit; 
    +    require-trusted-types-for 'script';
    +
`,1);function _(v,w){const a=o("ExternalLinkIcon");return r(),p(c,null,[u,s("p",null,[d,s("a",k,[b,e(a)])]),s("ul",null,[s("li",null,[s("p",null,[g,s("a",y,[h,e(a)]),m]),f])])],64)}var P=i(l,[["render",_]]);export{P as default}; diff --git a/assets/csp-defense.html.37265fe3.js b/assets/csp-defense.html.37265fe3.js new file mode 100644 index 00000000..8ad6a0b2 --- /dev/null +++ b/assets/csp-defense.html.37265fe3.js @@ -0,0 +1 @@ +const e={key:"v-1f53b9ee",path:"/csp/csp-defense.html",title:"4.2 CSP Defense",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"What are the risks of CSP ?",slug:"what-are-the-risks-of-csp",children:[]},{level:2,title:"How to implement CSP in real life ?",slug:"how-to-implement-csp-in-real-life",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"csp/csp-defense.md"};export{e as data}; diff --git a/assets/csp-defense.html.b1f2da71.js b/assets/csp-defense.html.b1f2da71.js new file mode 100644 index 00000000..e8910116 --- /dev/null +++ b/assets/csp-defense.html.b1f2da71.js @@ -0,0 +1 @@ +import{r as s,o as r,c as a,a as t,e as n,F as i,d as c,b as e}from"./app.14588456.js";import{_ as l}from"./plugin-vue_export-helper.21dcd24c.js";const h={},d=c('

4.2 CSP Defense

What are the risks of CSP ?

As shown in the overview section, there are many policies available to setup and maintain for your CSP. This raises a high risk of policies misconfiguration which can lead to unavailability of some features of your application. In this situation, teams become too lazy to bother about maintaining the policies and fall in the risk of using too permissive policies.

How to implement CSP in real life ?

For sensitive web applications, you want to make sure that only the resources you've written yourself can be loaded with CSP. In case of whitelist strategy, you can use a lockdown approach by starting with a default policy that blocks absolutely everything : default-src 'none' and build up your white list from there. In case of strict-dynamic strategy, you need to refactor your javascript code, include nonce generation in your application and add the nonce attribute to all your script tags.

',5),p=e("In either case, it is highly recommended testing your CSP in report only mode("),u=t("code",null,"report-uri",-1),_=e(" or "),f=t("code",null,"report-to",-1),g=e(") during a certain period first and resolve the alerts before applying the CSP. Also, "),m=t("strong",null,"use the same CSP in all your environments (tests and production)",-1),y=e(" in order to avoid incompatibility incidents. "),b={href:"https://github.com/nico3333fr/CSP-useful/blob/master/csp-wtf/explained.md",target:"_blank",rel:"noopener noreferrer"},w=e("This page"),k=e(" can help you analyse CSP notifications that may be difficult to understand."),v=e("To learn further: "),P={href:"https://owasp.org/www-pdf-archive//2019-02-22_-_How_do_I_Content_Security_Policy_-_Print.pdf",target:"_blank",rel:"noopener noreferrer"},S=e("OWASP CSP setup guide book");function C(x,I){const o=s("ExternalLinkIcon");return r(),a(i,null,[d,t("p",null,[p,u,_,f,g,m,y,t("a",b,[w,n(o)]),k]),t("p",null,[v,t("a",P,[S,n(o)])])],64)}var V=l(h,[["render",C]]);export{V as default}; diff --git a/assets/csp-overview.html.d5a381b9.js b/assets/csp-overview.html.d5a381b9.js new file mode 100644 index 00000000..878debe0 --- /dev/null +++ b/assets/csp-overview.html.d5a381b9.js @@ -0,0 +1,10 @@ +import{r as o,o as r,c,a as s,e as t,F as i,d as a,b as e}from"./app.14588456.js";import{_ as p}from"./plugin-vue_export-helper.21dcd24c.js";var l="/angular-security-training/assets/csp-wf.42e4bd18.png",d="/angular-security-training/assets/csp-strict-dynamic.65a61b8a.png";const u={},h=a('

4.1 CSP Overview

csp-workflow

Content Security Policy (CSP) is a W3C standard introduced to prevent cross-site-scripting, click-jacking and other code injection attacks resulting of malicious inputs in the trusted web page context. CSP defines a standard methods for web application developers to declare approved origins of content that browsers should allow loading.

CSP is a new browser security policy :

  • Defines what it is allowed to happen in a page
  • Delivered by the server in a response header or meta tag

imageimageimage

WARNING

CSP is not intended as a first level of defense against code injection attacks like XSS. CSP is best used as defense-in-depth. It reduces the risk that malicious injection can cause, but it is not a replacement for careful input validation and output encoding.

Implementation

To enable CSP, you need to configure your web application to return the Content-Security-Policy HTTP header.

image

CSP evolution to strict CSP

White-list CSP properties requires a lot of customization and maintenance.

CSP evolution with script-src directive :

image

A nonce is a random number generated for single usage to mark a <script> tag as trusted.

WARNING

Browsers still not support all CSP version 3 features. Most recent browsers version support CSP version 2.

CSP common directives

  • default-src : Default property if no other attributes are defined. In most cases, the value of this property is default-src 'self' meaning the browser can only upload resources from the current website.

  • script-src : Defines locations from which external scripts can be loaded. If your web application does not use client-side scripting, set the value to script-src 'none'.

  • img-src : Defines locations from which images can be retrieved.

  • media-src : Defines locations from which rich media like video can be retrieved.

  • object-src : Defines locations from which plugins can be retrieved.

  • manifest-src : Defines locations from which application manifests can be retrieved.

  • frame-ancestors : Defines locations from which another web page can be loaded using a frame, iframe, object, embed, or applet element.

  • form-action : Specifies URLs that can be used as part of an action in a <form> tag, meaning the browser restricts where form results can be sent. The form action does not revert to default-src, so this is a mandatory property if you are using form elements on your site.

  • plugin-types : Identifies the set of plugins that can be invoked via objects, embeds, or applets, defined using MIME types.

  • base-uri : Allows URLs in the src attribute of any tag.

  • report-uri: Instructs the browser to POST a reports of policy failures to this URI. You can also use Content-Security-Policy-Report-Only as the HTTP header name to instruct the browser to only send reports (does not block anything). This directive is deprecated in CSP Level 3 in favor of the report-to directive.

',18),m={class:"custom-container warning"},g=s("p",{class:"custom-container-title"},"WARNING",-1),f=e("As of today, some CSP properties "),k=s("strong",null,"may not be supported by all browsers",-1),b=e(". IE is known to have limited support for CSP. You can verify the compatibility of CSP properties with browser with several online tools like this one: "),w={href:"https://caniuse.com/?search=csp",target:"_blank",rel:"noopener noreferrer"},v=e("https://caniuse.com/?search=csp"),_=a('

CSP Common Source List Values

  • *: Wildcard, allows any URL except data: blob: filesystem: schemes
  • none: Prevents loading resources from any source, even from your own!
  • self : Allows loading resources from the same origin (same scheme, host and port).
  • unsafe-inline : Allows use of inline source elements such as style attribute, onclick, or script tag bodies and javascript: URIs. This value is as it says : unsafe !
  • nonce-rAnd0m : Allows an inline script or CSS to execute if the script (eg: <script nonce="rAnd0m">) tag contains a nonce attribute matching the nonce specified in the CSP header. The nonce should be a secure random string, and should not be reused.
  • strict-dynamic : Allows scripts to be included by any script already marked by a nonce or hash. Disables the whitelist and allows backward compatibility with CSP v1 and v2 as shown bellow: csp-workflow

Common white-list CSP example

Content-Security-Policy: 
+  default-src 'self'; 
+  style-src 'unsafe-inline' 'self' https://fonts.googleapis.com https://themes.googleusercontent.com;
+  frame-src https://www.slideshare.net www.youtube.com twitter.com; 
+  object-src 'none'; 
+  font-src 'self' data: https://themes.googleusercontent.com https://fonts.googleapis.com; 
+  script-src 'strict-dynamic' 'nonce-rAnd0m123' 'unsafe-inline' 'self' https://www.google.com twitter.com https://themes.googleusercontent.com;
+  base-uri 'none'; 
+  img-src 'self' https://www.google.com data: https://pbs.twimg.com https://img.youtube.com twitter.com
+
`,4),y={class:"custom-container tip"},S=s("p",{class:"custom-container-title"},"TIP",-1),C={href:"https://csper.io/evaluator",target:"_blank",rel:"noopener noreferrer"},P=e("This online tool"),x=e(" or "),I={href:"https://csp-evaluator.withgoogle.com/",target:"_blank",rel:"noopener noreferrer"},T=e("this one"),A=e(" can help you evaluate the strength of your CSP."),D=e("To learn further about CSP, don't hesitate to check "),N={href:"https://content-security-policy.com/",target:"_blank",rel:"noopener noreferrer"},R=e("there Quick Reference Guide"),j=e(".");function L(W,E){const n=o("ExternalLinkIcon");return r(),c(i,null,[h,s("div",m,[g,s("p",null,[f,k,b,s("a",w,[v,t(n)])])]),_,s("div",y,[S,s("p",null,[s("a",C,[P,t(n)]),x,s("a",I,[T,t(n)]),A])]),s("p",null,[D,s("a",N,[R,t(n)]),j])],64)}var B=p(u,[["render",L]]);export{B as default}; diff --git a/assets/csp-overview.html.f6add21f.js b/assets/csp-overview.html.f6add21f.js new file mode 100644 index 00000000..7a9d503f --- /dev/null +++ b/assets/csp-overview.html.f6add21f.js @@ -0,0 +1 @@ +const e={key:"v-6d19a24b",path:"/csp/csp-overview.html",title:"4.1 CSP Overview",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Implementation",slug:"implementation",children:[]},{level:2,title:"CSP evolution to strict CSP",slug:"csp-evolution-to-strict-csp",children:[]},{level:2,title:"CSP common directives",slug:"csp-common-directives",children:[]},{level:2,title:"CSP Common Source List Values",slug:"csp-common-source-list-values",children:[]},{level:2,title:"Common white-list CSP example",slug:"common-white-list-csp-example",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"csp/csp-overview.md"};export{e as data}; diff --git a/assets/csp-pw.html.093d9e16.js b/assets/csp-pw.html.093d9e16.js new file mode 100644 index 00000000..9dbc7c56 --- /dev/null +++ b/assets/csp-pw.html.093d9e16.js @@ -0,0 +1 @@ +const e={key:"v-3617bebd",path:"/csp/csp-pw.html",title:"4.4 CSP Practical Work",lang:"en-US",frontmatter:{},excerpt:"",headers:[{level:2,title:"Part 1",slug:"part-1",children:[]},{level:2,title:"Part 2",slug:"part-2",children:[]}],git:{updatedTime:1718353804e3,contributors:[{name:"Nourredine K",email:"nourredine.k@gmail.com",commits:1}]},filePathRelative:"csp/csp-pw.md"};export{e as data}; diff --git a/assets/csp-pw.html.8a2f6807.js b/assets/csp-pw.html.8a2f6807.js new file mode 100644 index 00000000..de2a95ca --- /dev/null +++ b/assets/csp-pw.html.8a2f6807.js @@ -0,0 +1,183 @@ +import{r as e,o,c,a as n,e as p,F as l,d as a,b as s}from"./app.14588456.js";import{_ as r}from"./pw-coding.b255a857.js";import{_ as i}from"./plugin-vue_export-helper.21dcd24c.js";const u={},k=a('

4.4 CSP Practical Work

Part 1

pw

Practical Work Web-Application directory : pw/pw-csp

1 - Starter Content Security Policy

',5),b=a('
  • Configure a minimalistic CSP in angular side (index.html) with the following value and observe the result (see console log) : default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';

    • Hints :
      • This policy allows images, scripts, AJAX, and CSS from the same origin, and does not allow any other resources to load (eg inline scripting, inline styles, object, frame, media, etc). It is a good starting point but often too restrictive for many existing sites
      • You can declare your CSP as a meta tag with http-equiv directive in index.html :<meta http-equiv="__directive__" content="__value__">
  • Update step by step the configuration in order to make the website load properly (Content-Security-Policy-Report-Only header)

    • Hint : add unsafe-inline or unsafe-eval wherever needed to remove console alerts
  • ',2),m=s("Check the security level of this CSP on "),d={href:"https://csp-evaluator.withgoogle.com/",target:"_blank",rel:"noopener noreferrer"},g=s("csp-evaluator.withgoogle.com/"),h=a("
  • Declare the CSP server-side

    • Hints :
      • Use the spring security API HttpSecurity : HttpSecurity#headers()#contentSecurityPolicy("...")
      • Set the CSP configuration in /server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
      • (if necessary) Bypass the angular-cli proxy in order to use the server-side CSP configuration (ng build, then use http://localhost:8080/#/home)
  • ",1),y=n("p",null,"2 - Configure a CSP 3",-1),f=n("p",null,"Use CSP to secure your app against inline scripting",-1),w=n("li",null,[n("p",null,[s("In "),n("code",null,"index.html"),s(", declare an arbitrary inline scripting : "),n("code",null,` + 2.4 CSRF Protection in Angular | Angular Security Training + + + + +

    2.4 CSRF Protection in Angular

    What are the defense mechanisms against CSRF available in Angular ?

    Built-in prevention

    Angular has a built-in CSRF prevention mechanism called cookie-to-header token. It is a mix of 2 protection patterns : "Double submit cookie" and "Custom header"

    1. During authentication, the server sends a random CSRF token in a cookie XSRF-TOKEN.
    2. Angular reads the token from the cookie XSRF-TOKEN.
    3. Angular puts token in request header X-XSRF-TOKEN.
    4. For each request, the browser sends the cookie and the request header.
    5. Server extract and compares both tokens received from the client.
    6. Server decides to verify the action only if the tokens match.

    cookie-to-header

    WARNING

    CSRF prevention needs to be implemented in both your server (back-end) side, and your client (front-end) side.

    As Angular provides solution as a client, you must investigate your back-end framework in order to generate and handle the random CSRF token.

    Configuring CSRF protection in Angular

    Angular HttpClientModule has a built-in CSRF protection enabled by default.

    For a server that supports a cookie-based CSRF protection system, use HttpClientXsrfModule (in the import section of the module where your components are declared) to configure the XSRF protection (cookie name, header name, ...) or to disable it.

    imports:[
    +HttpClientModule,
    +HttpClientXsrfModule.disable()
    +]
    +

    By default, your application will automatically send the cookie cookieName: 'XSRF-TOKEN', and the header headerName: 'X-XSRF-TOKEN' for each request sent from this component. If you want to customize the setting (to match server's cookie name for instance) :

    imports: [
    +HttpClientModule,
    +HttpClientXsrfModule.withOptions({
    +cookieName: 'your-custom-Xsrf-Cookie',
    +headerName: 'your-custom-Xsrf-Header'
    +})
    +]
    +

    The CSRF module implements the default interceptor HttpXsrfInterceptor.

    Further resources

    owasp.org/www-community/attacks/csrfopen in new windowangular.io/guide/http#security-xsrf-protectionopen in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/csrf/csrf-defense.html b/common-threats/csrf/csrf-defense.html new file mode 100644 index 00000000..d228cf01 --- /dev/null +++ b/common-threats/csrf/csrf-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 2.3 CSRF Defense Best Practices | Angular Security Training + + + + +

    2.3 CSRF Defense Best Practices

    What are the primary defense mechanisms against CSRF ?

    Synchronizer Tokens

    • Description: The server adds a request parameter to each form, assigning the field a different random value for each session (or for each request.) The server confirms that each subsequent request has this field and that its value matches the one assigned for the session.
    • Token type: Random
    • Strategy: Stored in HTTP session and sent as request parameter.
    • Server-side validation: Session token against request parameter.
    • Description: The random token value is saved in a cookie on the client. Every subsequent request must include the same value in a request parameter. On each request the server confirms that the request parameter is present and that its value matches that in the cookie.
    • Token type: Random
    • Strategy: Sent as a cookie and request parameter.
    • Server-side validation: Cookie token against request parameter.

    Custom Request Header

    • Description: Using a custom header works as a defense because only JavaScript can create custom headers, and the browser’s single-origin policy (SOP) blocks cross-site JavaScript calls.
    • Token type: No matter.
    • Strategy: Sent as Request header.
    • Server-side validation: Check request header exists.

    Encrypted Token

    • Description: Encrypting the token value using a private key known only on the server. The encrypted value should be a time stamp. Validate subsequent requests by confirming that each contains the request parameter, that you can decrypt the field value, verify the decrypted value is a timestamp, and that the timestamp has not expired.
    • Token type: Signed/ Encrypted
    • Strategy: Sent as Request header.
    • Server-side validation: Check request header exists.

    WARNING

    XSS Attacks can defeat any CSRF protection even if CSRF protection is perfectly implemented !

    XSS attackers can retrieve your tokens from the user's browser if your application is not protected.

    Solid XSS prevention is a mandatory prerequisite for CSRF defenses.

    What are the bad misconceptions about CSRF defenses ?

    Here are some myths which unfortunately will not save your application from CSRF attacks:

    • Using a secret cookie: even secret cookies are submitted with every request The authentication tokens will be submitted in any case !
    • Only accepting POST requests: Attackers have numerous methods to trick a victim into submitting a forged POST request.
    • Multi-Step transactions: Attackers can always predict and deduce transaction steps.
    • URL rewriting: The session cookie is exposed in the url which introduces another security flaw.
    • HTTPS alone does nothing to defend against CSRF by itself.
    • http_only session cookie will not protect you from CSRF.
    • JSON APIs are also vulnerable to CSRF attacks.
    • Same-site Cookie Attribute can be useful against CSRF but not sufficient by itself because it is not support by all browsers allowing attackers to bypass it.

    What can I do to continuously prevent CSRF attacks ?

    • Use built-in CSRF protection in your Framework. Check the Security Hardening by Framework section bellow.

    • CSRF prevention needs to be done on both client (front-end) and server (back-end) side:

      • Server side prevention measures:

        • Reject requests that have mismatched CSRF cookie/header.
        • Send a randomly generated CSRF cookie to the client (cannot be http_only).
        • Use CORS to prevent cross-origin AJAX from untrusted pages.
      • Client side prevention measures:

        • Send the CSRF value for each request.
        • Send a matching header with each request.
    • Apply OWASP CSRF prevention cheat sheetopen in new window

    • Use IDE plugins code scanners in order to alert on vulnerable code while you are developing.

    • Request code reviews from acknowledged Security Reviewers in your team.

    • Scan your code with automated code analysis tools in your merge requests and prevent pushing any vulnerable code in your application.

    • Perform regular SCA scans of your dependencies and fix the vulnerabilities raised.

    • Perform regular penetration testing with DAST tools like OWASP ZAP and fix the findings.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/csrf/csrf-detection.html b/common-threats/csrf/csrf-detection.html new file mode 100644 index 00000000..7d7f329a --- /dev/null +++ b/common-threats/csrf/csrf-detection.html @@ -0,0 +1,33 @@ + + + + + + + + + 2.2 CSRF Detection | Angular Security Training + + + + + + + + diff --git a/common-threats/csrf/csrf-overview.html b/common-threats/csrf/csrf-overview.html new file mode 100644 index 00000000..c1cb12bd --- /dev/null +++ b/common-threats/csrf/csrf-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + 2.1 CSRF Overview | Angular Security Training + + + + +

    2.1 CSRF Overview

    CSRF in a nutshell

    CSRF (Cross-Site Request Forgery) is an attack where a malicious website causes a logged-on user’s web browser to perform an unwanted action on a trusted site.

    csrf-wf-1

    csrf-wf-2

    csrf-wf-3

    csrf-wf-4

    Why CSRF can be dangerous ?

    A successful CSRF attack can force the user to perform state changing requests like transferring funds, changing their email address, and so forth. If the victim is an administrator account, CSRF can compromise the entire web application.

    What are CSRF famous real life cases ?

    • TikTokopen in new window : In 2020, a vulnerability that allowed attackers to send messages containing malware to Tiktok users. The attackers could perform CSRF or cross-site scripting (XSS) attacks, causing other user accounts to submit requests on their behalf to the TikTok application.

    • Netflixopen in new window : In 2006, Netflix website had numerous vulnerabilities regarding CSRF, which could have allowed any attacker to perform actions such as adding a DVD to the victim’s rental queue, changing the shipping address on the account, or altering the victim’s login credentials to fully compromise the account.

    • ING Directopen in new window : In 2008, ING Direct, the banking website of a Dutch-owned multinational banking group, had a CSRF vulnerability that allowed attackers to transfer money from users’ accounts, even though users were authenticated with SSL. The website did not have any protection against CSRF attacks, and the process of transferring funds was easy for attackers to see and replicate.

    What makes CSRF attacks possible ?

    Browser requests automatically include all cookies including session cookies. The attackers use social engineering to trick users which browsers cannot distinguish between legitimate authorized requests and forged authenticated requests.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/csrf/csrf-pw.html b/common-threats/csrf/csrf-pw.html new file mode 100644 index 00000000..f42b8ebb --- /dev/null +++ b/common-threats/csrf/csrf-pw.html @@ -0,0 +1,33 @@ + + + + + + + + + 2.5 CSRF Practical Work | Angular Security Training + + + + +

    2.5 CSRF Practical Work

    pw

    Practical Work Web-Application directory : pw/pw-csrfopen in new window

    1 - Enable and configure the csrf server-side protection

    • declare the csrf protection in /server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java

    • use cookie strategy

    • make sure the cookie is NOT HttpOnly

    • Hint : HttpSecurity#csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())

    2 - Test the CSRF server-side protection

    • in "Home" page, add a "news" : analyze request and response's headers using the client Firefox Browser Network Monitor , what do you notice ?
    • Hint : verify that expected cookies (XSRF-TOKEN) is set and client-side header (X-XSRF-Token) is missing

    3 - Notice the csrf client-side protection

    • in "Home" page, add a "news" : analyze request and response's headers using the client Firefox Browser Network Monitor , what do you notice ?

      • Hint : verify that expected cookies (XSRF-TOKEN) is set and client-side header (X-XSRF-Token) is set with the same value

    4 - Test the CSRF protection

    • Try to forge a request : use a curl command on http://localhost:8080/api/news/like/8 (use Firefox Browser Network Monitor, right-click on the request ("Add" a like on a news to see the request), "select copy for curl", then, execute the command in a shell) Explain the result ? How can we change this result ?

      • Hint : it's ok as far as you post the header and the cookie with same token - if we modify one of the token values or remove it, we get forbidden access to the page because the CsrfFilter
    • Do the same for http://localhost:8080/api/news GET request and modify CSRF tokens and explain the result.

      • Hint : Angular doesn't send X-XSRF-TOKEN for GET or HEAD methods (see github.com/angular/angular/blob/5.2.8/packages/common/http/src/xsrf.ts#L81).Also, at server-side level, GET requests are allowed by default (see CsrfFilter#DefaultRequiresCsrfMatcher)

    5 - Try to understand the spring implementation of the csrf protection. Take a look at the following source files :

    • org.springframework.security.web.csrf.CsrfFilter class : the csrf filter, check that token from header and from cookie match. Otherwise, redirect to error page with HTTP 403 status
    • org.springframework.security.web.csrf.CookieCsrfTokenRepository class : used for CSRF token repository strategy (session, cookie, ...)
    • org.springframework.security.config.annotation.web.configurers.CrsfConfigurer class : Adds CSRF protection for the methods (uses antMatchers)
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/index.html b/common-threats/index.html new file mode 100644 index 00000000..fca74310 --- /dev/null +++ b/common-threats/index.html @@ -0,0 +1,33 @@ + + + + + + + + + 2- Common Threats | Angular Security Training + + + + +

    2- Common Threats

    In this chapter we will focus on the following threats raised in OWASP Top 10 2017 and 2021:

    • Identification and Authentication Failures: we will use JWT for this training

    • Injections:

      • CSRF: Cross Site Requests Forgery
      • XSS: Cross Site Scripting
      • SSTI: Template Injection
      • XSSI: JSON Hijacking
    • Vulnerable and Outdated Components

    • Unprotected APIs

    For each threat, we will have an overview and recommended defense mechanisms to implement globally with Angular.

    A Practical Work session is available when necessary.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-best-current-practices.html b/common-threats/jwt/jwt-best-current-practices.html new file mode 100644 index 00000000..af1a35ac --- /dev/null +++ b/common-threats/jwt/jwt-best-current-practices.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.5 JWT Best Current Practices | Angular Security Training + + + + +

    1.5 JWT Best Current Practices

    The minimum recommendations to defend your JWT against the threats mentioned previously are :

    Perform Algorithm Verification

    • Enable the caller to specify a supported set of algorithms and do not use any other algorithms when performing cryptographic operations.
    • Ensure that the alg or enc header specifies the same algorithm that is used for the cryptographic operation.
    • Each key must be used exactly by one algorithm only.
    • Check the key each time the cryptographic operation is performed.

    Use Appropriate Algorithms

    • Consider the JWS as invalid if the algorithm is not accepted by the application, even if the JWS can be successfully validated.
    • Only allow the use of cryptographically current algorithms that meet the security requirements of the application.
    • Design your application to enable cryptographic agility.
    • Do not generate or consume tokens using none unless explicitly requested by the caller.
    • Avoid all RSA-PKCS1 v1.5 encryption algorithm and prefer RSAES-OAEP.
    • Use the deterministic approach if you want to implement ECDSA.

    Validate all cryptographic operations

    • Reject any cryptographic operation use in JWT if any validation step has failed.
    • Validate both outer and inner operations in nested JWT by using the keys and algorithms supplied by the application.

    Ensure Cryptographic Keys have sufficient entropy

    • Human-memorizable passwords MUST NOT be directly used as the key to a keyed-MAC algorithm such as "HS256".

    Use UTF-8

    • Do not use or allow the use of other Unicode encodings for encoding and decoding JSON used in Header Parameters and JWT Claims.

    Validate the Issuer and Subject

    • Reject the JWT if it does not contain the iss (issuer) claim.
    • Validate the cryptographic keys used for the cryptographic operations in the JWT belong to the issuer.
    • Validate that the sub (subject) value corresponds to a valid subject and/or issuer-subject pair at the application. Reject the JWT if the issuer, subject, or the pair are invalid.

    Use and Validate Audience

    • JWT must contain an aud (audience) claim that can be used to determine whether the JWT is being used by an intended party or was substituted by an attacker at an unintended party.
    • Validate the audience value and reject the JWT if the audience value is not present or not associated with the recipient.

    Source: RFC-8725open in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-known-threats.html b/common-threats/jwt/jwt-known-threats.html new file mode 100644 index 00000000..6ed157d4 --- /dev/null +++ b/common-threats/jwt/jwt-known-threats.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.4 JWT Known Threats | Angular Security Training + + + + +

    1.4 JWT Known Threats

    Weak signature and insufficient Signature validation

    Design flaws of some libraries and applications have led to the following attacks:

    • The alg property in the JOSE header can be changed to none by an attacker, and the badly designed libraries or application will trust this none value and consider the token valid without checking the signature.

    • An RS256 alg value can be changed to HS256 and some weak libraries will try to validate the signature using the RSA public key as the HMAC shared secret.

    Weak Symmetric Keys

    Using a weak symmetric key for HS256 signature can open the door to offline brute-force or dictionary attacks.

    Incorrect Composition of Encryption and Signature

    Some libraries that decrypt a JWE-encrypted Token to obtain a JWS-signed object do not go further and validate the internal signature.

    Insecure use of Elliptic Curve Encryption

    Several JOSE fail to validate their inputs correctly when using ECDH-ES algorithm. An attacked can send JWE of its choosing that uses invalid curve points and observe the cleartext output resulting from decryption with the invalid curve points to recover the recipient's private key.

    Multiplicity of JSON Encodings

    Previous obsolete JSON formats allowed several character encodings : UTF-8, UTF-16, and UTF-32. Today, only UTF-8 is allowed.

    Attackers use this ambiguity between both implementations to bypass the recipient's validation checks.

    Substitution Attacks

    An attacker can use a legit JWT that was initially intended for a resource A and will attempt to use the same JWT for a different resource B. If such situations are not caught, this can result to attackers to successfully gain access to resources that it is not entitled to access.

    Indirect Attacks on the server

    Many JWT claims are used by the recipient to perform lookup operations in databases and LDAP. Any of these claims can be used by attackers as vector for injection attacks or server-side request forgery attacks.

    Source: RFC-8725open in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-overview.html b/common-threats/jwt/jwt-overview.html new file mode 100644 index 00000000..dbf62342 --- /dev/null +++ b/common-threats/jwt/jwt-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.1 JWT Overview | Angular Security Training + + + + +

    1.1 JWT Overview

    JWT in a nutshell

    JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

    • Information can be verified and trusted because it is digitally signed.

    • Used in URL, POST parameter, or an HTTP header.

    • Used to authenticate an API.

    It contains all important information about an entity, meaning that no database queries are necessary and no need to save the session on the server.

    What is JWT Structure ?

    JWT looks like the following in its JWS compact serialization :

    jwt-structure

    JOSE Header: (Javascript Object Signing and Encryption) Indicates to the receiver which type of signature algorithm to use for payload validation.

    JWS Payload: Can contain any information but note that the content is not encrypted. So any information that you put in the token is readable by anyone who intercepts it. It is possible to use JSON Web Encryption (JWE) to encrypt the content of the payload which is then signed with JWS.

    JWS Signature: is a MAC (Message authentication code) produced with the header, payload and the secret key.

    What is a Bearer Token ?

    The Bearer Token is a JWT used during an authentication process. Bearer Tokens are part of the OAuth V2 standard and widely adopted by many APIs. In the JWT authentication workflow, the bearer token becomes a signed temporary replacement for the login/password credentials. JWS Payload indicates the bearer standard properties for JWT Based Authentication:

    • iss means the issuing entity, in this case, our authentication server
    • iat is the timestamp of creation of the JWT
    • sub contains the technical identifier of the user
    • exp contains the token expiration timestamp

    What are JWT benefits for authentication ?

    The main authentication methods are the following:

    • HTTP basic authentication: relies on a simple credential scheme with username:password. They are sent in every request with risk of exposure even if sent in a secure connection. The password management is not trivial as you have to ask the user to change their passwords regularly. This method must be avoided in modern web applications, unless you add a multi-factor authentication layer.
    • Authentication Cookies : CSRF and XSS can be mitigated if you always use HttpOnly flag and use signed cookies for authentication. This method is incompatible with REST as it introduces a state into a stateless protocol.
    • Signature: Requires private key management. Useful for API authentication only and not adapted for browser/client authentication.
    • JWT: Token based authentication widely spread today for both browser / client (SPA) and RESTful API authentication. Secure implementation is needed to avoid potential threats.

    JWT key benefit is to allow the possibility to separate the Authentication logic from the Application Server.

    The Application servers become safer and faster by delegating authentication to a third party server that can be one of the following:

    • A centralized in-house custom developed authentication server.

    • A commercial product like LDAP capable of issuing JWTs.

    • A completely external third-party authentication provider such as for example OAuth.

    Another benefit is to make the application server completely stateless and no need to store password digests in application server database.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-pw.html b/common-threats/jwt/jwt-pw.html new file mode 100644 index 00000000..c47904a8 --- /dev/null +++ b/common-threats/jwt/jwt-pw.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.6 JWT Practical Work | Angular Security Training + + + + +

    1.6 JWT Practical Work

    pw

    Practical Work Web-Application directory : pw/pw-jwt-oauth/

    1 - In client/src/app/services/auth/auth-jwt.service.ts, implement the authenticateSuccess function by following the comments.

    2 - Implement HttpInterceptor "client/src/services/auth/auth-jwt.interceptor.ts" in order to send the JWT via the Authorization request header

    3 - Make sure that the JWT configurer is active - See /server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java

    4 - Test the JWT authentication : http://localhost:4200/#/login

    • Check that the JWT is stored in client storage
    • Check the JWT after login process in the response header and then in all following request headers.
    • Decode the token in https://jwt.io/

    5 - Suggest how to improve the jwt security.

    • Check /server/src/main/java/com/worldline/bookstore/security/jwt/TokenProvider.java
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-storage.html b/common-threats/jwt/jwt-storage.html new file mode 100644 index 00000000..8d7935d7 --- /dev/null +++ b/common-threats/jwt/jwt-storage.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.3 JWT Storage | Angular Security Training + + + + +

    1.3 JWT Storage

    Storing the JWT in localStorage or sessionStorage will expose your application to XSS attacks.

    Using Cookies for JWT storage

    Using cookies is the best way to store safely your JWT. This tactic can be vulnerable to CSRF though. Therefore, you need to ensure to set SameSite=strict and httpOnly flag for JWT authentication cookies. Also, enable the CSRF defense mechanisms provided by your framework.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/jwt/jwt-workflow.html b/common-threats/jwt/jwt-workflow.html new file mode 100644 index 00000000..8f7dde87 --- /dev/null +++ b/common-threats/jwt/jwt-workflow.html @@ -0,0 +1,33 @@ + + + + + + + + + 1.2 JWT Workflow | Angular Security Training + + + + +

    1.2 JWT Workflow

    jwt-workflow

    1. The user submits the username and password to an Authentication server.

    2. The Authentication server validates the username and password combination and creates a Bearer Token: JWT token with a payload containing the user technical identifier, and an expiration timestamp.

    3. The Authentication server then takes a secret key, and uses it to sign the Header plus Payload and sends it back to the user browser.

    4. The browser takes the signed JWT and starts sending it with each HTTP request to our Application server.The signed JWT acts effectively as a temporary user credential, that replaces the permanent credential which is the username and password combination.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/sca/sca-angular.html b/common-threats/sca/sca-angular.html new file mode 100644 index 00000000..b2dee098 --- /dev/null +++ b/common-threats/sca/sca-angular.html @@ -0,0 +1,44 @@ + + + + + + + + + 6.4 Vulnerable Components Protection in Angular | Angular Security Training + + + + +

    6.4 Vulnerable Components Protection in Angular

    Checking JavaScript libraries integrity

    Including code from third-party providers requires a lot of trust

    • What if the provider gets compromised?
    • What if the provider decides to change the code you are including ?

    npm audit

    You can update your npm modules with npm audit fix command. It checks a vulnerabilities from the Github Advisory Repository. This repository is different than OSS index used for Dependency-Track. Therefore, reports from both npm audit and Dependency Track may defer.

    Sub-Resource Integrity (SRI)

    <script src="https://example.com/angular.js"
    +        integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
    +        crossorigin="anonymous"></script>
    +
    • SRI allows you to define what you want to include
    • Browser calculates and verifies the checksum before including the resources
    • Hash is provided by online tools / CDN / webpack plugin in your build process.
    • angular-cli provides an option to add automatically the subresource integrity to your files by adding --subresource-integrity in build scripts:
    "scripts": {
    +  "ng": "ng",
    +  "start": "ng serve",
    +  "build": "ng build --subresource-integrity",
    +  "test": "ng test",
    +  "lint": "ng lint",
    +  "e2e": "ng e2e"
    +},
    +
    • Supported by Chrome, firefox and Opera (backward compatible)
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/sca/sca-defense.html b/common-threats/sca/sca-defense.html new file mode 100644 index 00000000..518a0a97 --- /dev/null +++ b/common-threats/sca/sca-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 6.3 Vulnerable Component Defense | Angular Security Training + + + + +

    6.3 Vulnerable Component Defense

    How do I prevent using vulnerable components ?

    Continuously monitoring SCA tools reports and addressing the vulnerabilities raised will allow to reduce the risk of using vulnerable components. You can also add fail gates in your CI pipelines to avoid adding new vulnerable components in new code changes.

    TIP

    Continuously grooming and maintaining your application with a proper obsolescence management is key to reduce risks of having high/critical vulnerabilities.

    How to address existing vulnerable components ?

    1. Identify the Business criticality of your application (high or low importance ?)

    2. Prioritize by using the prioritization matrix (CVSS x business criticality) and order the vulnerabilities by their final priority.

    3. Analyze and discuss with your team about the vulnerability description by starting with the most critical vulnerabilities.

    4. Filter out false positives and ensure evidence is well documented and accessible.

    5. Document evidence and justification of your decisions and ensure they are always accessible.

    6. Plan to process vulnerabilities within their corresponding remediation time.

    7. Fix and remediate the vulnerabilities by agreeing on a resolution action for each vulnerability such as:

      • Patch vulnerable code.

      • Upgrade vulnerable component version.

      • Virtual Patch your application with WAF in case patching vulnerable code is impossible.

      • Block vulnerable functionality.

    8. Test any fix made to ensure the vulnerabilities are no longer present and exploitable. If possible, add unit tests for this specific flow.

    9. Monitor the security scans on a regular basis.

    10. Repeat the previous steps at each new vulnerability scan report.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/sca/sca-detection.html b/common-threats/sca/sca-detection.html new file mode 100644 index 00000000..93b813ce --- /dev/null +++ b/common-threats/sca/sca-detection.html @@ -0,0 +1,33 @@ + + + + + + + + + 6.2 Vulnerable Components Detection | Angular Security Training + + + + +

    6.2 Vulnerable Components Detection

    How do I know if my application has vulnerable components ?

    Software composition Analysis (SCA) tools enable you to perform automated scan of all client-side and server-side components, and their dependencies used in your web application. These SCA tools continuously monitor vulnerabilities databases like NVD or OSS Index and alert you of potential threats in your project. Here is a list of SCA tools we recommend to use: Dependency-Track, Dependency-Check

    WARNING

    SCA reports requires further analysis from your end in order to identify the real impact based on your context and filter out false positives.

    Detecting known vulnerable JavaScript libraries with npm audit

    npm provides an embedded dependency scanning during each compilation and provides an overview of vulnerable components present in your application. You can decide to upgrade the component as suggested by npm during the compilation.

    Detecting known vulnerable maven dependencies with Dependency-Check

    • Dependency-Check: Open source, maven plugin

    • Features:

      • Scan all maven dependencies
      • Access to the NVD and check CVE
      • Generate a complete report (HTML format): vulnerabilities, criticality level, CVE code, patched versions ...
    • Execute the maven command mvn org.owasp:dependency-check-maven:check

    • See usage & configuration

    Tracking project vulnerabilities with Dependency-Track

    Dependency-Track is an Open source tool provided by OWASP which allows continuous component analysis.

    Features:

    • CycloneDX and SPDX bill-of-material formats
    • Dependency-Check XML
    • Jenkins and gitlab-ci integration
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/sca/sca-overview.html b/common-threats/sca/sca-overview.html new file mode 100644 index 00000000..fb42791c --- /dev/null +++ b/common-threats/sca/sca-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + 6.1 Vulnerable Components Overview | Angular Security Training + + + + +

    6.1 Vulnerable Components Overview

    • The rank of this vulnerability moved from rank 9 to rank 6 in Owasp Top 10 2021

    • Components (libraries, framework, software modules,...) run with the same privileges as the application.

    • If a vulnerable component is exploited, the attack can result to serious data loss or server takeover.

    • Scope:

      • Server-side frameworks & applications (J2EE/Spring applications, SOA, batchs,...)
      • Client-side frameworks & applications (webapp, SPA, JS frameworks,...)
      • Proprietary & Open-source products/frameworks/projects.

    Examples:

    • Log4Shell CVE-2021-44228, a zero-day vulnerability in Log4j component, allowing arbitrary requests to LDAP and JNDI servers.
    • Equifax (a US credit bureau organization)- breach due to unpatched CVE-2017-5638 Apache Struts web framework
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/sca/sca-pw.html b/common-threats/sca/sca-pw.html new file mode 100644 index 00000000..b9692871 --- /dev/null +++ b/common-threats/sca/sca-pw.html @@ -0,0 +1,33 @@ + + + + + + + + + 6.5 Vulnerable Components Practical Work | Angular Security Training + + + + +

    6.5 Vulnerable Components Practical Work

    pw

    1 - Load a js script from Content Delivery Network (CDN) with SRI

    • In index.html, load the minified version 3.2.1 of "jquery" from cdnjs.cloudflare.com (take care of the version number)
    • Implement sub resource integrity (SRI) to check the resource integrity

    Hints :

    • Get resources with SRI from https://cdnjs.com or use the online tool https://www.srihash.org/ to generate SRI hash
    • if CSP is configured, you will have to update it in order to allow external resources from CDN (add appropriate hash in script-src directive - work only for Chrome)

    2 - Detect known vulnerable third party components

    • execute npm audit and review the report.
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/ssti/ssti-angular.html b/common-threats/ssti/ssti-angular.html new file mode 100644 index 00000000..e14b03af --- /dev/null +++ b/common-threats/ssti/ssti-angular.html @@ -0,0 +1,33 @@ + + + + + + + + + 5.3 SSTI Protection in Angular | Angular Security Training + + + + +

    5.3 SSTI Protection in Angular

    Angular templates are trusted by default and should be treated as executable code.

    WARNING

    Never generate templates by adding user inputs to template syntax !

    It will expose your application to XSS attacks and enable attackers to inject arbitrary code.

    If you really need to generate templates with users inputs, you must always use the AOT template compiler in production deployments.

    Angular offers ahead-of-time AOT compilation which offers better security when dealing with templates:

    • Compiles HTML templates and components into JavaScript files long before they are served to the client.
    • No templates to read and no risky client-side HTML or JavaScript evaluation

    AOT compilation effectively stops template injection attacks:

    • At the moment of injection, the application is already compiled.
    • The injected code will simply be rendered, not executed.

    AOT compiler is enabled by default starting Angular 9. (check configurations in angular.json)

    Another alternative is JIT (Just In Time) compiler which compiles templates to executable template code within the browser at runtime. This compiler was enabled by default until Angular 8.

    To learn further: https://angular.io/guide/aot-compileropen in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/ssti/ssti-defense.html b/common-threats/ssti/ssti-defense.html new file mode 100644 index 00000000..94a65c39 --- /dev/null +++ b/common-threats/ssti/ssti-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 5.2 SSTI Defense | Angular Security Training + + + + + + + + diff --git a/common-threats/ssti/ssti-overview.html b/common-threats/ssti/ssti-overview.html new file mode 100644 index 00000000..9ac77be7 --- /dev/null +++ b/common-threats/ssti/ssti-overview.html @@ -0,0 +1,34 @@ + + + + + + + + + 5.1 SSTI Overview | Angular Security Training + + + + +

    5.1 SSTI Overview

    SSTI in a nutshell

    Server-Side Template Injection is an attack where a malicious payload is injected into a template which is then executed server-side.

    What are Templates ?

    Templates allow to pre-populate dynamic data from the server into a web page. Web pages coming from web templates can structure the component of web pages in such a way that can be modified independently of each other. Templates are commonly used for:

    • Displaying information about users (username, bio...), products, companies.
    • Sending bulk emails.
    • Displaying a gallery of photos or videos.

    Example:

    Hello {{user.name}}
    +

    Example of vulnerable Angular Template

    template-injection-example

    A malicious user can inject malicious code from unknown origin which will be executed in potentialUserInput.

    Source: https://snyk.io/blog/angular-security-best-practices/open in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/xss/xss-angular.html b/common-threats/xss/xss-angular.html new file mode 100644 index 00000000..969489dd --- /dev/null +++ b/common-threats/xss/xss-angular.html @@ -0,0 +1,41 @@ + + + + + + + + + 3.4 XSS Protection in Angular | Angular Security Training + + + + +

    3.4 XSS Protection in Angular

    What are the XSS defense mechanisms available in Angular ?

    Angular Built in defense

    The good news is that Angular treats all users inputs as untrusted data by default.

    Therefore, it has an XSS defense by default.

    Contextual escaping

    xss-contextual-escaping

    Angular displays any input (malicious or not) as plain text on your webpage.

    It won't interpret the input as HTML (for example in comments) if you don't explicitly tell him to.

    Input Sanitization

    xss-contextual-escaping

    Use angular properties to bind user inputs:

    • [innerHtml]: binds html tags
    • [style]: binds CSS attributes
    • [href]: binds dynamic links

    Angular will interpret the bound inputs if you explicitly use the corresponding property.

    Angular automatically recognizes <script> tags as unsafe and removes it. A warning appears in the browser console to notify you if Angular has sanitized an input value.

    Input Sanitization Bypassing

    WARNING

    ⚠️ Use with care ⚠️

    Sometimes application need to include executable code, display an iframe from an url or construct potentially dangerous urls...

    ⚠️ You must discuss this use case with your Security Officer before going further. ⚠️

    If you really need to use validated user input in your application, you must mark it as trusted input.

    You can use the byPassSecurityTrust...() functions from the DomSanitizer class:

    abstract class DomSanitizer implements Sanitizer {
    +  abstract sanitize(context: SecurityContext, value: string | SafeValue): string | null
    +  abstract bypassSecurityTrustHtml(value: string): SafeHtml
    +  abstract bypassSecurityTrustStyle(value: string): SafeStyle
    +  abstract bypassSecurityTrustScript(value: string): SafeScript
    +  abstract bypassSecurityTrustUrl(value: string): SafeUrl
    +  abstract bypassSecurityTrustResourceUrl(value: string): SafeResourceUrl
    +}
    +

    Further resources

    angular.io/guide/security#server-side-xss-protectionopen in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/xss/xss-defense.html b/common-threats/xss/xss-defense.html new file mode 100644 index 00000000..85ebf7e6 --- /dev/null +++ b/common-threats/xss/xss-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 3.3 XSS Defense Best Practices | Angular Security Training + + + + +

    3.3 XSS Defense Best Practices

    What can I do to prevent XSS ?

    • Use frameworks that natively escape XSS by design.
    • Sanitize all users inputs. Sanitization is the inspection of an untrusted value, turning it into a value that is safe to use in the DOM.
    • Apply OWASP XSS prevention rulesopen in new window
    • Use SAST in your IDE in order to alert on vulnerable code while you are developing.
    • Request code reviews from acknowledged Security Reviewers in your team.
    • Scan your code with automated code analysis tools in your merge requests and prevent pushing any vulnerable code in your application.
    • Perform regular SCA scans of your dependencies with Dependency Track and fix the vulnerabilities raised.
    • Perform regular penetration testing with DAST tools like OWASP ZAP and fix the findings.

    https://owasp.org/www-project-top-ten/2017/A7_2017-Cross-Site_Scripting_(XSS)

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/xss/xss-detection.html b/common-threats/xss/xss-detection.html new file mode 100644 index 00000000..f2793dfc --- /dev/null +++ b/common-threats/xss/xss-detection.html @@ -0,0 +1,33 @@ + + + + + + + + + 3.2 XSS Detection | Angular Security Training + + + + +

    3.2 XSS Detection

    Is my application vulnerable to XSS ?

    XSS vulnerabilities are difficult to identify and completely be removed from web applications or API.

    • Review new code by a knowledgeable person in secure coding.
    • SAST scans are capable to detect vulnerable code which could allow XSS attacks.
    • DAST tools such as OWASP ZAP can scan your application and help you detect exploitable flaws allowing XSS.
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/xss/xss-overview.html b/common-threats/xss/xss-overview.html new file mode 100644 index 00000000..5036b641 --- /dev/null +++ b/common-threats/xss/xss-overview.html @@ -0,0 +1,33 @@ + + + + + + + + + 3.1 XSS Overview | Angular Security Training + + + + +

    3.1 XSS Overview

    XSS in a nutshell

    Cross-Site Scripting (XSS) is a vulnerability that occurs when untrusted data from the user is processed by the web application without validation and is reflected back to the browser without encoding or escaping, resulting in code execution at the browser engine.

    xss-wf

    Why XSS is dangerous ?

    The impact of XSS is moderate for reflected and DOM XSS, and severe for stored XSS, with remote code execution on the victim’s browser, such as stealing credentials, sessions, MFA bypass, DOM node replacement or defacement (such as trojan login panels), key logging or delivering malware to the victim.

    What causes XSS ?

    This attack happens mostly because the web application or API uses inputs from any users within the output it generates without proper validation or encoding.

    How XSS attacks happen ?

    XSS typeDescriptionPayload typePayload ExamplesReal cases
    ReflectedReflected in web server as an error message, search result or any input sent to the server as part of the request.From request parameter.imageepic gamesopen in new window
    StoredInjected script is permanently stored in target servers.Stored server-side.imagemyspace wormopen in new window
    DOM basedModifying the DOM environment in the victim browser.Introduced by DOM modification.imageMicrosoft, Apple, Google,...open in new window
    MutationLooks like safe (or just a syntax error) in itself but become active after mutation processMutated by browser.imageGoogle search baropen in new window
    Blind Cross-Site scriptingScript is saved on server side and reflected back in the backend application.Teslaopen in new window

    Further resources

    owasp.org/www-community/attacks/xss/open in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/common-threats/xss/xss-pw.html b/common-threats/xss/xss-pw.html new file mode 100644 index 00000000..50cf7aef --- /dev/null +++ b/common-threats/xss/xss-pw.html @@ -0,0 +1,33 @@ + + + + + + + + + 3.5 XSS Practical Work | Angular Security Training + + + + +

    3.5 XSS Practical Work

    pw

    Practical Work Web-Application directory : pw/pw-xss

    1 - Render HTML formatting elements in Angular

    • in "Home" page, create a "news" with formatted content by using HTML elements like <b> and <i> . What do you notice ?
      • Hint : Tags are silently and safety escaped by Angular if you use interpolation
    • Use [innerHTML] attribute to render safe HTML tags in home.html . What do you notice ?
      • Hint : html tags are safely executed.
    • create a "news" with scripting.( Example <a href="javascript:alert('XSS!')">click!</a> or <span onclick="alert('XSS!');">test!</span>) What do you observe ? Take a look at the console.
      • Hint : Scripting is safety escaped and not executed- Angular logs a warning on the client console

    2 - Use DomSanitizer service

    • Transform the "Welcome" message (see "Home" page) to a link which displays an alert popup (use javascript:alert syntax).
      • Hint : Check commented lines for trustedUrl in home.html and home.ts
    • (Just for the demo, don't do this in a real situation) Sanitize the "newsOfTheDay" content in order to execute some basic and safe scripting based on DOM even listener (onclick, ...)
      • Hint : Check commented lines for bypassSecurityTrustHtml and innerHtml in home.html and home.ts
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/continuous-prevention/index.html b/continuous-prevention/index.html new file mode 100644 index 00000000..37d265b2 --- /dev/null +++ b/continuous-prevention/index.html @@ -0,0 +1,33 @@ + + + + + + + + + 4. Global recommendations | Angular Security Training + + + + +

    4. Global recommendations

    • Use Angular built-in protection (CSRF, XSS, JSON hijacking)
    • Recommend your users to have recent browsers (CSP3 support, subresource integrity, “SameSite” cookie attribute, …)
    • Do not mix client and server templates.
    • Do not trust user-provided content without proper sanitization.
    • Consider using CSP as a defense in depth.
    • Consider using JWT for authentication.
    • Do not blindly trust 3rd party components (audit, «Subresource integrity» browser feature)
    • Always treat all data read from localStorage/sessionStorage as untrusted user input.
    • Review Angular Security guidelines at each new version: https://angular.io/guide/securityopen in new window
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/csp/csp-angular.html b/csp/csp-angular.html new file mode 100644 index 00000000..1eb066c8 --- /dev/null +++ b/csp/csp-angular.html @@ -0,0 +1,50 @@ + + + + + + + + + 4.3 CSP in Angular | Angular Security Training + + + + +

    4.3 CSP in Angular

    Angular suggests completing their built-in XSS prevention with the following:

    • Use Content-Security-Policy HTTP header: We recommend you to use strict CSP instead of allow-list CSP as it is more effective and does not need customization. You can choose to use Nonce-based strict CSP or Hash-based strict CSP.
    Content-Security-Policy:
    +  script-src 'nonce-{RANDOM}' 'strict-dynamic';
    +  object-src 'none';
    +  base-uri 'none';
    +
    Content-Security-Policy:
    +  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
    +  object-src 'none';
    +  base-uri 'none';
    +

    Note that using these headers requires you to refactor code that is not compatible with CSP.

    To learn further please check this page: web.dev/strict-csp/open in new window

    • Use Trusted Typesopen in new window (an API that allows applications to lock down DOM-XSS injection sinks to only accept non-spoofable, typed values in place of strings) by configuring your web server to emit HTTP headers with one of the following Angular policies:

      • angular This policy is used in security-reviewed code that is internal to Angular, and is required for Angular to function when Trusted Types are enforced. Any inline template values or content sanitized by Angular is treated as safe by this policy.

        Content-Security-Policy: 
        +    trusted-types angular; 
        +    require-trusted-types-for 'script';
        +
      • angular#unsafe-bypass - This policy is used for applications that use any of the methods in Angular DomSanitizer that bypass security, such as bypassSecurityTrustHtml. Any application that uses these methods must enable this policy.

        Content-Security-Policy: 
        +    trusted-types angular angular#unsafe-bypass; 
        +    require-trusted-types-for 'script';
        +
      • angular#unsafe-jit This policy is used by the JIT compiler. You must enable this policy if your application interacts directly with the JIT compiler or is running in JIT mode using the platform browser dynamic.

        Content-Security-Policy: 
        +    trusted-types angular angular#unsafe-jit; 
        +    require-trusted-types-for 'script';
        +
    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/csp/csp-defense.html b/csp/csp-defense.html new file mode 100644 index 00000000..907da187 --- /dev/null +++ b/csp/csp-defense.html @@ -0,0 +1,33 @@ + + + + + + + + + 4.2 CSP Defense | Angular Security Training + + + + +

    4.2 CSP Defense

    What are the risks of CSP ?

    As shown in the overview section, there are many policies available to setup and maintain for your CSP. This raises a high risk of policies misconfiguration which can lead to unavailability of some features of your application. In this situation, teams become too lazy to bother about maintaining the policies and fall in the risk of using too permissive policies.

    How to implement CSP in real life ?

    For sensitive web applications, you want to make sure that only the resources you've written yourself can be loaded with CSP. In case of whitelist strategy, you can use a lockdown approach by starting with a default policy that blocks absolutely everything : default-src 'none' and build up your white list from there. In case of strict-dynamic strategy, you need to refactor your javascript code, include nonce generation in your application and add the nonce attribute to all your script tags.

    In either case, it is highly recommended testing your CSP in report only mode(report-uri or report-to) during a certain period first and resolve the alerts before applying the CSP. Also, use the same CSP in all your environments (tests and production) in order to avoid incompatibility incidents. This pageopen in new window can help you analyse CSP notifications that may be difficult to understand.

    To learn further: OWASP CSP setup guide bookopen in new window

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/csp/csp-overview.html b/csp/csp-overview.html new file mode 100644 index 00000000..136b8794 --- /dev/null +++ b/csp/csp-overview.html @@ -0,0 +1,42 @@ + + + + + + + + + 4.1 CSP Overview | Angular Security Training + + + + +

    4.1 CSP Overview

    csp-workflow

    Content Security Policy (CSP) is a W3C standard introduced to prevent cross-site-scripting, click-jacking and other code injection attacks resulting of malicious inputs in the trusted web page context. CSP defines a standard methods for web application developers to declare approved origins of content that browsers should allow loading.

    CSP is a new browser security policy :

    • Defines what it is allowed to happen in a page
    • Delivered by the server in a response header or meta tag

    imageimageimage

    WARNING

    CSP is not intended as a first level of defense against code injection attacks like XSS. CSP is best used as defense-in-depth. It reduces the risk that malicious injection can cause, but it is not a replacement for careful input validation and output encoding.

    Implementation

    To enable CSP, you need to configure your web application to return the Content-Security-Policy HTTP header.

    image

    CSP evolution to strict CSP

    White-list CSP properties requires a lot of customization and maintenance.

    CSP evolution with script-src directive :

    image

    A nonce is a random number generated for single usage to mark a <script> tag as trusted.

    WARNING

    Browsers still not support all CSP version 3 features. Most recent browsers version support CSP version 2.

    CSP common directives

    • default-src : Default property if no other attributes are defined. In most cases, the value of this property is default-src 'self' meaning the browser can only upload resources from the current website.

    • script-src : Defines locations from which external scripts can be loaded. If your web application does not use client-side scripting, set the value to script-src 'none'.

    • img-src : Defines locations from which images can be retrieved.

    • media-src : Defines locations from which rich media like video can be retrieved.

    • object-src : Defines locations from which plugins can be retrieved.

    • manifest-src : Defines locations from which application manifests can be retrieved.

    • frame-ancestors : Defines locations from which another web page can be loaded using a frame, iframe, object, embed, or applet element.

    • form-action : Specifies URLs that can be used as part of an action in a <form> tag, meaning the browser restricts where form results can be sent. The form action does not revert to default-src, so this is a mandatory property if you are using form elements on your site.

    • plugin-types : Identifies the set of plugins that can be invoked via objects, embeds, or applets, defined using MIME types.

    • base-uri : Allows URLs in the src attribute of any tag.

    • report-uri: Instructs the browser to POST a reports of policy failures to this URI. You can also use Content-Security-Policy-Report-Only as the HTTP header name to instruct the browser to only send reports (does not block anything). This directive is deprecated in CSP Level 3 in favor of the report-to directive.

    WARNING

    As of today, some CSP properties may not be supported by all browsers. IE is known to have limited support for CSP. You can verify the compatibility of CSP properties with browser with several online tools like this one: https://caniuse.com/?search=cspopen in new window

    CSP Common Source List Values

    • *: Wildcard, allows any URL except data: blob: filesystem: schemes
    • none: Prevents loading resources from any source, even from your own!
    • self : Allows loading resources from the same origin (same scheme, host and port).
    • unsafe-inline : Allows use of inline source elements such as style attribute, onclick, or script tag bodies and javascript: URIs. This value is as it says : unsafe !
    • nonce-rAnd0m : Allows an inline script or CSS to execute if the script (eg: <script nonce="rAnd0m">) tag contains a nonce attribute matching the nonce specified in the CSP header. The nonce should be a secure random string, and should not be reused.
    • strict-dynamic : Allows scripts to be included by any script already marked by a nonce or hash. Disables the whitelist and allows backward compatibility with CSP v1 and v2 as shown bellow: csp-workflow

    Common white-list CSP example

    Content-Security-Policy: 
    +  default-src 'self'; 
    +  style-src 'unsafe-inline' 'self' https://fonts.googleapis.com https://themes.googleusercontent.com;
    +  frame-src https://www.slideshare.net www.youtube.com twitter.com; 
    +  object-src 'none'; 
    +  font-src 'self' data: https://themes.googleusercontent.com https://fonts.googleapis.com; 
    +  script-src 'strict-dynamic' 'nonce-rAnd0m123' 'unsafe-inline' 'self' https://www.google.com twitter.com https://themes.googleusercontent.com;
    +  base-uri 'none'; 
    +  img-src 'self' https://www.google.com data: https://pbs.twimg.com https://img.youtube.com twitter.com
    +

    TIP

    This online toolopen in new window or this oneopen in new window can help you evaluate the strength of your CSP.

    To learn further about CSP, don't hesitate to check there Quick Reference Guideopen in new window.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/csp/csp-pw.html b/csp/csp-pw.html new file mode 100644 index 00000000..7962410e --- /dev/null +++ b/csp/csp-pw.html @@ -0,0 +1,215 @@ + + + + + + + + + 4.4 CSP Practical Work | Angular Security Training + + + + +

    4.4 CSP Practical Work

    Part 1

    pw

    Practical Work Web-Application directory : pw/pw-csp

    1 - Starter Content Security Policy

    • Configure a minimalistic CSP in angular side (index.html) with the following value and observe the result (see console log) : default-src 'none'; script-src 'self'; connect-src 'self'; img-src 'self'; style-src 'self';

      • Hints :
        • This policy allows images, scripts, AJAX, and CSS from the same origin, and does not allow any other resources to load (eg inline scripting, inline styles, object, frame, media, etc). It is a good starting point but often too restrictive for many existing sites
        • You can declare your CSP as a meta tag with http-equiv directive in index.html :<meta http-equiv="__directive__" content="__value__">
    • Update step by step the configuration in order to make the website load properly (Content-Security-Policy-Report-Only header)

      • Hint : add unsafe-inline or unsafe-eval wherever needed to remove console alerts
    • Check the security level of this CSP on csp-evaluator.withgoogle.com/open in new window

    • Declare the CSP server-side

      • Hints :
        • Use the spring security API HttpSecurity : HttpSecurity#headers()#contentSecurityPolicy("...")
        • Set the CSP configuration in /server/src/main/java/com/worldline/bookstore/config/SecurityConfiguration.java
        • (if necessary) Bypass the angular-cli proxy in order to use the server-side CSP configuration (ng build, then use http://localhost:8080/#/home)

    2 - Configure a CSP 3

    • Use CSP to secure your app against inline scripting

      • In index.html, declare an arbitrary inline scripting : <script>document.write('<h1>Inline scripting is <b>not recommended</b>! But if you have not the choice, <b>secure your app with CSP</b></h1>');</script>"

      • Update the CSP in order to block the inline scripting.

      • Update the CSP in order to allow this inline scripting securely (consider CSP3 SHA-256 hash syntax)

      • Hints : To generate the hash of the script content, use this online tool : report-uri.io/home/hashopen in new window (beware of spaces and carriage returns...)

    • Check the security level of this CSP on csp-evaluator.withgoogle.com/open in new window

    Part 2

    Practical Work Web-Application directory : pw/pw-csp-nonce

    The purpose of this PW is to implement a CSP based on a dynamic nonce. The CSP will be generated server-side but loaded client-side. Then we will add inline scripting and secure it with a nonce.

    1 - Implement a CSP based on a nonce, server-side:

    • Implement a CSPResource REST api endpoint to generate a random nonce and return a content-security-policy which declares this nonce in the script-src directive
                package com.worldline.bookstore.web.rest;
    +
    +            import java.security.MessageDigest;
    +            import java.security.NoSuchAlgorithmException;
    +            import java.security.SecureRandom;
    +            import java.util.Collections;
    +            
    +            import javax.servlet.http.HttpServletResponse;
    +            
    +            import org.slf4j.Logger;
    +            import org.slf4j.LoggerFactory;
    +            import org.springframework.http.HttpStatus;
    +            import org.springframework.http.ResponseEntity;
    +            import org.springframework.security.crypto.codec.Hex;
    +            import org.springframework.web.bind.annotation.GetMapping;
    +            import org.springframework.web.bind.annotation.RequestMapping;
    +            import org.springframework.web.bind.annotation.RestController;
    +            
    +            import com.codahale.metrics.annotation.Timed;
    +            
    +            /**
    +             * REST controller for managing Content-Security-Policy confuguration with random nonce.
    +             */
    +            @RestController
    +            @RequestMapping("/api")
    +            public class CSPResource {
    +            
    +            	private final Logger log = LoggerFactory.getLogger(CSPResource.class);
    +            	
    +            	/** Used for Script Nonce */
    +            	private SecureRandom prng = null;
    +            
    +            	@GetMapping("/csp")
    +                @Timed
    +                // Add Script Nonce CSP Policy
    +                public ResponseEntity<?> generateCSP(HttpServletResponse response) {
    +              		// --Get its digest
    +              		MessageDigest sha;
    +              		// --Generate a random number
    +              		String randomNum;
    +              		try {
    +              			this.prng = SecureRandom.getInstance("SHA1PRNG");
    +              			randomNum = new Integer(this.prng.nextInt()).toString();
    +              			sha = MessageDigest.getInstance("SHA-1");
    +              		}
    +              		catch (NoSuchAlgorithmException e) {
    +              			return new ResponseEntity<>(Collections.singletonMap("CSPException",e.getLocalizedMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
    +              		}
    +              		
    +              		byte[] digest = sha.digest(randomNum.getBytes());
    +              		
    +              		// --Encode it into HEXA
    +              		char[] scriptNonce = Hex.encode(digest);
    +              
    +              		String csp = "script-src" +
    +              				" 'unsafe-eval' 'strict-dynamic' " +
    +              				" 'nonce-"+String.valueOf(scriptNonce)+"'" +
    +              				" 'sha256-hwg4gsxgFZhOsEEamdOYGBf13FyQuiTwlAQgxVSNgt4='" + // SRI hashes for https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js (work only for Chrome)
    +              				";" +
    +              				// add connect-src directive to adapt CSP over cross-origin requests (CORS)  
    +              				"connect-src"+
    +              				" http://localhost:8080 http://localhost:4200 ws://localhost:4200"
    +              				+ ";"+
    +              				" style-src" +
    +              				" 'self' 'unsafe-inline'"+
    +              				";" +
    +              				" font-src" +
    +              				" 'self' "+
    +              				";" +
    +              				" img-src" +
    +              				" 'self' data:" +
    +              				";" +
    +              				" child-src" +
    +              				" 'self' " +
    +              				";" +
    +              				" object-src" +
    +              				" 'none' " +
    +              				";" +
    +              				" default-src" +
    +              		    " 'self' ";
    +              		
    +                      CSP conf = new CSP(csp);
    +                      conf.setNonce(String.valueOf(scriptNonce));
    +                      
    +                      log.debug(conf.toString());
    +                      
    +                      return ResponseEntity.ok(conf);  
    +                }
    +            
    +            }
    +
    +

    Note : Implement a CSP wrapper class (used by the CSPResource class) with 2 attributes :

    • config : stores the complete Content-Security-Policy

    • nonce : stores the nonce

    • Remove the csp configuration loaded from the SecurityConfiguration.java if any

    2 - Client-side implementation:

    • Create a services/cspConfigService.ts file to GET the CSP config and nonce from the CSPResources REST api
                    import { Injectable, Injector } from '@angular/core';
    +                import { HttpClient, HttpResponse } from '@angular/common/http';
    +                
    +                @Injectable()
    +                // Thuis service gets the Content-Security-Policy and a random nonce from a REST api endpoint /api/csp
    +                export class CspConfig {
    +                
    +                    private _config: any;
    +                    private _nonce: any;
    +                    private http: HttpClient;
    +                  
    +                    // can't use classical Angular DI for HttpClient here, because of "cyclic dependency" issues
    +                    // Use Injector service to instanciate HttpClient 
    +                  	constructor(injector:Injector) {
    +                    	this.http = injector.get(HttpClient);
    +                  	}
    +                
    +                    // Load Content-Security-Policy from a REST api endpoint
    +                    // The returned data will contain the CSP configuration ('value') and the a random generated nonce ('nonce')
    +                    load(): Promise<any>{
    +                        return this.http.get('/api/csp')
    +                              .toPromise()
    +                              .then(data => {
    +                                  this._config = data['value'];
    +                                  this._nonce = data['nonce'];
    +                                  return data;
    +                               })
    +                    }
    +                    
    +                    get config(): any {
    +                        return this._config;
    +                    }
    +                
    +                    get nonce(): any {
    +                        return this._nonce;
    +                    }
    +                }
    +
    +

    Note : you will have to import and declare this new service in app.module.ts

    • Load the CSP with a meta tag and add an arbitrary script block with a nonce
            Update app.component.ts to add the following implementation
    +        
    +          export class AppComponent {
    +          	
    +          	private csp: string;
    +          	private nonce:string; 
    +          	
    +          	constructor(
    +          		private router: Router, 
    +          		public userService: UserService,
    +          		public cspConfig: CspConfig) {
    +          
    +          	  	cspConfig.load().then(
    +          	  		data => {
    +          	  			
    +          	  			this.csp = data['value'];
    +          	  			this.nonce = data['nonce'];;
    +          	  			
    +          	  			console.debug('csp : '+this.csp);
    +          	  			console.debug('nonce : '+this.nonce);
    +          				
    +          				  // can't use the Meta#addTags() method to set CSP because it will insert the meta tag too late, so we add it "manually"
    +          	  			var meta = "<meta http-equiv=\"Content-Security-Policy\" content=\""+this.csp+"\">";
    +          	  			this.renderHtml(meta, 'head');
    +          	  			console.log('content-security-policy meta  : '+meta);
    +                    
    +                    // Add secure inline scripting (a script block with a nonce)
    +          	  		  // The script will just render a message at the bottom of the page
    +          				  // (here, we don't use document.write method otherwise it will replace the whole page rendering)
    +          				  var yourHtmlString = 
    +          					"<script nonce='"+this.nonce+"'>"+
    +          						"document.getElementsByTagName('body')[0].appendChild("+
    +          							"document.createRange().createContextualFragment("+
    +          								"'<h1>Inline scripting is <b>not recommended</b>! But if you have not the choice, <b>secure your app with CSP</b></h1>'));</script>";
    +          			 	  this.renderHtml(yourHtmlString, 'head');
    +          				  console.log('inline scripting !!! ', yourHtmlString);				    
    +          	  	});
    +          	}
    +          
    +            ...
    +            
    +            /**
    +          	 *	
    +          	 *	Renders an html portion inside a given html tag
    +          	 *	@param message: a string which represents the html portion to render in the page
    +          	 *	@param parentTag : the html tag name in which the html portion will be inserted as a first child
    +          	 */
    +            private renderHtml(message:string, parentTag: string){
    +          		var fragment = document.createRange().createContextualFragment(message);
    +          		document.getElementsByTagName(parentTag)[0].appendChild(fragment);
    +            }
    +          }
    +        
    +
    • Test your app from http://localhost:4200 : the script should be executed without error message Check that your DOM contains the Content-Security-Policy meta tag with the nonce and that the inline scripting uses this nonce.

    Hint: Get http://localhost:4200 and see the bottom of the "home" page. Normally a message is displayed. Check the browser's console. No error message. If you modify the nonce or remove it from script block (see app.component.ts), the script is not executed, and the message is no more displayed. An error appears in the console.

    Note : strict-dynamic source expression specifies that the trust is explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script. At the same time, any whitelist or source expressions such as self or unsafe-inline will be ignored.

    Last Updated:
    Contributors: Nourredine K
    + + + diff --git a/index.html b/index.html new file mode 100644 index 00000000..599ba58d --- /dev/null +++ b/index.html @@ -0,0 +1,33 @@ + + + + + + + + + Angular Security Training + + + + +
    Angular Security Training

    Angular Security Training

    Learn how to prevent common threats in your Angular web application !

    Get started →

    Common Threats

    Discover the common threats and vulnerabilities your Angular application can have and how to detect them.

    Security Hardening

    Learn how to prevent and defend your Angular application against common threats.

    Continuous Prevention

    Apply these guidelines to keep your Angular application healthy against any future threats.

    + + + diff --git a/introduction/index.html b/introduction/index.html new file mode 100644 index 00000000..2fdf98c5 --- /dev/null +++ b/introduction/index.html @@ -0,0 +1,33 @@ + + + + + + + + + 1 Introduction | Angular Security Training + + + + + + + + diff --git a/logo.svg b/logo.svg new file mode 100644 index 00000000..629e0f2d --- /dev/null +++ b/logo.svg @@ -0,0 +1,33 @@ + + + +Created with Fabric.js 3.6.3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/prerequisites/index.html b/prerequisites/index.html new file mode 100644 index 00000000..d85c32c4 --- /dev/null +++ b/prerequisites/index.html @@ -0,0 +1,37 @@ + + + + + + + + + Prerequisites | Angular Security Training + + + + +

    Prerequisites

    • Basic knowledge of Angular.
    • OWASP Top 10 Vulnerabilities.

    Development environment for practical work

    TIP

    If it is allowed by your local security policy, you can work under a Linux VM, to get a better development experience. This is not mandatory for this training though.

    To implement practical works on your local system, you need the following:

    • Node.js v16.10+ (https://nodejs.org/en/download/)
    • npm package manager (installed with Node.js)
    • Maven 3 (https://maven.apache.org/download.cgi)
    • Java 11 (https://www.azul.com/downloads/?architecture=x86-64-bit&package=jdk)
    • Visual Studio Code (https://code.visualstudio.com/) or any IDE

    Practical work: Setup your project

    The project used for practical works stands as the webapp backend and hosts the RESTful api of the ecommerce website based on Spring stack. It is composed of two prject:

    • A server created using Spring
    • A client created using Angular

    Get started

    • Clone the project

      git clone https://github.com/worldline/angular-security-training.git
      +
    • Go to first practical work

      cd angular-security-training/pw/pw-jwt-oauth
      +
    • Open the "server" folder in a terminal and execute "mvn"

      Optional : only in case of error on jdk version during execution, export JAVA_HOME variable and re-execute mvn

      export JAVA_HOME='PATH_TO_JDK11_ROOT_FOLDER';
      +mvn
      +
    • Open the "client" folder in another terminal

    • npm install (only for the first time)

    • npm start

    • Open the url "http://localhost:4200" in a browser

    Last Updated:
    Contributors: Nourredine K
    + + +