@authhero/widget 0.8.6 → 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.
- package/README.md +130 -0
- package/dist/authhero-widget/authhero-widget.esm.js +1 -1
- package/dist/authhero-widget/index.esm.js +1 -1
- package/dist/authhero-widget/p-1625b214.entry.js +1 -0
- package/dist/authhero-widget/p-55e6c943.entry.js +1 -0
- package/dist/cjs/authhero-node.cjs.entry.js +70 -5
- package/dist/cjs/authhero-widget.cjs.entry.js +134 -1
- package/dist/cjs/index.cjs.js +1 -1
- package/dist/collection/components/authhero-node/authhero-node.js +71 -6
- package/dist/collection/components/authhero-widget/authhero-widget.js +4 -3
- package/dist/collection/utils/sanitize-html.js +132 -0
- package/dist/components/authhero-node.js +1 -1
- package/dist/components/authhero-widget.js +1 -1
- package/dist/components/index.js +1 -1
- package/dist/components/p-Cuu5Lfc5.js +1 -0
- package/dist/esm/authhero-node.entry.js +70 -5
- package/dist/esm/authhero-widget.entry.js +134 -1
- package/dist/esm/index.js +1 -1
- package/dist/types/components/authhero-node/authhero-node.d.ts +5 -0
- package/dist/types/utils/sanitize-html.d.ts +20 -0
- package/hydrate/index.js +204 -6
- package/hydrate/index.mjs +204 -6
- package/package.json +7 -3
- package/dist/authhero-widget/p-072b155c.entry.js +0 -1
- package/dist/authhero-widget/p-3ae71c86.entry.js +0 -1
- package/dist/components/p-C_j5g_sG.js +0 -1
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();
|
|
@@ -5174,8 +5185,48 @@ class AuthheroNode {
|
|
|
5174
5185
|
}
|
|
5175
5186
|
renderSocialField(component) {
|
|
5176
5187
|
const providers = component.config?.providers ?? [];
|
|
5188
|
+
const providerDetails = component.config?.provider_details;
|
|
5189
|
+
// Create a map of provider details for quick lookup
|
|
5190
|
+
const detailsMap = new Map(providerDetails?.map((d) => [d.name, d]) ?? []);
|
|
5191
|
+
// Known provider identifiers for icon matching
|
|
5192
|
+
const knownProviders = [
|
|
5193
|
+
"google-oauth2",
|
|
5194
|
+
"google",
|
|
5195
|
+
"facebook",
|
|
5196
|
+
"apple",
|
|
5197
|
+
"github",
|
|
5198
|
+
"microsoft",
|
|
5199
|
+
"windowslive",
|
|
5200
|
+
"linkedin",
|
|
5201
|
+
"vipps",
|
|
5202
|
+
];
|
|
5203
|
+
// Find matching known provider from name or strategy
|
|
5204
|
+
const findKnownProvider = (name, strategy) => {
|
|
5205
|
+
const nameLower = name.toLowerCase();
|
|
5206
|
+
const strategyLower = strategy?.toLowerCase();
|
|
5207
|
+
// First check exact match on strategy
|
|
5208
|
+
if (strategyLower && knownProviders.includes(strategyLower)) {
|
|
5209
|
+
return strategyLower;
|
|
5210
|
+
}
|
|
5211
|
+
// Then check exact match on name
|
|
5212
|
+
if (knownProviders.includes(nameLower)) {
|
|
5213
|
+
return nameLower;
|
|
5214
|
+
}
|
|
5215
|
+
// Check if name contains a known provider (e.g., "Vipps Login" contains "vipps")
|
|
5216
|
+
for (const known of knownProviders) {
|
|
5217
|
+
if (nameLower.includes(known)) {
|
|
5218
|
+
return known;
|
|
5219
|
+
}
|
|
5220
|
+
}
|
|
5221
|
+
return null;
|
|
5222
|
+
};
|
|
5177
5223
|
// Map provider IDs to display names
|
|
5178
5224
|
const getProviderDisplayName = (provider) => {
|
|
5225
|
+
// First check provider_details
|
|
5226
|
+
const details = detailsMap.get(provider);
|
|
5227
|
+
if (details?.display_name) {
|
|
5228
|
+
return details.display_name;
|
|
5229
|
+
}
|
|
5179
5230
|
const displayNames = {
|
|
5180
5231
|
"google-oauth2": "Google",
|
|
5181
5232
|
facebook: "Facebook",
|
|
@@ -5201,6 +5252,7 @@ class AuthheroNode {
|
|
|
5201
5252
|
"salesforce-sandbox": "Salesforce",
|
|
5202
5253
|
yahoo: "Yahoo",
|
|
5203
5254
|
auth0: "Auth0",
|
|
5255
|
+
vipps: "Vipps",
|
|
5204
5256
|
};
|
|
5205
5257
|
return (displayNames[provider.toLowerCase()] ||
|
|
5206
5258
|
provider
|
|
@@ -5208,9 +5260,16 @@ class AuthheroNode {
|
|
|
5208
5260
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
5209
5261
|
.join(" "));
|
|
5210
5262
|
};
|
|
5211
|
-
// Get provider icon SVG
|
|
5263
|
+
// Get provider icon - either from provider_details or built-in SVG
|
|
5212
5264
|
const getProviderIcon = (provider) => {
|
|
5213
|
-
|
|
5265
|
+
// First check if we have a custom icon URL from provider_details
|
|
5266
|
+
const details = detailsMap.get(provider);
|
|
5267
|
+
if (details?.icon_url) {
|
|
5268
|
+
return (hAsync("img", { class: "social-icon", src: details.icon_url, alt: details.display_name || provider }));
|
|
5269
|
+
}
|
|
5270
|
+
// Try to find a known provider from name or strategy
|
|
5271
|
+
const knownProvider = findKnownProvider(provider, details?.strategy);
|
|
5272
|
+
const p = knownProvider || provider.toLowerCase();
|
|
5214
5273
|
if (p === "google-oauth2" || p === "google") {
|
|
5215
5274
|
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" }), hAsync("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" }), hAsync("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" }), hAsync("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" })));
|
|
5216
5275
|
}
|
|
@@ -5229,10 +5288,16 @@ class AuthheroNode {
|
|
|
5229
5288
|
if (p === "linkedin") {
|
|
5230
5289
|
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" })));
|
|
5231
5290
|
}
|
|
5232
|
-
|
|
5233
|
-
|
|
5291
|
+
if (p === "vipps") {
|
|
5292
|
+
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 48 48", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" }), hAsync("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" }), hAsync("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" })));
|
|
5293
|
+
}
|
|
5294
|
+
// Default: generic globe icon for unknown providers
|
|
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" })));
|
|
5234
5296
|
};
|
|
5235
|
-
return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((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
|
+
})));
|
|
5236
5301
|
}
|
|
5237
5302
|
// ===========================================================================
|
|
5238
5303
|
// Main Render
|
|
@@ -5610,6 +5675,139 @@ function applyCssVars(element, vars) {
|
|
|
5610
5675
|
});
|
|
5611
5676
|
}
|
|
5612
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, "&")
|
|
5723
|
+
.replace(/</g, "<")
|
|
5724
|
+
.replace(/>/g, ">")
|
|
5725
|
+
.replace(/"/g, """)
|
|
5726
|
+
.replace(/'/g, "'");
|
|
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(/<br\s*\/?>/gi, "<br>");
|
|
5732
|
+
continue;
|
|
5733
|
+
}
|
|
5734
|
+
// Opening tags with optional attributes
|
|
5735
|
+
const openingPattern = new RegExp(`<${tag}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`, "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(/"/g, '"')
|
|
5743
|
+
.replace(/'/g, "'")
|
|
5744
|
+
.replace(/&/g, "&")
|
|
5745
|
+
.replace(/</g, "<")
|
|
5746
|
+
.replace(/>/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(`</${tag}>`, "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, "&")
|
|
5806
|
+
.replace(/"/g, """)
|
|
5807
|
+
.replace(/</g, "<")
|
|
5808
|
+
.replace(/>/g, ">");
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5613
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}}`;
|
|
5614
5812
|
|
|
5615
5813
|
class AuthheroWidget {
|
|
@@ -6208,7 +6406,7 @@ class AuthheroWidget {
|
|
|
6208
6406
|
const hasDivider = components.some((c) => this.isDividerComponent(c));
|
|
6209
6407
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
6210
6408
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
6211
|
-
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"
|
|
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 &&
|
|
6212
6410
|
fieldComponents.length > 0 &&
|
|
6213
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, {
|
|
6214
6412
|
id: link.id,
|
package/hydrate/index.mjs
CHANGED
|
@@ -5013,6 +5013,17 @@ class AuthheroNode {
|
|
|
5013
5013
|
value: target.checked ? "true" : "false",
|
|
5014
5014
|
});
|
|
5015
5015
|
};
|
|
5016
|
+
/**
|
|
5017
|
+
* Sanitize a string for use in CSS class names and part tokens.
|
|
5018
|
+
* Replaces spaces and special characters with hyphens, converts to lowercase.
|
|
5019
|
+
*/
|
|
5020
|
+
sanitizeForCssToken(value) {
|
|
5021
|
+
return value
|
|
5022
|
+
.toLowerCase()
|
|
5023
|
+
.replace(/[^a-z0-9-]/g, "-") // Replace non-alphanumeric chars with hyphen
|
|
5024
|
+
.replace(/-+/g, "-") // Collapse multiple hyphens
|
|
5025
|
+
.replace(/^-|-$/g, ""); // Remove leading/trailing hyphens
|
|
5026
|
+
}
|
|
5016
5027
|
handleButtonClick = (e, type, value) => {
|
|
5017
5028
|
if (type !== "submit") {
|
|
5018
5029
|
e.preventDefault();
|
|
@@ -5172,8 +5183,48 @@ class AuthheroNode {
|
|
|
5172
5183
|
}
|
|
5173
5184
|
renderSocialField(component) {
|
|
5174
5185
|
const providers = component.config?.providers ?? [];
|
|
5186
|
+
const providerDetails = component.config?.provider_details;
|
|
5187
|
+
// Create a map of provider details for quick lookup
|
|
5188
|
+
const detailsMap = new Map(providerDetails?.map((d) => [d.name, d]) ?? []);
|
|
5189
|
+
// Known provider identifiers for icon matching
|
|
5190
|
+
const knownProviders = [
|
|
5191
|
+
"google-oauth2",
|
|
5192
|
+
"google",
|
|
5193
|
+
"facebook",
|
|
5194
|
+
"apple",
|
|
5195
|
+
"github",
|
|
5196
|
+
"microsoft",
|
|
5197
|
+
"windowslive",
|
|
5198
|
+
"linkedin",
|
|
5199
|
+
"vipps",
|
|
5200
|
+
];
|
|
5201
|
+
// Find matching known provider from name or strategy
|
|
5202
|
+
const findKnownProvider = (name, strategy) => {
|
|
5203
|
+
const nameLower = name.toLowerCase();
|
|
5204
|
+
const strategyLower = strategy?.toLowerCase();
|
|
5205
|
+
// First check exact match on strategy
|
|
5206
|
+
if (strategyLower && knownProviders.includes(strategyLower)) {
|
|
5207
|
+
return strategyLower;
|
|
5208
|
+
}
|
|
5209
|
+
// Then check exact match on name
|
|
5210
|
+
if (knownProviders.includes(nameLower)) {
|
|
5211
|
+
return nameLower;
|
|
5212
|
+
}
|
|
5213
|
+
// Check if name contains a known provider (e.g., "Vipps Login" contains "vipps")
|
|
5214
|
+
for (const known of knownProviders) {
|
|
5215
|
+
if (nameLower.includes(known)) {
|
|
5216
|
+
return known;
|
|
5217
|
+
}
|
|
5218
|
+
}
|
|
5219
|
+
return null;
|
|
5220
|
+
};
|
|
5175
5221
|
// Map provider IDs to display names
|
|
5176
5222
|
const getProviderDisplayName = (provider) => {
|
|
5223
|
+
// First check provider_details
|
|
5224
|
+
const details = detailsMap.get(provider);
|
|
5225
|
+
if (details?.display_name) {
|
|
5226
|
+
return details.display_name;
|
|
5227
|
+
}
|
|
5177
5228
|
const displayNames = {
|
|
5178
5229
|
"google-oauth2": "Google",
|
|
5179
5230
|
facebook: "Facebook",
|
|
@@ -5199,6 +5250,7 @@ class AuthheroNode {
|
|
|
5199
5250
|
"salesforce-sandbox": "Salesforce",
|
|
5200
5251
|
yahoo: "Yahoo",
|
|
5201
5252
|
auth0: "Auth0",
|
|
5253
|
+
vipps: "Vipps",
|
|
5202
5254
|
};
|
|
5203
5255
|
return (displayNames[provider.toLowerCase()] ||
|
|
5204
5256
|
provider
|
|
@@ -5206,9 +5258,16 @@ class AuthheroNode {
|
|
|
5206
5258
|
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
5207
5259
|
.join(" "));
|
|
5208
5260
|
};
|
|
5209
|
-
// Get provider icon SVG
|
|
5261
|
+
// Get provider icon - either from provider_details or built-in SVG
|
|
5210
5262
|
const getProviderIcon = (provider) => {
|
|
5211
|
-
|
|
5263
|
+
// First check if we have a custom icon URL from provider_details
|
|
5264
|
+
const details = detailsMap.get(provider);
|
|
5265
|
+
if (details?.icon_url) {
|
|
5266
|
+
return (hAsync("img", { class: "social-icon", src: details.icon_url, alt: details.display_name || provider }));
|
|
5267
|
+
}
|
|
5268
|
+
// Try to find a known provider from name or strategy
|
|
5269
|
+
const knownProvider = findKnownProvider(provider, details?.strategy);
|
|
5270
|
+
const p = knownProvider || provider.toLowerCase();
|
|
5212
5271
|
if (p === "google-oauth2" || p === "google") {
|
|
5213
5272
|
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" }), hAsync("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" }), hAsync("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" }), hAsync("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" })));
|
|
5214
5273
|
}
|
|
@@ -5227,10 +5286,16 @@ class AuthheroNode {
|
|
|
5227
5286
|
if (p === "linkedin") {
|
|
5228
5287
|
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 24 24", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" })));
|
|
5229
5288
|
}
|
|
5230
|
-
|
|
5231
|
-
|
|
5289
|
+
if (p === "vipps") {
|
|
5290
|
+
return (hAsync("svg", { class: "social-icon", viewBox: "0 0 48 48", xmlns: "http://www.w3.org/2000/svg" }, hAsync("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" }), hAsync("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" }), hAsync("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" })));
|
|
5291
|
+
}
|
|
5292
|
+
// Default: generic globe icon for unknown providers
|
|
5293
|
+
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" })));
|
|
5232
5294
|
};
|
|
5233
|
-
return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) =>
|
|
5295
|
+
return (hAsync("div", { class: "social-buttons", part: "social-buttons" }, providers.map((provider) => {
|
|
5296
|
+
const safeProvider = this.sanitizeForCssToken(provider);
|
|
5297
|
+
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)}`)));
|
|
5298
|
+
})));
|
|
5234
5299
|
}
|
|
5235
5300
|
// ===========================================================================
|
|
5236
5301
|
// Main Render
|
|
@@ -5608,6 +5673,139 @@ function applyCssVars(element, vars) {
|
|
|
5608
5673
|
});
|
|
5609
5674
|
}
|
|
5610
5675
|
|
|
5676
|
+
/**
|
|
5677
|
+
* Sanitize HTML to only allow safe formatting tags
|
|
5678
|
+
*
|
|
5679
|
+
* Allowed tags:
|
|
5680
|
+
* - <br>, <br/> - Line breaks
|
|
5681
|
+
* - <em>, <i> - Italic
|
|
5682
|
+
* - <strong>, <b> - Bold
|
|
5683
|
+
* - <u> - Underline
|
|
5684
|
+
* - <span> - Generic inline container (for styling)
|
|
5685
|
+
* - <a> - Links (href attribute only, with target="_blank" and rel="noopener")
|
|
5686
|
+
*
|
|
5687
|
+
* All other tags and attributes are stripped.
|
|
5688
|
+
*/
|
|
5689
|
+
// Allowed tags and their allowed attributes
|
|
5690
|
+
const ALLOWED_TAGS = {
|
|
5691
|
+
br: [],
|
|
5692
|
+
em: [],
|
|
5693
|
+
i: [],
|
|
5694
|
+
strong: [],
|
|
5695
|
+
b: [],
|
|
5696
|
+
u: [],
|
|
5697
|
+
span: ["class"],
|
|
5698
|
+
a: ["href", "class"],
|
|
5699
|
+
};
|
|
5700
|
+
/**
|
|
5701
|
+
* Sanitize HTML string to only allow safe formatting tags
|
|
5702
|
+
*
|
|
5703
|
+
* @param html - The HTML string to sanitize
|
|
5704
|
+
* @returns Sanitized HTML string safe for innerHTML
|
|
5705
|
+
*/
|
|
5706
|
+
function sanitizeHtml(html) {
|
|
5707
|
+
if (!html)
|
|
5708
|
+
return "";
|
|
5709
|
+
// If no < character present, return as-is (optimization)
|
|
5710
|
+
// Must check for any < to prevent bypassing sanitization with malformed tags
|
|
5711
|
+
// like "<img src=x onerror=..." which forgiving HTML parsers may still execute
|
|
5712
|
+
if (!html.includes("<")) {
|
|
5713
|
+
return html;
|
|
5714
|
+
}
|
|
5715
|
+
// Use a simple regex-based approach that's safe for our limited use case
|
|
5716
|
+
// This avoids needing DOMParser which may not be available in all environments
|
|
5717
|
+
let result = html;
|
|
5718
|
+
// First, escape all HTML
|
|
5719
|
+
result = result
|
|
5720
|
+
.replace(/&/g, "&")
|
|
5721
|
+
.replace(/</g, "<")
|
|
5722
|
+
.replace(/>/g, ">")
|
|
5723
|
+
.replace(/"/g, """)
|
|
5724
|
+
.replace(/'/g, "'");
|
|
5725
|
+
// Then selectively re-enable allowed tags
|
|
5726
|
+
for (const [tag, allowedAttrs] of Object.entries(ALLOWED_TAGS)) {
|
|
5727
|
+
// Self-closing tags (like <br> and <br/>)
|
|
5728
|
+
if (tag === "br") {
|
|
5729
|
+
result = result.replace(/<br\s*\/?>/gi, "<br>");
|
|
5730
|
+
continue;
|
|
5731
|
+
}
|
|
5732
|
+
// Opening tags with optional attributes
|
|
5733
|
+
const openingPattern = new RegExp(`<${tag}((?:\\s+[a-z-]+(?:="[^&]*"|='[^&]*')?)*)\\s*>`, "gi");
|
|
5734
|
+
result = result.replace(openingPattern, (_match, attrsStr) => {
|
|
5735
|
+
// Parse and filter attributes
|
|
5736
|
+
const filteredAttrs = [];
|
|
5737
|
+
if (attrsStr) {
|
|
5738
|
+
// Unescape the attributes string for parsing
|
|
5739
|
+
const unescapedAttrs = attrsStr
|
|
5740
|
+
.replace(/"/g, '"')
|
|
5741
|
+
.replace(/'/g, "'")
|
|
5742
|
+
.replace(/&/g, "&")
|
|
5743
|
+
.replace(/</g, "<")
|
|
5744
|
+
.replace(/>/g, ">");
|
|
5745
|
+
// Extract attributes
|
|
5746
|
+
const attrPattern = /([a-z-]+)=["']([^"']*)["']/gi;
|
|
5747
|
+
let attrMatch;
|
|
5748
|
+
while ((attrMatch = attrPattern.exec(unescapedAttrs)) !== null) {
|
|
5749
|
+
const [, attrName, attrValue] = attrMatch;
|
|
5750
|
+
if (attrName && allowedAttrs.includes(attrName.toLowerCase())) {
|
|
5751
|
+
// For href, validate it's a safe URL
|
|
5752
|
+
if (attrName.toLowerCase() === "href") {
|
|
5753
|
+
if (isSafeUrl(attrValue || "")) {
|
|
5754
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
5755
|
+
}
|
|
5756
|
+
}
|
|
5757
|
+
else {
|
|
5758
|
+
filteredAttrs.push(`${attrName}="${escapeAttr(attrValue || "")}"`);
|
|
5759
|
+
}
|
|
5760
|
+
}
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
// For <a> tags, always add security attributes
|
|
5764
|
+
if (tag === "a") {
|
|
5765
|
+
filteredAttrs.push('target="_blank"');
|
|
5766
|
+
filteredAttrs.push('rel="noopener noreferrer"');
|
|
5767
|
+
}
|
|
5768
|
+
const attrsOutput = filteredAttrs.length
|
|
5769
|
+
? " " + filteredAttrs.join(" ")
|
|
5770
|
+
: "";
|
|
5771
|
+
return `<${tag}${attrsOutput}>`;
|
|
5772
|
+
});
|
|
5773
|
+
// Closing tags
|
|
5774
|
+
const closingPattern = new RegExp(`</${tag}>`, "gi");
|
|
5775
|
+
result = result.replace(closingPattern, `</${tag}>`);
|
|
5776
|
+
}
|
|
5777
|
+
return result;
|
|
5778
|
+
}
|
|
5779
|
+
/**
|
|
5780
|
+
* Check if a URL is safe (http, https, or relative)
|
|
5781
|
+
*/
|
|
5782
|
+
function isSafeUrl(url) {
|
|
5783
|
+
if (!url)
|
|
5784
|
+
return false;
|
|
5785
|
+
// Allow relative URLs
|
|
5786
|
+
if (url.startsWith("/") || url.startsWith("#") || url.startsWith("?")) {
|
|
5787
|
+
return true;
|
|
5788
|
+
}
|
|
5789
|
+
// Allow http and https
|
|
5790
|
+
try {
|
|
5791
|
+
const parsed = new URL(url, "https://example.com");
|
|
5792
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
5793
|
+
}
|
|
5794
|
+
catch {
|
|
5795
|
+
return false;
|
|
5796
|
+
}
|
|
5797
|
+
}
|
|
5798
|
+
/**
|
|
5799
|
+
* Escape attribute value
|
|
5800
|
+
*/
|
|
5801
|
+
function escapeAttr(value) {
|
|
5802
|
+
return value
|
|
5803
|
+
.replace(/&/g, "&")
|
|
5804
|
+
.replace(/"/g, """)
|
|
5805
|
+
.replace(/</g, "<")
|
|
5806
|
+
.replace(/>/g, ">");
|
|
5807
|
+
}
|
|
5808
|
+
|
|
5611
5809
|
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}}`;
|
|
5612
5810
|
|
|
5613
5811
|
class AuthheroWidget {
|
|
@@ -6206,7 +6404,7 @@ class AuthheroWidget {
|
|
|
6206
6404
|
const hasDivider = components.some((c) => this.isDividerComponent(c));
|
|
6207
6405
|
// Get logo URL from theme.widget (takes precedence) or branding
|
|
6208
6406
|
const logoUrl = this._theme?.widget?.logo_url || this._branding?.logo_url;
|
|
6209
|
-
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"
|
|
6407
|
+
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 &&
|
|
6210
6408
|
fieldComponents.length > 0 &&
|
|
6211
6409
|
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, {
|
|
6212
6410
|
id: link.id,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@authhero/widget",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Server-Driven UI widget for AuthHero authentication flows",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
"collection": "./dist/collection/collection-manifest.json",
|
|
21
21
|
"exports": {
|
|
22
22
|
".": "./dist/index.js",
|
|
23
|
-
"./hydrate":
|
|
23
|
+
"./hydrate": {
|
|
24
|
+
"types": "./hydrate/index.d.ts",
|
|
25
|
+
"import": "./hydrate/index.mjs",
|
|
26
|
+
"require": "./hydrate/index.js"
|
|
27
|
+
},
|
|
24
28
|
"./loader": "./loader/index.js",
|
|
25
29
|
"./dist/esm/loader.js": "./dist/esm/loader.js",
|
|
26
30
|
"./server": {
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
}
|
|
34
38
|
},
|
|
35
39
|
"dependencies": {
|
|
36
|
-
"@authhero/adapter-interfaces": "0.
|
|
40
|
+
"@authhero/adapter-interfaces": "0.130.0"
|
|
37
41
|
},
|
|
38
42
|
"devDependencies": {
|
|
39
43
|
"@hono/node-server": "^1.14.1",
|