@authhero/widget 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ import{t,p as e,H as r,c as i,h as o}from"./p-BbVVe_wV.js";const a=e(class extends r{constructor(t){super(),!1!==t&&this.__registerHost(),this.__attachShadow(),this.fieldChange=i(this,"fieldChange"),this.buttonClick=i(this,"buttonClick")}component;value;disabled=!1;passwordVisible=!1;fieldChange;buttonClick;handleInput=t=>{this.fieldChange.emit({id:this.component.id,value:t.target.value})};handleCheckbox=t=>{this.fieldChange.emit({id:this.component.id,value:t.target.checked?"true":"false"})};sanitizeForCssToken(t){return t.toLowerCase().replace(/[^a-z0-9-]/g,"-").replace(/-+/g,"-").replace(/^-|-$/g,"")}handleButtonClick=(t,e,r)=>{"submit"!==e&&t.preventDefault(),this.buttonClick.emit({id:this.component.id,type:e,value:r})};togglePasswordVisibility=()=>{this.passwordVisible=!this.passwordVisible};getErrors(){const t=this.component;return t.messages?.filter((t=>"error"===t.type))||[]}renderFloatingLabel(t,e,r,i){return t?o("label",{class:{"input-label":!0,floating:!!i},part:"label",htmlFor:e},t,r&&o("span",{class:"required"},"*")):null}renderLabel(t,e,r){return t?o("label",{class:"input-label",part:"label",htmlFor:e},t,r&&o("span",{class:"required"},"*")):null}renderPasswordToggle(){return this.passwordVisible?o("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},o("path",{d:"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"}),o("line",{x1:"1",y1:"1",x2:"23",y2:"23"})):o("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2","stroke-linecap":"round","stroke-linejoin":"round"},o("path",{d:"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"}),o("circle",{cx:"12",cy:"12",r:"3"}))}renderErrors(){return this.getErrors().map((t=>o("span",{class:"error-text",part:"error-text",key:t.id??t.text},t.text)))}renderHint(t){return t?o("span",{class:"helper-text",part:"helper-text"},t):null}renderDivider(){return o("hr",{class:"divider",part:"divider"})}renderHtml(t){return o("div",{class:"html-content",part:"html-content",innerHTML:t.config?.content??""})}renderImage(t){const{src:e,alt:r,width:i,height:a}=t.config??{};return e?o("img",{class:"image",part:"image",src:e,alt:r??"",width:i,height:a,loading:"lazy"}):null}renderRichText(t){return o("div",{class:"rich-text",part:"rich-text",innerHTML:t.config?.content??""})}renderNextButton(t){return o("button",{type:"submit",class:"btn btn-primary",part:"button button-primary",disabled:this.disabled,onClick:t=>this.handleButtonClick(t,"submit","next")},t.config.text??"Continue")}renderPreviousButton(t){return o("button",{type:"button",class:"btn btn-secondary",part:"button button-secondary",disabled:this.disabled,onClick:t=>this.handleButtonClick(t,"previous","back")},t.config.text??"Back")}renderJumpButton(t){return o("button",{type:"button",class:"btn btn-link",part:"button button-link",disabled:this.disabled,onClick:e=>this.handleButtonClick(e,"jump",t.config.target_step)},t.config.text??"Go")}renderResendButton(t){return o("button",{type:"button",class:"btn btn-link",part:"button button-link",disabled:this.disabled,onClick:e=>this.handleButtonClick(e,"resend",t.config.resend_action)},t.config.text??"Resend")}renderTextField(t){const e=`input-${t.id}`,r=this.getErrors(),{multiline:i,max_length:a}=t.config??{},s=!!(this.value&&this.value.length>0);return i?o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("textarea",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input textarea",name:t.id,placeholder:" ",required:t.required,disabled:this.disabled,maxLength:a,onInput:this.handleInput},this.value??""),this.renderErrors(),0===r.length&&this.renderHint(t.hint)):o("div",{class:"input-wrapper",part:"input-wrapper"},o("div",{class:"input-container"},o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:t.sensitive?"password":"text",name:t.id,value:this.value??"",placeholder:" ",required:t.required,disabled:this.disabled,maxLength:a,onInput:this.handleInput}),this.renderFloatingLabel(t.label,e,t.required,s)),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderEmailField(t){const e=`input-${t.id}`,r=this.getErrors(),i=!!(this.value&&this.value.length>0);return o("div",{class:"input-wrapper",part:"input-wrapper"},o("div",{class:"input-container"},o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:"email",name:t.id,value:this.value??"",placeholder:" ",required:t.required,disabled:this.disabled,autocomplete:"email",onInput:this.handleInput}),this.renderFloatingLabel(t.label,e,t.required,i)),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderPasswordField(t){const e=`input-${t.id}`,r=this.getErrors(),i=!!(this.value&&this.value.length>0),a=t.config?.forgot_password_link;return o("div",{class:"input-wrapper",part:"input-wrapper"},o("div",{class:"input-container password-container"},o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:this.passwordVisible?"text":"password",name:t.id,value:this.value??"",placeholder:" ",required:t.required,disabled:this.disabled,minLength:t.config?.min_length,autocomplete:"current-password",onInput:this.handleInput}),this.renderFloatingLabel(t.label,e,t.required,i),o("button",{type:"button",class:"password-toggle",part:"password-toggle",onClick:this.togglePasswordVisibility,"aria-label":"Toggle password visibility","aria-pressed":this.passwordVisible?"true":"false"},this.renderPasswordToggle())),this.renderErrors(),0===r.length&&this.renderHint(t.hint),a&&o("div",{class:"field-link",part:"field-link"},o("a",{href:a,class:"link",part:"link"},"Forgot password?")))}renderNumberField(t){const e=`input-${t.id}`,r=this.getErrors(),{placeholder:i,min:a,max:s,step:n}=t.config??{};return o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:"number",name:t.id,value:this.value??"",placeholder:i,required:t.required,disabled:this.disabled,min:a,max:s,step:n,onInput:this.handleInput}),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderTelField(t){const e=`input-${t.id}`,r=this.getErrors();return o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:"tel",name:t.id,value:this.value??"",placeholder:t.config?.placeholder,required:t.required,disabled:this.disabled,autocomplete:"tel",onInput:this.handleInput}),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderUrlField(t){const e=`input-${t.id}`,r=this.getErrors();return o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:"url",name:t.id,value:this.value??"",placeholder:t.config?.placeholder,required:t.required,disabled:this.disabled,onInput:this.handleInput}),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderDateField(t){const e=`input-${t.id}`,r=this.getErrors(),{min:i,max:a}=t.config??{};return o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("input",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input",type:"date",name:t.id,value:this.value??"",required:t.required,disabled:this.disabled,min:i,max:a,onInput:this.handleInput}),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderBooleanField(t){return o("label",{class:"checkbox-wrapper",part:"checkbox-wrapper"},o("input",{type:"checkbox",part:"checkbox",name:t.id,checked:"true"===this.value||!0===t.config?.default_value,required:t.required,disabled:this.disabled,onChange:this.handleCheckbox}),o("span",{class:"checkbox-label",part:"checkbox-label"},t.label))}renderLegalField(t){const e=t.config?.text??t.label??"",r=!0===t.config?.html;return o("label",{class:"checkbox-wrapper",part:"checkbox-wrapper"},o("input",{type:"checkbox",part:"checkbox",name:t.id,checked:"true"===this.value,required:t.required,disabled:this.disabled,onChange:this.handleCheckbox}),r?o("span",{class:"checkbox-label",part:"checkbox-label",innerHTML:e}):o("span",{class:"checkbox-label",part:"checkbox-label"},e))}renderDropdownField(t){const e=`input-${t.id}`,r=this.getErrors(),{options:i,placeholder:a}=t.config??{};return o("div",{class:"input-wrapper",part:"input-wrapper"},this.renderLabel(t.label,e,t.required),o("select",{id:e,class:{"input-field":!0,"has-error":r.length>0},part:"input select",name:t.id,required:t.required,disabled:this.disabled,onChange:this.handleInput},a&&o("option",{value:"",disabled:!0,selected:!this.value},a),i?.map((t=>o("option",{value:t.value,selected:this.value===t.value,key:t.value},t.label)))),this.renderErrors(),0===r.length&&this.renderHint(t.hint))}renderChoiceField(t){const e=this.getErrors(),{options:r,display:i}=t.config??{},a="checkbox"===i,s=a?"checkbox":"radio";return o("div",{class:"choice-wrapper",part:"choice-wrapper"},t.label&&o("span",{class:"choice-label",part:"choice-label"},t.label,t.required&&o("span",{class:"required"},"*")),o("div",{class:"choice-options",part:"choice-options"},r?.map((e=>o("label",{class:"choice-option",part:"choice-option",key:e.value},o("input",{type:s,part:s,name:t.id,value:e.value,checked:this.value===e.value,required:t.required&&!a,disabled:this.disabled,onChange:this.handleInput}),o("span",null,e.label))))),this.renderErrors(),0===e.length&&this.renderHint(t.hint))}renderSocialField(t){const e=t.config?.providers??[],r=t.config?.provider_details,i=new Map(r?.map((t=>[t.name,t]))??[]),a=["google-oauth2","google","facebook","apple","github","microsoft","windowslive","linkedin","vipps"];return o("div",{class:"social-buttons",part:"social-buttons"},e.map((t=>{const e=this.sanitizeForCssToken(t);return o("button",{type:"button",class:`btn btn-secondary btn-social btn-social-${e}`,part:`button button-secondary button-social button-social-${e}`,"data-provider":t,disabled:this.disabled,onClick:e=>this.handleButtonClick(e,"SOCIAL",t),key:t},(t=>{const e=i.get(t);if(e?.icon_url)return o("img",{class:"social-icon",src:e.icon_url,alt:e.display_name||t});const r=((t,e)=>{const r=t.toLowerCase(),i=e?.toLowerCase();if(i&&a.includes(i))return i;if(a.includes(r))return r;for(const t of a)if(r.includes(t))return t;return null})(t,e?.strategy)||t.toLowerCase();return"google-oauth2"===r||"google"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z",fill:"#4285F4"}),o("path",{d:"M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z",fill:"#34A853"}),o("path",{d:"M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z",fill:"#FBBC05"}),o("path",{d:"M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z",fill:"#EA4335"})):"facebook"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z",fill:"#1877F2"})):"apple"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M17.05 20.28c-.98.95-2.05.8-3.08.35-1.09-.46-2.09-.48-3.24 0-1.44.62-2.2.44-3.06-.35C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.8 1.18-.24 2.31-.93 3.57-.84 1.51.12 2.65.72 3.4 1.8-3.12 1.87-2.38 5.98.48 7.13-.57 1.5-1.31 2.99-2.54 4.09l.01-.01zM12.03 7.25c-.15-2.23 1.66-4.07 3.74-4.25.29 2.58-2.34 4.5-3.74 4.25z",fill:"#000000"})):"github"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M12 0C5.374 0 0 5.373 0 12c0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z",fill:"#181717"})):"microsoft"===r||"windowslive"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M0 0h11.377v11.372H0V0z",fill:"#f25022"}),o("path",{d:"M12.623 0H24v11.372H12.623V0z",fill:"#7fba00"}),o("path",{d:"M0 12.623h11.377V24H0v-11.377z",fill:"#00a4ef"}),o("path",{d:"M12.623 12.623H24V24H12.623v-11.377z",fill:"#ffb900"})):"linkedin"===r?o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("path",{d:"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",fill:"#0A66C2"})):"vipps"===r?o("svg",{class:"social-icon",viewBox:"0 0 48 48",xmlns:"http://www.w3.org/2000/svg"},o("path",{fill:"#FF5B24",d:"M3.5,8h41c1.9,0,3.5,1.6,3.5,3.5v25c0,1.9-1.6,3.5-3.5,3.5h-41C1.6,40,0,38.4,0,36.5v-25C0,9.6,1.6,8,3.5,8z"}),o("path",{fill:"#FFFFFF",d:"M27.9,20.3c1.4,0,2.6-1,2.6-2.5c0-1.5-1.2-2.5-2.6-2.5c-1.4,0-2.6,1-2.6,2.5C25.3,19.2,26.5,20.3,27.9,20.3z"}),o("path",{fill:"#FFFFFF",d:"M31.2,24.4c-1.7,2.2-3.5,3.8-6.7,3.8c-3.2,0-5.8-2-7.7-4.8c-0.8-1.2-2-1.4-2.9-0.8c-0.8,0.6-1,1.8-0.3,2.9c2.7,4.1,6.5,6.6,10.9,6.6c4,0,7.2-2,9.6-5.2c0.9-1.2,0.9-2.5,0-3.1C33.3,22.9,32.1,23.2,31.2,24.4z"})):o("svg",{class:"social-icon",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg"},o("circle",{cx:"12",cy:"12",r:"10",fill:"none",stroke:"#666","stroke-width":"2"}),o("path",{d:"M2 12h20M12 2c-2.5 2.5-4 5.5-4 10s1.5 7.5 4 10c2.5-2.5 4-5.5 4-10s-1.5-7.5-4-10z",fill:"none",stroke:"#666","stroke-width":"2"}))})(t),o("span",{part:"button-social-text"},`Continue with ${(t=>{const e=i.get(t);return e?.display_name?e.display_name:{"google-oauth2":"Google",facebook:"Facebook",twitter:"Twitter",github:"GitHub",linkedin:"LinkedIn",apple:"Apple",microsoft:"Microsoft",windowslive:"Microsoft",amazon:"Amazon",dropbox:"Dropbox",bitbucket:"Bitbucket",spotify:"Spotify",slack:"Slack",discord:"Discord",twitch:"Twitch",line:"LINE",shopify:"Shopify",paypal:"PayPal","paypal-sandbox":"PayPal",box:"Box",salesforce:"Salesforce","salesforce-sandbox":"Salesforce",yahoo:"Yahoo",auth0:"Auth0",vipps:"Vipps"}[t.toLowerCase()]||t.split("-").map((t=>t.charAt(0).toUpperCase()+t.slice(1))).join(" ")})(t)}`))})))}render(){if(!this.component)return null;if(!1===this.component.visible)return null;switch(this.component.type){case"DIVIDER":return this.renderDivider();case"HTML":return this.renderHtml(this.component);case"IMAGE":return this.renderImage(this.component);case"RICH_TEXT":return this.renderRichText(this.component);case"NEXT_BUTTON":return this.renderNextButton(this.component);case"PREVIOUS_BUTTON":return this.renderPreviousButton(this.component);case"JUMP_BUTTON":return this.renderJumpButton(this.component);case"RESEND_BUTTON":return this.renderResendButton(this.component);case"TEXT":return this.renderTextField(this.component);case"EMAIL":return this.renderEmailField(this.component);case"PASSWORD":return this.renderPasswordField(this.component);case"NUMBER":return this.renderNumberField(this.component);case"TEL":return this.renderTelField(this.component);case"URL":return this.renderUrlField(this.component);case"DATE":return this.renderDateField(this.component);case"BOOLEAN":return this.renderBooleanField(this.component);case"LEGAL":return this.renderLegalField(this.component);case"DROPDOWN":return this.renderDropdownField(this.component);case"CHOICE":return this.renderChoiceField(this.component);case"SOCIAL":return this.renderSocialField(this.component);case"AUTH0_VERIFIABLE_CREDENTIALS":case"GMAPS_ADDRESS":case"RECAPTCHA":return console.warn(`Widget component "${this.component.type}" not yet implemented`),null;case"CARDS":case"CUSTOM":case"FILE":case"PAYMENT":return console.warn(`Component "${this.component.type}" not yet implemented`),null;default:return console.warn(`Unknown component type: ${this.component.type}`),null}}static get style(){return':host{display:block}.input-wrapper{display:flex;flex-direction:column;position:relative;margin-bottom:16px}.input-container{position:relative;width:100%}.input-label{position:absolute;left:16px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:var(--ah-font-weight-label, 400);color:var(--ah-color-text-muted, #65676e);pointer-events:none;transition:all 0.15s ease-out;background-color:transparent;padding:0;z-index:1}.input-label.floating,.input-field:focus+.input-label,.input-field:not(:placeholder-shown)+.input-label{top:-8px;transform:translateY(0);font-size:12px;background-color:var(--ah-color-bg, #ffffff);padding:0 4px;left:12px;color:var(--ah-color-text-muted, #65676e)}.input-field:focus+.input-label{color:var(--ah-color-primary, #635dff)}.required{color:var(--ah-color-error, #d03c38);margin-left:2px}.input-field{width:100%;padding:16px;font-size:16px;font-family:inherit;color:var(--ah-color-text, #1e212a);background-color:var(--ah-color-input-bg, #ffffff);border:1px solid var(--ah-color-border, #c9cace);border-radius:var(--ah-input-radius, 3px);outline:none;transition:border-color 0.15s ease-out, box-shadow 0.15s ease-out;box-sizing:border-box}.input-field::placeholder{color:transparent}.input-field:hover{border-color:var(--ah-color-border-hover, #65676e)}.input-field:focus{border-color:var(--ah-color-primary, #635dff);box-shadow:inset 0 0 0 1px var(--ah-color-primary, #635dff)}.input-field.has-error{border-color:var(--ah-color-error, #d03c38)}.input-field.has-error:focus{box-shadow:inset 0 0 0 1px var(--ah-color-error, #d03c38)}.input-field:disabled{background-color:var(--ah-color-bg-disabled, #f5f5f5);border-color:var(--ah-color-border-disabled, #e0e1e3);cursor:not-allowed;opacity:0.7}.password-container{position:relative;display:flex;align-items:center}.password-container .input-field{padding-right:48px}.password-toggle{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;padding:4px;display:flex;align-items:center;justify-content:center;color:var(--ah-color-text-muted, #65676e);transition:color 0.15s ease}.password-toggle:hover{color:var(--ah-color-text, #1e212a)}.password-toggle svg{width:20px;height:20px}.error-text{font-size:12px;color:var(--ah-color-error, #d03c38);margin-top:4px;line-height:1.4}.helper-text{font-size:12px;color:var(--ah-color-text-muted, #65676e);margin-top:4px;line-height:1.4}.field-link{display:block;text-align:left;margin-top:8px;margin-bottom:16px}.field-link a{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400)}.field-link a:hover{text-decoration:underline}.checkbox-wrapper{display:flex;align-items:flex-start;gap:10px;cursor:pointer;margin-bottom:16px}.checkbox-wrapper input[type="checkbox"]{width:18px;height:18px;margin:0;accent-color:var(--ah-color-primary, #635dff);cursor:pointer;flex-shrink:0}.checkbox-label{font-size:14px;color:var(--ah-color-text, #1e212a);line-height:1.5}.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;width:100%;padding:14px 20px;font-size:16px;font-weight:var(--ah-font-weight-btn, 400);font-family:inherit;line-height:1.25;text-align:center;text-decoration:none;border:none;border-radius:var(--ah-btn-radius, 3px);cursor:pointer;transition:background-color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;box-sizing:border-box}.btn:disabled{opacity:0.6;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(0.98)}.btn:focus-visible{outline:2px solid var(--ah-color-primary, #635dff);outline-offset:2px}.btn-primary{background-color:var(--ah-color-primary, #635dff);color:var(--ah-color-text-on-primary, #ffffff);margin-top:12px}.btn-primary:not(:disabled):hover{filter:brightness(0.85)}.btn-secondary{background-color:var(--ah-color-bg, #ffffff);color:var(--ah-color-text, #1e212a);border:1px solid var(--ah-color-border, #c9cace)}.btn-secondary:not(:disabled):hover{background-color:var(--ah-color-bg-hover, #f5f5f5);border-color:var(--ah-color-border-hover, #65676e)}.btn-link{background:none;border:none;color:var(--ah-color-link, #635dff);padding:8px 0;font-weight:var(--ah-font-weight-link, 400);text-decoration:none}.btn-link:hover{text-decoration:underline}.social-buttons{display:flex;flex-direction:column;gap:12px}.btn-social{display:flex;align-items:center;justify-content:center;gap:12px}.social-icon{width:20px;height:20px;flex-shrink:0}@media (max-width: 480px){.social-buttons:has(.btn-social:nth-child(3)){flex-direction:row;flex-wrap:nowrap;justify-content:stretch;gap:8px}.social-buttons:has(.btn-social:nth-child(3)) .btn-social{width:auto;min-width:0;padding:12px;flex:1 1 0}.social-buttons:has(.btn-social:nth-child(3)) .btn-social span{display:none}.social-buttons:has(.btn-social:nth-child(3)) .social-icon{width:24px;height:24px}}.btn-icon{width:20px;height:20px;flex-shrink:0}.btn-icon img{width:100%;height:100%;object-fit:contain}.text-title{font-size:20px;font-weight:400;color:var(--ah-color-text, #1e212a);margin:8px 0;line-height:1.3}.text-title.text-success{color:var(--ah-color-success, #13a769)}.text-description{font-size:14px;color:var(--ah-color-text-muted, #65676e);margin:4px 0;line-height:1.5}.image{display:block;max-width:100%;height:auto;border-radius:4px}.image-centered{margin:0 auto 16px;width:52px;height:52px;object-fit:contain}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;transition:color 0.15s ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.node-error{padding:12px 16px;background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38);border-radius:0;font-size:14px;margin-bottom:16px}.node-success{padding:12px 16px;background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769);border-radius:0;font-size:14px;margin-bottom:16px}.divider{display:flex;align-items:center;text-align:center;margin:16px 0}.divider::before,.divider::after{content:"";flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0.5px}'}},[513,"authhero-node",{component:[16],value:[1],disabled:[4],passwordVisible:[32]}]);function s(){"undefined"!=typeof customElements&&["authhero-node"].forEach((e=>{"authhero-node"===e&&(customElements.get(t(e))||customElements.define(t(e),a))}))}s();export{a as A,s as d}
@@ -44,6 +44,17 @@ const AuthheroNode = class {
44
44
  value: target.checked ? "true" : "false",
45
45
  });
