@cedros/login-react 0.0.46 → 0.0.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +32 -2
  2. package/dist/EmailRegisterForm-ByYQ43yL.cjs +1 -0
  3. package/dist/EmailRegisterForm-ByYQ43yL.cjs.map +1 -0
  4. package/dist/EmailRegisterForm-DMUcNQT-.js +781 -0
  5. package/dist/EmailRegisterForm-DMUcNQT-.js.map +1 -0
  6. package/dist/GoogleLoginButton-DEwtBp56.cjs +1 -0
  7. package/dist/GoogleLoginButton-DEwtBp56.cjs.map +1 -0
  8. package/dist/{GoogleLoginButton-C1WNu7W3.js → GoogleLoginButton-DwyxvhnL.js} +82 -80
  9. package/dist/GoogleLoginButton-DwyxvhnL.js.map +1 -0
  10. package/dist/{PermissionsSection-mm9hfp-u.js → PermissionsSection-0oNHPZzL.js} +383 -415
  11. package/dist/PermissionsSection-0oNHPZzL.js.map +1 -0
  12. package/dist/PermissionsSection-CZsJuxo4.cjs +1 -0
  13. package/dist/PermissionsSection-CZsJuxo4.cjs.map +1 -0
  14. package/dist/SolanaLoginButton-2504p6cr.cjs +1 -0
  15. package/dist/SolanaLoginButton-2504p6cr.cjs.map +1 -0
  16. package/dist/SolanaLoginButton-C7Kc_m6n.js +234 -0
  17. package/dist/SolanaLoginButton-C7Kc_m6n.js.map +1 -0
  18. package/dist/{TeamSection-Km7EwLWD.cjs → TeamSection-DN8PEHH3.cjs} +1 -1
  19. package/dist/{TeamSection-Km7EwLWD.cjs.map → TeamSection-DN8PEHH3.cjs.map} +1 -1
  20. package/dist/{TeamSection-C_eODdLU.js → TeamSection-gUyP4YDM.js} +1 -1
  21. package/dist/{TeamSection-C_eODdLU.js.map → TeamSection-gUyP4YDM.js.map} +1 -1
  22. package/dist/{UsersSection-C1Tt0ePx.cjs → UsersSection-8wLuD0fr.cjs} +1 -1
  23. package/dist/{UsersSection-C1Tt0ePx.cjs.map → UsersSection-8wLuD0fr.cjs.map} +1 -1
  24. package/dist/{UsersSection-Ct_E-MBF.js → UsersSection-CnsFrG-6.js} +1 -1
  25. package/dist/{UsersSection-Ct_E-MBF.js.map → UsersSection-CnsFrG-6.js.map} +1 -1
  26. package/dist/admin-only.cjs +1 -1
  27. package/dist/admin-only.js +1 -1
  28. package/dist/email-only.cjs +1 -1
  29. package/dist/email-only.d.ts +10 -2
  30. package/dist/email-only.js +2 -2
  31. package/dist/google-only.cjs +1 -1
  32. package/dist/google-only.d.ts +7 -2
  33. package/dist/google-only.js +2 -2
  34. package/dist/index.cjs +12 -12
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.ts +182 -11
  37. package/dist/index.js +5221 -4657
  38. package/dist/index.js.map +1 -1
  39. package/dist/login-react.css +1 -1
  40. package/dist/{plugin-Ci67QMGG.cjs → plugin-DNFjEfYp.cjs} +1 -1
  41. package/dist/{plugin-Ci67QMGG.cjs.map → plugin-DNFjEfYp.cjs.map} +1 -1
  42. package/dist/{plugin-Cm8Q6O4-.js → plugin-WYMrRNbz.js} +1 -1
  43. package/dist/{plugin-Cm8Q6O4-.js.map → plugin-WYMrRNbz.js.map} +1 -1
  44. package/dist/solana-only.cjs +1 -1
  45. package/dist/solana-only.d.ts +6 -1
  46. package/dist/solana-only.js +2 -2
  47. package/dist/useAuth-2vgrAH-M.cjs +1 -0
  48. package/dist/useAuth-2vgrAH-M.cjs.map +1 -0
  49. package/dist/{useAuth-l-itM5am.js → useAuth-CNflw856.js} +465 -469
  50. package/dist/useAuth-CNflw856.js.map +1 -0
  51. package/dist/useServerFeatures-9_aNPaa6.cjs +1 -0
  52. package/dist/useServerFeatures-9_aNPaa6.cjs.map +1 -0
  53. package/dist/useServerFeatures-DSkYdan-.js +82 -0
  54. package/dist/useServerFeatures-DSkYdan-.js.map +1 -0
  55. package/dist/{useUsersStatsSummary-BGeh3RnI.js → useUsersStatsSummary-B4_RBEYy.js} +504 -442
  56. package/dist/useUsersStatsSummary-B4_RBEYy.js.map +1 -0
  57. package/dist/useUsersStatsSummary-CHRMrlk4.cjs +1 -0
  58. package/dist/useUsersStatsSummary-CHRMrlk4.cjs.map +1 -0
  59. package/package.json +1 -1
  60. package/dist/EmailRegisterForm-p2X5QP58.js +0 -750
  61. package/dist/EmailRegisterForm-p2X5QP58.js.map +0 -1
  62. package/dist/EmailRegisterForm-xFb6MaVA.cjs +0 -1
  63. package/dist/EmailRegisterForm-xFb6MaVA.cjs.map +0 -1
  64. package/dist/GoogleLoginButton-2zNTIKMm.cjs +0 -1
  65. package/dist/GoogleLoginButton-2zNTIKMm.cjs.map +0 -1
  66. package/dist/GoogleLoginButton-C1WNu7W3.js.map +0 -1
  67. package/dist/PermissionsSection-4zcE9Zs9.cjs +0 -1
  68. package/dist/PermissionsSection-4zcE9Zs9.cjs.map +0 -1
  69. package/dist/PermissionsSection-mm9hfp-u.js.map +0 -1
  70. package/dist/SolanaLoginButton-CqdzSSeJ.cjs +0 -1
  71. package/dist/SolanaLoginButton-CqdzSSeJ.cjs.map +0 -1
  72. package/dist/SolanaLoginButton-CyeX35eU.js +0 -232
  73. package/dist/SolanaLoginButton-CyeX35eU.js.map +0 -1
  74. package/dist/sanitization-Bo_tn-L2.cjs +0 -1
  75. package/dist/sanitization-Bo_tn-L2.cjs.map +0 -1
  76. package/dist/sanitization-CQ-H1MSg.js +0 -39
  77. package/dist/sanitization-CQ-H1MSg.js.map +0 -1
  78. package/dist/useAuth-B1yS_YiD.cjs +0 -1
  79. package/dist/useAuth-B1yS_YiD.cjs.map +0 -1
  80. package/dist/useAuth-l-itM5am.js.map +0 -1
  81. package/dist/useUsersStatsSummary-BGeh3RnI.js.map +0 -1
  82. package/dist/useUsersStatsSummary-DnsYtFGX.cjs +0 -1
  83. package/dist/useUsersStatsSummary-DnsYtFGX.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ "use strict";const i=require("react"),p=require("./useSystemSettings-B2jY51ob.cjs"),_=["https:"],f=["javascript:","data:","vbscript:","file:"];function m(r){if(!r||typeof r!="string")return;const t=r.trim();if(!t)return;const o=t.toLowerCase();for(const e of f)if(o.startsWith(e))return;try{const e=new URL(t);return _.includes(e.protocol)?t:void 0}catch{return}}function h(r){if(!r||typeof r!="string")return;const t=r.trim();if(!t)return;const o=t.toLowerCase();for(const e of f)if(o.startsWith(e))return;try{const e=new URL(t);return e.protocol!=="https:"&&e.protocol!=="http:"?void 0:t}catch{return}}const S={organizations:!1,sso:!1,mfa:!1,mfaRequired:!1,walletSigning:!1,credits:!1,userWithdrawals:!1,cedrosPay:!1,signupAccessCodeRequired:!1};function w(){const{settings:r,isLoading:t,error:o,fetchSettings:e,getValue:s}=p.useSystemSettings(),[u,l]=i.useState(!1);i.useEffect(()=>{u||(e(),l(!0))},[e,u]);const a=i.useCallback(n=>n===void 0?!1:n==="true"||n==="1",[]),c=i.useMemo(()=>Object.keys(r).length===0?S:{organizations:a(s("feature_organizations")),sso:a(s("feature_sso")),mfa:a(s("feature_mfa")),mfaRequired:a(s("security_require_mfa")),walletSigning:a(s("feature_wallet_signing")),credits:a(s("feature_credits")),userWithdrawals:a(s("feature_user_withdrawals")),cedrosPay:a(s("feature_cedros_pay")),signupAccessCodeRequired:a(s("signup_access_code_enabled"))},[r,s,a]),d=i.useCallback(async()=>{await e()},[e]),g=i.useCallback(n=>c[n],[c]);return{features:c,isLoading:t,error:o,refetch:d,isEnabled:g}}exports.sanitizeExternalUrl=h;exports.sanitizeImageUrl=m;exports.useServerFeatures=w;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useServerFeatures-9_aNPaa6.cjs","sources":["../src/utils/sanitization.ts","../src/hooks/useServerFeatures.ts"],"sourcesContent":["/**\n * Input sanitization utilities for security\n */\n\n/**\n * Allowed image URL protocols\n */\nconst SAFE_PROTOCOLS = ['https:'];\n\n/**\n * Dangerous protocols that should never be allowed\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:', 'file:'];\n\n/**\n * Sanitizes an image URL to prevent XSS and other attacks.\n *\n * ## Security Properties\n *\n * - Only allows HTTPS URLs (prevents protocol-based attacks)\n * - Blocks javascript:, data:, vbscript:, file: protocols\n * - Returns undefined for invalid URLs\n *\n * ## Limitations (S-20/SEC-08)\n *\n * This function validates the URL protocol but does NOT validate the domain.\n * Any HTTPS URL will pass validation, including URLs from untrusted domains.\n * This is intentional - domain allowlisting is application-specific and should\n * be implemented at the consumer level (see example below).\n *\n * If your application requires domain validation (e.g., only allowing images\n * from trusted CDNs), implement additional validation:\n *\n * @example Domain allowlisting (application-level)\n * ```ts\n * const ALLOWED_DOMAINS = ['cdn.example.com', 'images.trusted.org'];\n *\n * function isAllowedImageUrl(url: string): boolean {\n * const sanitized = sanitizeImageUrl(url);\n * if (!sanitized) return false;\n *\n * try {\n * const hostname = new URL(sanitized).hostname;\n * return ALLOWED_DOMAINS.some(d => hostname === d || hostname.endsWith('.' + d));\n * } catch {\n * return false;\n * }\n * }\n * ```\n *\n * @param url - The URL to sanitize\n * @returns The sanitized URL or undefined if invalid\n *\n * @example Basic usage\n * ```ts\n * sanitizeImageUrl('https://example.com/avatar.png') // 'https://example.com/avatar.png'\n * sanitizeImageUrl('javascript:alert(1)') // undefined\n * sanitizeImageUrl('data:image/svg+xml,...') // undefined\n * ```\n */\nexport function sanitizeImageUrl(url: string | undefined | null): string | undefined {\n if (!url || typeof url !== 'string') {\n return undefined;\n }\n\n // Trim whitespace\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return undefined;\n }\n\n // Check for dangerous protocols (case-insensitive)\n const lowerUrl = trimmedUrl.toLowerCase();\n for (const protocol of DANGEROUS_PROTOCOLS) {\n if (lowerUrl.startsWith(protocol)) {\n return undefined;\n }\n }\n\n // Try to parse as URL\n try {\n const parsed = new URL(trimmedUrl);\n\n // Only allow safe protocols\n if (!SAFE_PROTOCOLS.includes(parsed.protocol)) {\n return undefined;\n }\n\n // Return the original URL (preserves case)\n return trimmedUrl;\n } catch {\n // Invalid URL\n return undefined;\n }\n}\n\n/**\n * Sanitizes an external link URL for use in href attributes.\n *\n * Security goals:\n * - Block dangerous protocols (javascript:, data:, vbscript:, file:)\n * - Only allow http/https absolute URLs\n */\nexport function sanitizeExternalUrl(url: string | undefined | null): string | undefined {\n if (!url || typeof url !== 'string') {\n return undefined;\n }\n\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return undefined;\n }\n\n const lowerUrl = trimmedUrl.toLowerCase();\n for (const protocol of DANGEROUS_PROTOCOLS) {\n if (lowerUrl.startsWith(protocol)) {\n return undefined;\n }\n }\n\n try {\n const parsed = new URL(trimmedUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n return undefined;\n }\n return trimmedUrl;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Sanitizes user-provided text to prevent XSS in text content.\n * Escapes HTML special characters.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text\n */\nexport function sanitizeText(text: string | undefined | null): string {\n if (!text || typeof text !== 'string') {\n return '';\n }\n\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;');\n}\n","import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useSystemSettings } from './useSystemSettings';\n\n/**\n * Server-side feature flags stored in system settings.\n *\n * These control which features are available in the application.\n * Unlike client-side FeatureFlags (passed to CedrosLoginProvider),\n * these can be toggled at runtime via the admin dashboard.\n *\n * **Cosmetic vs enforced:**\n * Some flags only affect UI visibility (cosmetic) while others are\n * enforced server-side. See per-field docs below.\n *\n * Settings that are enforced per-request by the server (hard 403 if disabled):\n * `auth_email_enabled`, `auth_google_enabled`, `auth_apple_enabled`,\n * `auth_instantlink_enabled`, `feature_user_withdrawals`, `privacy_period_secs`,\n * withdrawal worker settings, and deposit fee settings.\n *\n * Settings applied at server startup (take effect after restart):\n * `security_cors_origins`, `rate_limit_*`, `auth_webauthn_*`, `webhook_*`.\n */\nexport interface ServerFeatures {\n /**\n * Enable multi-tenant organizations. Controls: Team, Invites sections.\n *\n * **Cosmetic** — org endpoints are always reachable server-side.\n * This flag only controls admin dashboard section visibility.\n */\n organizations: boolean;\n /** Enable Enterprise SSO for organizations. Startup-config enforced. */\n sso: boolean;\n /**\n * Enable two-factor authentication (TOTP).\n *\n * **Cosmetic** — MFA is actually gated by whether the user has TOTP\n * enrolled (`has_mfa_enabled()`), not by this flag. This controls\n * admin dashboard visibility only.\n */\n mfa: boolean;\n /**\n * Require email/password users to set up TOTP two-factor authentication.\n *\n * **Enforced** — the server returns a `setup_mfa` post-login action for\n * password users who haven't enrolled TOTP. Works independently of `mfa`\n * (which only controls UI visibility).\n */\n mfaRequired: boolean;\n /** Enable embedded wallet for transaction signing. Startup-config enforced. */\n walletSigning: boolean;\n /**\n * Enable deposits and credits system.\n * Controls: Deposits, Withdrawals, Credit System admin sections.\n * Deposit/withdrawal endpoints are enforced server-side.\n */\n credits: boolean;\n /**\n * Enable user withdrawals from embedded wallet to external addresses.\n * **Enforced** — server returns 403 on withdrawal endpoints when disabled.\n */\n userWithdrawals: boolean;\n /**\n * Enable Cedros Pay integration.\n * Controls: Products, Transactions, Refunds admin sections.\n *\n * **Cosmetic** — only controls admin dashboard tab visibility.\n */\n cedrosPay: boolean;\n /**\n * Require an access code to register.\n *\n * **Enforced** — the server rejects registrations without a valid code\n * when this is enabled. The EmailRegisterForm shows an access code field\n * when this flag is true.\n */\n signupAccessCodeRequired: boolean;\n}\n\n/**\n * Default feature values when settings haven't loaded yet.\n * All features disabled by default for safety.\n */\nconst DEFAULT_FEATURES: ServerFeatures = {\n organizations: false,\n sso: false,\n mfa: false,\n mfaRequired: false,\n walletSigning: false,\n credits: false,\n userWithdrawals: false,\n cedrosPay: false,\n signupAccessCodeRequired: false,\n};\n\nexport interface UseServerFeaturesReturn {\n /** Current feature flag states */\n features: ServerFeatures;\n /** Whether settings are still loading */\n isLoading: boolean;\n /** Error if settings failed to load */\n error: Error | null;\n /** Refresh feature flags from server */\n refetch: () => Promise<void>;\n /** Check if a specific feature is enabled */\n isEnabled: (feature: keyof ServerFeatures) => boolean;\n}\n\n/**\n * Hook for reading server-side feature flags from system settings.\n *\n * Use this to conditionally show/hide features based on admin settings.\n *\n * @example\n * ```tsx\n * function AdminDashboard() {\n * const { features, isLoading, isEnabled } = useServerFeatures();\n *\n * if (isLoading) return <LoadingSpinner />;\n *\n * return (\n * <nav>\n * {isEnabled('organizations') && <NavItem>Team</NavItem>}\n * {isEnabled('credits') && <NavItem>Deposits</NavItem>}\n * </nav>\n * );\n * }\n * ```\n */\nexport function useServerFeatures(): UseServerFeaturesReturn {\n const { settings, isLoading, error, fetchSettings, getValue } = useSystemSettings();\n const [hasFetched, setHasFetched] = useState(false);\n\n // Fetch settings on mount\n useEffect(() => {\n if (!hasFetched) {\n fetchSettings();\n setHasFetched(true);\n }\n }, [fetchSettings, hasFetched]);\n\n // Parse boolean value from string setting\n const parseBoolean = useCallback((value: string | undefined): boolean => {\n if (value === undefined) return false;\n return value === 'true' || value === '1';\n }, []);\n\n // Derive features from settings\n const features = useMemo<ServerFeatures>(() => {\n // If no settings loaded yet, return defaults\n if (Object.keys(settings).length === 0) {\n return DEFAULT_FEATURES;\n }\n\n return {\n organizations: parseBoolean(getValue('feature_organizations')),\n sso: parseBoolean(getValue('feature_sso')),\n mfa: parseBoolean(getValue('feature_mfa')),\n mfaRequired: parseBoolean(getValue('security_require_mfa')),\n walletSigning: parseBoolean(getValue('feature_wallet_signing')),\n credits: parseBoolean(getValue('feature_credits')),\n userWithdrawals: parseBoolean(getValue('feature_user_withdrawals')),\n cedrosPay: parseBoolean(getValue('feature_cedros_pay')),\n signupAccessCodeRequired: parseBoolean(getValue('signup_access_code_enabled')),\n };\n }, [settings, getValue, parseBoolean]);\n\n const refetch = useCallback(async () => {\n await fetchSettings();\n }, [fetchSettings]);\n\n const isEnabled = useCallback(\n (feature: keyof ServerFeatures): boolean => {\n return features[feature];\n },\n [features]\n );\n\n return {\n features,\n isLoading,\n error,\n refetch,\n isEnabled,\n };\n}\n"],"names":["SAFE_PROTOCOLS","DANGEROUS_PROTOCOLS","sanitizeImageUrl","url","trimmedUrl","lowerUrl","protocol","parsed","sanitizeExternalUrl","DEFAULT_FEATURES","useServerFeatures","settings","isLoading","error","fetchSettings","getValue","useSystemSettings","hasFetched","setHasFetched","useState","useEffect","parseBoolean","useCallback","value","features","useMemo","refetch","isEnabled","feature"],"mappings":"oFAOMA,EAAiB,CAAC,QAAQ,EAK1BC,EAAsB,CAAC,cAAe,QAAS,YAAa,OAAO,EAgDlE,SAASC,EAAiBC,EAAoD,CACnF,GAAI,CAACA,GAAO,OAAOA,GAAQ,SACzB,OAIF,MAAMC,EAAaD,EAAI,KAAA,EACvB,GAAI,CAACC,EACH,OAIF,MAAMC,EAAWD,EAAW,YAAA,EAC5B,UAAWE,KAAYL,EACrB,GAAII,EAAS,WAAWC,CAAQ,EAC9B,OAKJ,GAAI,CACF,MAAMC,EAAS,IAAI,IAAIH,CAAU,EAGjC,OAAKJ,EAAe,SAASO,EAAO,QAAQ,EAKrCH,EAJL,MAKJ,MAAQ,CAEN,MACF,CACF,CASO,SAASI,EAAoBL,EAAoD,CACtF,GAAI,CAACA,GAAO,OAAOA,GAAQ,SACzB,OAGF,MAAMC,EAAaD,EAAI,KAAA,EACvB,GAAI,CAACC,EACH,OAGF,MAAMC,EAAWD,EAAW,YAAA,EAC5B,UAAWE,KAAYL,EACrB,GAAII,EAAS,WAAWC,CAAQ,EAC9B,OAIJ,GAAI,CACF,MAAMC,EAAS,IAAI,IAAIH,CAAU,EACjC,OAAIG,EAAO,WAAa,UAAYA,EAAO,WAAa,QACtD,OAEKH,CACT,MAAQ,CACN,MACF,CACF,CC/CA,MAAMK,EAAmC,CACvC,cAAe,GACf,IAAK,GACL,IAAK,GACL,YAAa,GACb,cAAe,GACf,QAAS,GACT,gBAAiB,GACjB,UAAW,GACX,yBAA0B,EAC5B,EAoCO,SAASC,GAA6C,CAC3D,KAAM,CAAE,SAAAC,EAAU,UAAAC,EAAW,MAAAC,EAAO,cAAAC,EAAe,SAAAC,CAAA,EAAaC,oBAAA,EAC1D,CAACC,EAAYC,CAAa,EAAIC,EAAAA,SAAS,EAAK,EAGlDC,EAAAA,UAAU,IAAM,CACTH,IACHH,EAAA,EACAI,EAAc,EAAI,EAEtB,EAAG,CAACJ,EAAeG,CAAU,CAAC,EAG9B,MAAMI,EAAeC,cAAaC,GAC5BA,IAAU,OAAkB,GACzBA,IAAU,QAAUA,IAAU,IACpC,CAAA,CAAE,EAGCC,EAAWC,EAAAA,QAAwB,IAEnC,OAAO,KAAKd,CAAQ,EAAE,SAAW,EAC5BF,EAGF,CACL,cAAeY,EAAaN,EAAS,uBAAuB,CAAC,EAC7D,IAAKM,EAAaN,EAAS,aAAa,CAAC,EACzC,IAAKM,EAAaN,EAAS,aAAa,CAAC,EACzC,YAAaM,EAAaN,EAAS,sBAAsB,CAAC,EAC1D,cAAeM,EAAaN,EAAS,wBAAwB,CAAC,EAC9D,QAASM,EAAaN,EAAS,iBAAiB,CAAC,EACjD,gBAAiBM,EAAaN,EAAS,0BAA0B,CAAC,EAClE,UAAWM,EAAaN,EAAS,oBAAoB,CAAC,EACtD,yBAA0BM,EAAaN,EAAS,4BAA4B,CAAC,CAAA,EAE9E,CAACJ,EAAUI,EAAUM,CAAY,CAAC,EAE/BK,EAAUJ,EAAAA,YAAY,SAAY,CACtC,MAAMR,EAAA,CACR,EAAG,CAACA,CAAa,CAAC,EAEZa,EAAYL,EAAAA,YACfM,GACQJ,EAASI,CAAO,EAEzB,CAACJ,CAAQ,CAAA,EAGX,MAAO,CACL,SAAAA,EACA,UAAAZ,EACA,MAAAC,EACA,QAAAa,EACA,UAAAC,CAAA,CAEJ"}
@@ -0,0 +1,82 @@
1
+ import { useState as g, useEffect as m, useCallback as c, useMemo as _ } from "react";
2
+ import { u as h } from "./useSystemSettings-rgskaDqP.js";
3
+ const w = ["https:"], u = ["javascript:", "data:", "vbscript:", "file:"];
4
+ function U(r) {
5
+ if (!r || typeof r != "string")
6
+ return;
7
+ const t = r.trim();
8
+ if (!t)
9
+ return;
10
+ const a = t.toLowerCase();
11
+ for (const e of u)
12
+ if (a.startsWith(e))
13
+ return;
14
+ try {
15
+ const e = new URL(t);
16
+ return w.includes(e.protocol) ? t : void 0;
17
+ } catch {
18
+ return;
19
+ }
20
+ }
21
+ function R(r) {
22
+ if (!r || typeof r != "string")
23
+ return;
24
+ const t = r.trim();
25
+ if (!t)
26
+ return;
27
+ const a = t.toLowerCase();
28
+ for (const e of u)
29
+ if (a.startsWith(e))
30
+ return;
31
+ try {
32
+ const e = new URL(t);
33
+ return e.protocol !== "https:" && e.protocol !== "http:" ? void 0 : t;
34
+ } catch {
35
+ return;
36
+ }
37
+ }
38
+ const v = {
39
+ organizations: !1,
40
+ sso: !1,
41
+ mfa: !1,
42
+ mfaRequired: !1,
43
+ walletSigning: !1,
44
+ credits: !1,
45
+ userWithdrawals: !1,
46
+ cedrosPay: !1,
47
+ signupAccessCodeRequired: !1
48
+ };
49
+ function E() {
50
+ const { settings: r, isLoading: t, error: a, fetchSettings: e, getValue: s } = h(), [f, d] = g(!1);
51
+ m(() => {
52
+ f || (e(), d(!0));
53
+ }, [e, f]);
54
+ const o = c((i) => i === void 0 ? !1 : i === "true" || i === "1", []), n = _(() => Object.keys(r).length === 0 ? v : {
55
+ organizations: o(s("feature_organizations")),
56
+ sso: o(s("feature_sso")),
57
+ mfa: o(s("feature_mfa")),
58
+ mfaRequired: o(s("security_require_mfa")),
59
+ walletSigning: o(s("feature_wallet_signing")),
60
+ credits: o(s("feature_credits")),
61
+ userWithdrawals: o(s("feature_user_withdrawals")),
62
+ cedrosPay: o(s("feature_cedros_pay")),
63
+ signupAccessCodeRequired: o(s("signup_access_code_enabled"))
64
+ }, [r, s, o]), l = c(async () => {
65
+ await e();
66
+ }, [e]), p = c(
67
+ (i) => n[i],
68
+ [n]
69
+ );
70
+ return {
71
+ features: n,
72
+ isLoading: t,
73
+ error: a,
74
+ refetch: l,
75
+ isEnabled: p
76
+ };
77
+ }
78
+ export {
79
+ U as a,
80
+ R as s,
81
+ E as u
82
+ };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useServerFeatures-DSkYdan-.js","sources":["../src/utils/sanitization.ts","../src/hooks/useServerFeatures.ts"],"sourcesContent":["/**\n * Input sanitization utilities for security\n */\n\n/**\n * Allowed image URL protocols\n */\nconst SAFE_PROTOCOLS = ['https:'];\n\n/**\n * Dangerous protocols that should never be allowed\n */\nconst DANGEROUS_PROTOCOLS = ['javascript:', 'data:', 'vbscript:', 'file:'];\n\n/**\n * Sanitizes an image URL to prevent XSS and other attacks.\n *\n * ## Security Properties\n *\n * - Only allows HTTPS URLs (prevents protocol-based attacks)\n * - Blocks javascript:, data:, vbscript:, file: protocols\n * - Returns undefined for invalid URLs\n *\n * ## Limitations (S-20/SEC-08)\n *\n * This function validates the URL protocol but does NOT validate the domain.\n * Any HTTPS URL will pass validation, including URLs from untrusted domains.\n * This is intentional - domain allowlisting is application-specific and should\n * be implemented at the consumer level (see example below).\n *\n * If your application requires domain validation (e.g., only allowing images\n * from trusted CDNs), implement additional validation:\n *\n * @example Domain allowlisting (application-level)\n * ```ts\n * const ALLOWED_DOMAINS = ['cdn.example.com', 'images.trusted.org'];\n *\n * function isAllowedImageUrl(url: string): boolean {\n * const sanitized = sanitizeImageUrl(url);\n * if (!sanitized) return false;\n *\n * try {\n * const hostname = new URL(sanitized).hostname;\n * return ALLOWED_DOMAINS.some(d => hostname === d || hostname.endsWith('.' + d));\n * } catch {\n * return false;\n * }\n * }\n * ```\n *\n * @param url - The URL to sanitize\n * @returns The sanitized URL or undefined if invalid\n *\n * @example Basic usage\n * ```ts\n * sanitizeImageUrl('https://example.com/avatar.png') // 'https://example.com/avatar.png'\n * sanitizeImageUrl('javascript:alert(1)') // undefined\n * sanitizeImageUrl('data:image/svg+xml,...') // undefined\n * ```\n */\nexport function sanitizeImageUrl(url: string | undefined | null): string | undefined {\n if (!url || typeof url !== 'string') {\n return undefined;\n }\n\n // Trim whitespace\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return undefined;\n }\n\n // Check for dangerous protocols (case-insensitive)\n const lowerUrl = trimmedUrl.toLowerCase();\n for (const protocol of DANGEROUS_PROTOCOLS) {\n if (lowerUrl.startsWith(protocol)) {\n return undefined;\n }\n }\n\n // Try to parse as URL\n try {\n const parsed = new URL(trimmedUrl);\n\n // Only allow safe protocols\n if (!SAFE_PROTOCOLS.includes(parsed.protocol)) {\n return undefined;\n }\n\n // Return the original URL (preserves case)\n return trimmedUrl;\n } catch {\n // Invalid URL\n return undefined;\n }\n}\n\n/**\n * Sanitizes an external link URL for use in href attributes.\n *\n * Security goals:\n * - Block dangerous protocols (javascript:, data:, vbscript:, file:)\n * - Only allow http/https absolute URLs\n */\nexport function sanitizeExternalUrl(url: string | undefined | null): string | undefined {\n if (!url || typeof url !== 'string') {\n return undefined;\n }\n\n const trimmedUrl = url.trim();\n if (!trimmedUrl) {\n return undefined;\n }\n\n const lowerUrl = trimmedUrl.toLowerCase();\n for (const protocol of DANGEROUS_PROTOCOLS) {\n if (lowerUrl.startsWith(protocol)) {\n return undefined;\n }\n }\n\n try {\n const parsed = new URL(trimmedUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n return undefined;\n }\n return trimmedUrl;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Sanitizes user-provided text to prevent XSS in text content.\n * Escapes HTML special characters.\n *\n * @param text - The text to sanitize\n * @returns The sanitized text\n */\nexport function sanitizeText(text: string | undefined | null): string {\n if (!text || typeof text !== 'string') {\n return '';\n }\n\n return text\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#x27;');\n}\n","import { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useSystemSettings } from './useSystemSettings';\n\n/**\n * Server-side feature flags stored in system settings.\n *\n * These control which features are available in the application.\n * Unlike client-side FeatureFlags (passed to CedrosLoginProvider),\n * these can be toggled at runtime via the admin dashboard.\n *\n * **Cosmetic vs enforced:**\n * Some flags only affect UI visibility (cosmetic) while others are\n * enforced server-side. See per-field docs below.\n *\n * Settings that are enforced per-request by the server (hard 403 if disabled):\n * `auth_email_enabled`, `auth_google_enabled`, `auth_apple_enabled`,\n * `auth_instantlink_enabled`, `feature_user_withdrawals`, `privacy_period_secs`,\n * withdrawal worker settings, and deposit fee settings.\n *\n * Settings applied at server startup (take effect after restart):\n * `security_cors_origins`, `rate_limit_*`, `auth_webauthn_*`, `webhook_*`.\n */\nexport interface ServerFeatures {\n /**\n * Enable multi-tenant organizations. Controls: Team, Invites sections.\n *\n * **Cosmetic** — org endpoints are always reachable server-side.\n * This flag only controls admin dashboard section visibility.\n */\n organizations: boolean;\n /** Enable Enterprise SSO for organizations. Startup-config enforced. */\n sso: boolean;\n /**\n * Enable two-factor authentication (TOTP).\n *\n * **Cosmetic** — MFA is actually gated by whether the user has TOTP\n * enrolled (`has_mfa_enabled()`), not by this flag. This controls\n * admin dashboard visibility only.\n */\n mfa: boolean;\n /**\n * Require email/password users to set up TOTP two-factor authentication.\n *\n * **Enforced** — the server returns a `setup_mfa` post-login action for\n * password users who haven't enrolled TOTP. Works independently of `mfa`\n * (which only controls UI visibility).\n */\n mfaRequired: boolean;\n /** Enable embedded wallet for transaction signing. Startup-config enforced. */\n walletSigning: boolean;\n /**\n * Enable deposits and credits system.\n * Controls: Deposits, Withdrawals, Credit System admin sections.\n * Deposit/withdrawal endpoints are enforced server-side.\n */\n credits: boolean;\n /**\n * Enable user withdrawals from embedded wallet to external addresses.\n * **Enforced** — server returns 403 on withdrawal endpoints when disabled.\n */\n userWithdrawals: boolean;\n /**\n * Enable Cedros Pay integration.\n * Controls: Products, Transactions, Refunds admin sections.\n *\n * **Cosmetic** — only controls admin dashboard tab visibility.\n */\n cedrosPay: boolean;\n /**\n * Require an access code to register.\n *\n * **Enforced** — the server rejects registrations without a valid code\n * when this is enabled. The EmailRegisterForm shows an access code field\n * when this flag is true.\n */\n signupAccessCodeRequired: boolean;\n}\n\n/**\n * Default feature values when settings haven't loaded yet.\n * All features disabled by default for safety.\n */\nconst DEFAULT_FEATURES: ServerFeatures = {\n organizations: false,\n sso: false,\n mfa: false,\n mfaRequired: false,\n walletSigning: false,\n credits: false,\n userWithdrawals: false,\n cedrosPay: false,\n signupAccessCodeRequired: false,\n};\n\nexport interface UseServerFeaturesReturn {\n /** Current feature flag states */\n features: ServerFeatures;\n /** Whether settings are still loading */\n isLoading: boolean;\n /** Error if settings failed to load */\n error: Error | null;\n /** Refresh feature flags from server */\n refetch: () => Promise<void>;\n /** Check if a specific feature is enabled */\n isEnabled: (feature: keyof ServerFeatures) => boolean;\n}\n\n/**\n * Hook for reading server-side feature flags from system settings.\n *\n * Use this to conditionally show/hide features based on admin settings.\n *\n * @example\n * ```tsx\n * function AdminDashboard() {\n * const { features, isLoading, isEnabled } = useServerFeatures();\n *\n * if (isLoading) return <LoadingSpinner />;\n *\n * return (\n * <nav>\n * {isEnabled('organizations') && <NavItem>Team</NavItem>}\n * {isEnabled('credits') && <NavItem>Deposits</NavItem>}\n * </nav>\n * );\n * }\n * ```\n */\nexport function useServerFeatures(): UseServerFeaturesReturn {\n const { settings, isLoading, error, fetchSettings, getValue } = useSystemSettings();\n const [hasFetched, setHasFetched] = useState(false);\n\n // Fetch settings on mount\n useEffect(() => {\n if (!hasFetched) {\n fetchSettings();\n setHasFetched(true);\n }\n }, [fetchSettings, hasFetched]);\n\n // Parse boolean value from string setting\n const parseBoolean = useCallback((value: string | undefined): boolean => {\n if (value === undefined) return false;\n return value === 'true' || value === '1';\n }, []);\n\n // Derive features from settings\n const features = useMemo<ServerFeatures>(() => {\n // If no settings loaded yet, return defaults\n if (Object.keys(settings).length === 0) {\n return DEFAULT_FEATURES;\n }\n\n return {\n organizations: parseBoolean(getValue('feature_organizations')),\n sso: parseBoolean(getValue('feature_sso')),\n mfa: parseBoolean(getValue('feature_mfa')),\n mfaRequired: parseBoolean(getValue('security_require_mfa')),\n walletSigning: parseBoolean(getValue('feature_wallet_signing')),\n credits: parseBoolean(getValue('feature_credits')),\n userWithdrawals: parseBoolean(getValue('feature_user_withdrawals')),\n cedrosPay: parseBoolean(getValue('feature_cedros_pay')),\n signupAccessCodeRequired: parseBoolean(getValue('signup_access_code_enabled')),\n };\n }, [settings, getValue, parseBoolean]);\n\n const refetch = useCallback(async () => {\n await fetchSettings();\n }, [fetchSettings]);\n\n const isEnabled = useCallback(\n (feature: keyof ServerFeatures): boolean => {\n return features[feature];\n },\n [features]\n );\n\n return {\n features,\n isLoading,\n error,\n refetch,\n isEnabled,\n };\n}\n"],"names":["SAFE_PROTOCOLS","DANGEROUS_PROTOCOLS","sanitizeImageUrl","url","trimmedUrl","lowerUrl","protocol","parsed","sanitizeExternalUrl","DEFAULT_FEATURES","useServerFeatures","settings","isLoading","error","fetchSettings","getValue","useSystemSettings","hasFetched","setHasFetched","useState","useEffect","parseBoolean","useCallback","value","features","useMemo","refetch","isEnabled","feature"],"mappings":";;AAOA,MAAMA,IAAiB,CAAC,QAAQ,GAK1BC,IAAsB,CAAC,eAAe,SAAS,aAAa,OAAO;AAgDlE,SAASC,EAAiBC,GAAoD;AACnF,MAAI,CAACA,KAAO,OAAOA,KAAQ;AACzB;AAIF,QAAMC,IAAaD,EAAI,KAAA;AACvB,MAAI,CAACC;AACH;AAIF,QAAMC,IAAWD,EAAW,YAAA;AAC5B,aAAWE,KAAYL;AACrB,QAAII,EAAS,WAAWC,CAAQ;AAC9B;AAKJ,MAAI;AACF,UAAMC,IAAS,IAAI,IAAIH,CAAU;AAGjC,WAAKJ,EAAe,SAASO,EAAO,QAAQ,IAKrCH,IAJL;AAAA,EAKJ,QAAQ;AAEN;AAAA,EACF;AACF;AASO,SAASI,EAAoBL,GAAoD;AACtF,MAAI,CAACA,KAAO,OAAOA,KAAQ;AACzB;AAGF,QAAMC,IAAaD,EAAI,KAAA;AACvB,MAAI,CAACC;AACH;AAGF,QAAMC,IAAWD,EAAW,YAAA;AAC5B,aAAWE,KAAYL;AACrB,QAAII,EAAS,WAAWC,CAAQ;AAC9B;AAIJ,MAAI;AACF,UAAMC,IAAS,IAAI,IAAIH,CAAU;AACjC,WAAIG,EAAO,aAAa,YAAYA,EAAO,aAAa,UACtD,SAEKH;AAAA,EACT,QAAQ;AACN;AAAA,EACF;AACF;AC/CA,MAAMK,IAAmC;AAAA,EACvC,eAAe;AAAA,EACf,KAAK;AAAA,EACL,KAAK;AAAA,EACL,aAAa;AAAA,EACb,eAAe;AAAA,EACf,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,WAAW;AAAA,EACX,0BAA0B;AAC5B;AAoCO,SAASC,IAA6C;AAC3D,QAAM,EAAE,UAAAC,GAAU,WAAAC,GAAW,OAAAC,GAAO,eAAAC,GAAe,UAAAC,EAAA,IAAaC,EAAA,GAC1D,CAACC,GAAYC,CAAa,IAAIC,EAAS,EAAK;AAGlD,EAAAC,EAAU,MAAM;AACd,IAAKH,MACHH,EAAA,GACAI,EAAc,EAAI;AAAA,EAEtB,GAAG,CAACJ,GAAeG,CAAU,CAAC;AAG9B,QAAMI,IAAeC,EAAY,CAACC,MAC5BA,MAAU,SAAkB,KACzBA,MAAU,UAAUA,MAAU,KACpC,CAAA,CAAE,GAGCC,IAAWC,EAAwB,MAEnC,OAAO,KAAKd,CAAQ,EAAE,WAAW,IAC5BF,IAGF;AAAA,IACL,eAAeY,EAAaN,EAAS,uBAAuB,CAAC;AAAA,IAC7D,KAAKM,EAAaN,EAAS,aAAa,CAAC;AAAA,IACzC,KAAKM,EAAaN,EAAS,aAAa,CAAC;AAAA,IACzC,aAAaM,EAAaN,EAAS,sBAAsB,CAAC;AAAA,IAC1D,eAAeM,EAAaN,EAAS,wBAAwB,CAAC;AAAA,IAC9D,SAASM,EAAaN,EAAS,iBAAiB,CAAC;AAAA,IACjD,iBAAiBM,EAAaN,EAAS,0BAA0B,CAAC;AAAA,IAClE,WAAWM,EAAaN,EAAS,oBAAoB,CAAC;AAAA,IACtD,0BAA0BM,EAAaN,EAAS,4BAA4B,CAAC;AAAA,EAAA,GAE9E,CAACJ,GAAUI,GAAUM,CAAY,CAAC,GAE/BK,IAAUJ,EAAY,YAAY;AACtC,UAAMR,EAAA;AAAA,EACR,GAAG,CAACA,CAAa,CAAC,GAEZa,IAAYL;AAAA,IAChB,CAACM,MACQJ,EAASI,CAAO;AAAA,IAEzB,CAACJ,CAAQ;AAAA,EAAA;AAGX,SAAO;AAAA,IACL,UAAAA;AAAA,IACA,WAAAZ;AAAA,IACA,OAAAC;AAAA,IACA,SAAAa;AAAA,IACA,WAAAC;AAAA,EAAA;AAEJ;"}