46
46
  };
47
+ /**
48
+ * Sanitize a string for use in CSS class names and part tokens.
49
+ * Replaces spaces and special characters with hyphens, converts to lowercase.
50
+ */
51
+ sanitizeForCssToken(value) {
52
+ return value
53
+ .toLowerCase()
54
+ .replace(/[^a-z0-9-]/g, "-") // Replace non-alphanumeric chars with hyphen
55
+ .replace(/-+/g, "-") // Collapse multiple hyphens
56
+ .replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
57
+ }
47
58
  handleButtonClick = (e, type, value) => {
48
59
  if (type !== "submit") {
49
60
  e.preventDefault();
@@ -312,7 +323,10 @@ const AuthheroNode = class {
312
323
  // Default: generic globe icon for unknown providers
313
324
  return (h("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, h("circle", { cx: "12", cy: "12", r: "10", fill: "none", stroke: "#666", "stroke-width": "2" }), h("path", { d: "M2 12h20M12 2c-2.5 2.5-4 5.5-4 10s1.5 7.5 4 10c2.5-2.5 4-5.5 4-10s-1.5-7.5-4-10z", fill: "none", stroke: "#666", "stroke-width": "2" })));
314
325
  };
315
- return (h("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => (h("button", { type: "button", class: "btn btn-secondary btn-social", part: "button button-secondary button-social", "data-provider": provider, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, getProviderIcon(provider), h("span", null, "Continue with ", getProviderDisplayName(provider)))))));
326
+ return (h("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => {
327
+ const safeProvider = this.sanitizeForCssToken(provider);
328
+ return (h("button", { type: "button", class: `btn btn-secondary btn-social btn-social-${safeProvider}`, part: `button button-secondary button-social button-social-${safeProvider}`, "data-provider": provider, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, getProviderIcon(provider), h("span", { part: "button-social-text" }, `Continue with ${getProviderDisplayName(provider)}`)));
329
+ })));
316
330
  }
317
331
  // ===========================================================================
318
332
  // Main Render
@@ -288,6 +288,139 @@ function applyCssVars(element, vars) {
288
288
  });
289
289
  }
290
290
 
291
+ /**
292
+ * Sanitize HTML to only allow safe formatting tags
293
+ *
294
+ * Allowed tags:
295
+ * - <br>, <br/> - Line breaks
296
+ * - <em>, <i> - Italic
297
+ * - <strong>, <b> - Bold
298
+ * - <u> - Underline
299
+ * - <span> - Generic inline container (for styling)
300
+ * - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
301
+ *
302
+ * All other tags and attributes are stripped.
303
+ */
304
+ // Allowed tags and their allowed attributes
305
+ const ALLOWED_TAGS = {
306
+ br: [],
307
+ em: [],
308
+ i: [],
309
+ strong: [],
310
+ b: [],
311
+ u: [],
312
+ span: ["class"],
313
+ a: ["href", "class"],
314
+ };
315
+ /**
316
+ * Sanitize HTML string to only allow safe formatting tags
317
+ *
318
+ * @param html - The HTML string to sanitize
319
+ * @returns Sanitized HTML string safe for innerHTML
320
+ */
321
+ function sanitizeHtml(html) {
322
+ if (!html)
323
+ return "";
324
+ // If no < character present, return as-is (optimization)
325
+ // Must check for any < to prevent bypassing sanitization with malformed tags
326
+ // like "<img src=x onerror=..." which forgiving HTML parsers may still execute
327
+ if (!html.includes("<")) {
328
+ return html;
329
+ }
330
+ // Use a simple regex-based approach that's safe for our limited use case
331
+ // This avoids needing DOMParser which may not be available in all environments
332
+ let result = html;
333
+ // First, escape all HTML
334
+ result = result
335
+ .replace(/&/g, "&amp;")
336
+ .replace(/</g, "&lt;")
337
+ .replace(/>/g, "&gt;")
338
+ .replace(/"/g, "&quot;")
339
+ .replace(/'/g, "&#39;");
340
+ // Then selectively re-enable allowed tags
341
+ for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
342
+ // Self-closing tags (like <br> and <br/>)
343
+ if (tag === "br") {
344
+ result = result.replace(/&lt;br\s*\/?&gt;/gi, "<br>");
345
+ continue;
346
+ }
347
+ // Opening tags with optional attributes
348
+ const openingPattern = new RegExp(`&lt;${tag}((?:\\s+[a-z-]+(?:=&quot;[^&]*&quot;|=&#39;[^&]*&#39;)?)*)\\s*&gt;`, "gi");
349
+ result = result.replace(openingPattern, (_match, attrsStr) => {
350
+ // Parse and filter attributes
351
+ const filteredAttrs = [];
352
+ if (attrsStr) {
353
+ // Unescape the attributes string for parsing
354
+ const unescapedAttrs = attrsStr
355
+ .replace(/&quot;/g, '"')
356
+ .replace(/&#39;/g, "'")
357
+ .replace(/&amp;/g, "&")
358
+ .replace(/&lt;/g, "<")
359
+ .replace(/&gt;/g, ">");
360
+ // Extract attributes
361
+ const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
362
+ let attrMatch;
363
+ while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
364
+ const [, attrName, attrValue] = attrMatch;
365
+ if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
366
+ // For href, validate it's a safe URL
367
+ if (attrName.toLowerCase() === "href") {
368
+ if (isSafeUrl(attrValue || "")) {
369
+ filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
370
+ }
371
+ }
372
+ else {
373
+ filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
374
+ }
375
+ }
376
+ }
377
+ }
378
+ // For <a> tags, always add security attributes
379
+ if (tag === "a") {
380
+ filteredAttrs.push('target="_blank"');
381
+ filteredAttrs.push('rel="noopener noreferrer"');
382
+ }
383
+ const attrsOutput = filteredAttrs.length
384
+ ? " " + filteredAttrs.join(" ")
385
+ : "";
386
+ return `<${tag}${attrsOutput}>`;
387
+ });
388
+ // Closing tags
389
+ const closingPattern = new RegExp(`&lt;/${tag}&gt;`, "gi");
390
+ result = result.replace(closingPattern, `</${tag}>`);
391
+ }
392
+ return result;
393
+ }
394
+ /**
395
+ * Check if a URL is safe (http, https, or relative)
396
+ */
397
+ function isSafeUrl(url) {
398
+ if (!url)
399
+ return false;
400
+ // Allow relative URLs
401
+ if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
402
+ return true;
403
+ }
404
+ // Allow http and https
405
+ try {
406
+ const parsed = new URL(url, "https://example.com");
407
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
408
+ }
409
+ catch {
410
+ return false;
411
+ }
412
+ }
413
+ /**
414
+ * Escape attribute value
415
+ */
416
+ function escapeAttr(value) {
417
+ return value
418
+ .replace(/&/g, "&amp;")
419
+ .replace(/"/g, "&quot;")
420
+ .replace(/</g, "&lt;")
421
+ .replace(/>/g, "&gt;");
422
+ }
423
+
291
424
  const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
292
425
 
293
426
  const AuthheroWidget = class {
@@ -886,7 +1019,7 @@ const AuthheroWidget = class {
886
1019
  const hasDivider = components.some((c) => this.isDividerComponent(c));
887
1020
  // Get logo URL from theme.widget (takes precedence) or branding
888
1021
  const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
889
- return (h("div", { class: "widget-container", part: "container" }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), this._screen.title && (h("h1", { class: "title", part: "title" }, this._screen.title)), this._screen.description && (h("p", { class: "description", part: "description" }, this._screen.description))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, part: "form" }, h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading }))))), socialComponents.length > 0 &&
1022
+ return (h("div", { class: "widget-container", part: "container" }, h("header", { class: "widget-header", part: "header" }, logoUrl && (h("div", { class: "logo-wrapper", part: "logo-wrapper" }, h("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), this._screen.title && (h("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(this._screen.title) })), this._screen.description && (h("p", { class: "description", part: "description", innerHTML: sanitizeHtml(this._screen.description) }))), h("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (h("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (h("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), h("form", { onSubmit: this.handleSubmit, part: "form" }, h("div", { class: "form-content" }, socialComponents.length > 0 && (h("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading }))))), socialComponents.length > 0 &&
890
1023
  fieldComponents.length > 0 &&
891
1024
  hasDivider && (h("div", { class: "divider", part: "divider" }, h("span", { class: "divider-text" }, "Or"))), h("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (h("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading })))))), this._screen.links && this._screen.links.length > 0 && (h("div", { class: "links", part: "links" }, this._screen.links.map((link) => (h("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (h("span", null, link.text, " ", h("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
892
1025
  id: link.id,
@@ -35,6 +35,11 @@ export declare class AuthheroNode {
35
35
  }>;
36
36
  private handleInput;
37
37
  private handleCheckbox;
38
+ /**
39
+ * Sanitize a string for use in CSS class names and part tokens.
40
+ * Replaces spaces and special characters with hyphens, converts to lowercase.
41
+ */
42
+ private sanitizeForCssToken;
38
43
  private handleButtonClick;
39
44
  private togglePasswordVisibility;
40
45
  /**
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Sanitize HTML to only allow safe formatting tags
3
+ *
4
+ * Allowed tags:
5
+ * - <br>, <br/> - Line breaks
6
+ * - <em>, <i> - Italic
7
+ * - <strong>, <b> - Bold
8
+ * - <u> - Underline
9
+ * - <span> - Generic inline container (for styling)
10
+ * - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
11
+ *
12
+ * All other tags and attributes are stripped.
13
+ */
14
+ /**
15
+ * Sanitize HTML string to only allow safe formatting tags
16
+ *
17
+ * @param html - The HTML string to sanitize
18
+ * @returns Sanitized HTML string safe for innerHTML
19
+ */
20
+ export declare function sanitizeHtml(html: string | undefined | null): string;
package/hydrate/index.js CHANGED
@@ -5015,6 +5015,17 @@ class AuthheroNode {
5015
5015
  value: target.checked ? "true" : "false",
5016
5016
  });
5017
5017
  };
5018
+ /**
5019
+ * Sanitize a string for use in CSS class names and part tokens.
5020
+ * Replaces spaces and special characters with hyphens, converts to lowercase.
5021
+ */
5022
+ sanitizeForCssToken(value) {
5023
+ return value
5024
+ .toLowerCase()
5025
+ .replace(/[^a-z0-9-]/g, "-") // Replace non-alphanumeric chars with hyphen
5026
+ .replace(/-+/g, "-") // Collapse multiple hyphens
5027
+ .replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
5028
+ }
5018
5029
  handleButtonClick = (e, type, value) => {
5019
5030
  if (type !== "submit") {
5020
5031
  e.preventDefault();
@@ -5283,7 +5294,10 @@ class AuthheroNode {
5283
5294
  // Default: generic globe icon for unknown providers
5284
5295
  return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("circle", { cx: "12", cy: "12", r: "10", fill: "none", stroke: "#666", "stroke-width": "2" }), hAsync("path", { d: "M2 12h20M12 2c-2.5 2.5-4 5.5-4 10s1.5 7.5 4 10c2.5-2.5 4-5.5 4-10s-1.5-7.5-4-10z", fill: "none", stroke: "#666", "stroke-width": "2" })));
5285
5296
  };
5286
- return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => (hAsync("button", { type: "button", class: "btn btn-secondary btn-social", part: "button button-secondary button-social", "data-provider": provider, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, getProviderIcon(provider), hAsync("span", null, "Continue with ", getProviderDisplayName(provider)))))));
5297
+ return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => {
5298
+ const safeProvider = this.sanitizeForCssToken(provider);
5299
+ return (hAsync("button", { type: "button", class: `btn btn-secondary btn-social btn-social-${safeProvider}`, part: `button button-secondary button-social button-social-${safeProvider}`, "data-provider": provider, disabled: this.disabled, onClick: (e) => this.handleButtonClick(e, "SOCIAL", provider), key: provider }, getProviderIcon(provider), hAsync("span", { part: "button-social-text" }, `Continue with ${getProviderDisplayName(provider)}`)));
5300
+ })));
5287
5301
  }
5288
5302
  // ===========================================================================
5289
5303
  // Main Render
@@ -5661,6 +5675,139 @@ function applyCssVars(element, vars) {
5661
5675
  });
5662
5676
  }
5663
5677
 
5678
+ /**
5679
+ * Sanitize HTML to only allow safe formatting tags
5680
+ *
5681
+ * Allowed tags:
5682
+ * - <br>, <br/> - Line breaks
5683
+ * - <em>, <i> - Italic
5684
+ * - <strong>, <b> - Bold
5685
+ * - <u> - Underline
5686
+ * - <span> - Generic inline container (for styling)
5687
+ * - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
5688
+ *
5689
+ * All other tags and attributes are stripped.
5690
+ */
5691
+ // Allowed tags and their allowed attributes
5692
+ const ALLOWED_TAGS = {
5693
+ br: [],
5694
+ em: [],
5695
+ i: [],
5696
+ strong: [],
5697
+ b: [],
5698
+ u: [],
5699
+ span: ["class"],
5700
+ a: ["href", "class"],
5701
+ };
5702
+ /**
5703
+ * Sanitize HTML string to only allow safe formatting tags
5704
+ *
5705
+ * @param html - The HTML string to sanitize
5706
+ * @returns Sanitized HTML string safe for innerHTML
5707
+ */
5708
+ function sanitizeHtml(html) {
5709
+ if (!html)
5710
+ return "";
5711
+ // If no < character present, return as-is (optimization)
5712
+ // Must check for any < to prevent bypassing sanitization with malformed tags
5713
+ // like "<img src=x onerror=..." which forgiving HTML parsers may still execute
5714
+ if (!html.includes("<")) {
5715
+ return html;
5716
+ }
5717
+ // Use a simple regex-based approach that's safe for our limited use case
5718
+ // This avoids needing DOMParser which may not be available in all environments
5719
+ let result = html;
5720
+ // First, escape all HTML
5721
+ result = result
5722
+ .replace(/&/g, "&amp;")
5723
+ .replace(/</g, "&lt;")
5724
+ .replace(/>/g, "&gt;")
5725
+ .replace(/"/g, "&quot;")
5726
+ .replace(/'/g, "&#39;");
5727
+ // Then selectively re-enable allowed tags
5728
+ for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
5729
+ // Self-closing tags (like <br> and <br/>)
5730
+ if (tag === "br") {
5731
+ result = result.replace(/&lt;br\s*\/?&gt;/gi, "<br>");
5732
+ continue;
5733
+ }
5734
+ // Opening tags with optional attributes
5735
+ const openingPattern = new RegExp(`&lt;${tag}((?:\\s+[a-z-]+(?:=&quot;[^&]*&quot;|=&#39;[^&]*&#39;)?)*)\\s*&gt;`, "gi");
5736
+ result = result.replace(openingPattern, (_match, attrsStr) => {
5737
+ // Parse and filter attributes
5738
+ const filteredAttrs = [];
5739
+ if (attrsStr) {
5740
+ // Unescape the attributes string for parsing
5741
+ const unescapedAttrs = attrsStr
5742
+ .replace(/&quot;/g, '"')
5743
+ .replace(/&#39;/g, "'")
5744
+ .replace(/&amp;/g, "&")
5745
+ .replace(/&lt;/g, "<")
5746
+ .replace(/&gt;/g, ">");
5747
+ // Extract attributes
5748
+ const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
5749
+ let attrMatch;
5750
+ while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
5751
+ const [, attrName, attrValue] = attrMatch;
5752
+ if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
5753
+ // For href, validate it's a safe URL
5754
+ if (attrName.toLowerCase() === "href") {
5755
+ if (isSafeUrl(attrValue || "")) {
5756
+ filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
5757
+ }
5758
+ }
5759
+ else {
5760
+ filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
5761
+ }
5762
+ }
5763
+ }
5764
+ }
5765
+ // For <a> tags, always add security attributes
5766
+ if (tag === "a") {
5767
+ filteredAttrs.push('target="_blank"');
5768
+ filteredAttrs.push('rel="noopener noreferrer"');
5769
+ }
5770
+ const attrsOutput = filteredAttrs.length
5771
+ ? " " + filteredAttrs.join(" ")
5772
+ : "";
5773
+ return `<${tag}${attrsOutput}>`;
5774
+ });
5775
+ // Closing tags
5776
+ const closingPattern = new RegExp(`&lt;/${tag}&gt;`, "gi");
5777
+ result = result.replace(closingPattern, `</${tag}>`);
5778
+ }
5779
+ return result;
5780
+ }
5781
+ /**
5782
+ * Check if a URL is safe (http, https, or relative)
5783
+ */
5784
+ function isSafeUrl(url) {
5785
+ if (!url)
5786
+ return false;
5787
+ // Allow relative URLs
5788
+ if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
5789
+ return true;
5790
+ }
5791
+ // Allow http and https
5792
+ try {
5793
+ const parsed = new URL(url, "https://example.com");
5794
+ return parsed.protocol === "http:" || parsed.protocol === "https:";
5795
+ }
5796
+ catch {
5797
+ return false;
5798
+ }
5799
+ }
5800
+ /**
5801
+ * Escape attribute value
5802
+ */
5803
+ function escapeAttr(value) {
5804
+ return value
5805
+ .replace(/&/g, "&amp;")
5806
+ .replace(/"/g, "&quot;")
5807
+ .replace(/</g, "&lt;")
5808
+ .replace(/>/g, "&gt;");
5809
+ }
5810
+
5664
5811
  const authheroWidgetCss = () => `:host{display:block;font-family:var(--ah-font-family, 'ulp-font', -apple-system, BlinkMacSystemFont, Roboto, Helvetica, sans-serif);font-size:var(--ah-font-size-base, 14px);line-height:var(--ah-line-height-base, 1.5);color:var(--ah-color-text, #1e212a);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.widget-container{max-width:var(--ah-widget-max-width, 400px);width:100%;margin:0 auto;background-color:var(--ah-color-bg, #ffffff);border-radius:var(--ah-widget-radius, 5px);box-shadow:var(--ah-widget-shadow, 0 4px 22px 0 rgba(0, 0, 0, 0.11));box-sizing:border-box}.widget-header{padding:var(--ah-header-padding, 40px 48px 24px)}.widget-body{padding:var(--ah-body-padding, 0 48px 40px)}.logo-wrapper{display:var(--ah-logo-display, flex);justify-content:var(--ah-logo-align, center);margin-bottom:8px}.logo{display:block;height:var(--ah-logo-height, 52px);max-width:100%;width:auto;object-fit:contain}.title{font-size:var(--ah-font-size-title, 24px);font-weight:var(--ah-font-weight-title, 700);text-align:var(--ah-title-align, center);margin:var(--ah-title-margin, 24px 0 8px);color:var(--ah-color-header, #1e212a);line-height:1.2}.description{font-size:var(--ah-font-size-description, 14px);text-align:var(--ah-title-align, center);margin:var(--ah-description-margin, 0 0 8px);color:var(--ah-color-text, #1e212a);line-height:1.5}.message{padding:12px 16px;border-radius:4px;margin-bottom:16px;font-size:14px;line-height:1.5}.message-error{background-color:var(--ah-color-error-bg, #ffeaea);color:var(--ah-color-error, #d03c38);border-left:3px solid var(--ah-color-error, #d03c38)}.message-success{background-color:var(--ah-color-success-bg, #e6f9f1);color:var(--ah-color-success, #13a769);border-left:3px solid var(--ah-color-success, #13a769)}form{display:flex;flex-direction:column}.form-content{display:flex;flex-direction:column}.social-section{display:flex;flex-direction:column;gap:8px;order:var(--ah-social-order, 2)}.fields-section{display:flex;flex-direction:column;order:var(--ah-fields-order, 0)}.divider{display:flex;align-items:center;text-align:center;margin:16px 0;order:var(--ah-divider-order, 1)}.divider::before,.divider::after{content:'';flex:1;border-bottom:1px solid var(--ah-color-border-muted, #c9cace)}.divider-text{padding:0 10px;font-size:12px;font-weight:400;color:var(--ah-color-text-muted, #65676e);text-transform:uppercase;letter-spacing:0}.links{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:16px}.link-wrapper{font-size:14px;color:var(--ah-color-text, #1e212a)}.link{color:var(--ah-color-link, #635dff);text-decoration:var(--ah-link-decoration, none);font-size:14px;font-weight:var(--ah-font-weight-link, 400);transition:color 150ms ease}.link:hover{text-decoration:underline}.link:focus-visible{outline:2px solid var(--ah-color-link, #635dff);outline-offset:2px;border-radius:2px}.loading-spinner{width:32px;height:32px;margin:24px auto;border:3px solid var(--ah-color-border-muted, #e0e1e3);border-top-color:var(--ah-color-primary, #635dff);border-radius:50%;animation:spin 0.8s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.error-message{text-align:center;color:var(--ah-color-error, #d03c38);padding:16px;font-size:14px}@media (max-width: 480px){:host{display:block;width:100%;min-height:100vh;background-color:var(--ah-color-bg, #ffffff)}.widget-container{box-shadow:none;border-radius:0;max-width:none;width:100%;margin:0}.widget-header{padding:24px 16px 16px}.widget-body{padding:0 16px 24px}}`;
5665
5812
 
5666
5813
  class AuthheroWidget {
@@ -6259,7 +6406,7 @@ class AuthheroWidget {
6259
6406
  const hasDivider = components.some((c) => this.isDividerComponent(c));
6260
6407
  // Get logo URL from theme.widget (takes precedence) or branding
6261
6408
  const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
6262
- return (hAsync("div", { class: "widget-container", part: "container" }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), this._screen.title && (hAsync("h1", { class: "title", part: "title" }, this._screen.title)), this._screen.description && (hAsync("p", { class: "description", part: "description" }, this._screen.description))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, part: "form" }, hAsync("div", { class: "form-content" }, socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading }))))), socialComponents.length > 0 &&
6409
+ return (hAsync("div", { class: "widget-container", part: "container" }, hAsync("header", { class: "widget-header", part: "header" }, logoUrl && (hAsync("div", { class: "logo-wrapper", part: "logo-wrapper" }, hAsync("img", { class: "logo", part: "logo", src: logoUrl, alt: "Logo" }))), this._screen.title && (hAsync("h1", { class: "title", part: "title", innerHTML: sanitizeHtml(this._screen.title) })), this._screen.description && (hAsync("p", { class: "description", part: "description", innerHTML: sanitizeHtml(this._screen.description) }))), hAsync("div", { class: "widget-body", part: "body" }, screenErrors.map((err) => (hAsync("div", { class: "message message-error", part: "message message-error", key: err.id ?? err.text }, err.text))), screenSuccesses.map((msg) => (hAsync("div", { class: "message message-success", part: "message message-success", key: msg.id ?? msg.text }, msg.text))), hAsync("form", { onSubmit: this.handleSubmit, part: "form" }, hAsync("div", { class: "form-content" }, socialComponents.length > 0 && (hAsync("div", { class: "social-section", part: "social-section" }, socialComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading }))))), socialComponents.length > 0 &&
6263
6410
  fieldComponents.length > 0 &&
6264
6411
  hasDivider && (hAsync("div", { class: "divider", part: "divider" }, hAsync("span", { class: "divider-text" }, "Or"))), hAsync("div", { class: "fields-section", part: "fields-section" }, fieldComponents.map((component) => (hAsync("authhero-node", { key: component.id, component: component, value: this.formData[component.id], onFieldChange: (e) => this.handleInputChange(e.detail.id, e.detail.value), onButtonClick: (e) => this.handleButtonClick(e.detail), disabled: this.loading })))))), this._screen.links && this._screen.links.length > 0 && (hAsync("div", { class: "links", part: "links" }, this._screen.links.map((link) => (hAsync("span", { class: "link-wrapper", part: "link-wrapper", key: link.id ?? link.href }, link.linkText ? (hAsync("span", null, link.text, " ", hAsync("a", { href: link.href, class: "link", part: "link", onClick: (e) => this.handleLinkClick(e, {
6265
6412
  id: link.id,