@hexclave/shared 1.0.3 → 1.0.5
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/dist/ai/unified-prompts/reminders.js +4 -3
- package/dist/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +216 -29
- package/dist/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/config/schema.d.ts +71 -71
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +5 -5
- package/dist/config/schema.js.map +1 -1
- package/dist/config-authoring.d.ts +4 -4
- package/dist/config-authoring.d.ts.map +1 -1
- package/dist/config-authoring.js +2 -2
- package/dist/config-authoring.js.map +1 -1
- package/dist/config-rendering.d.ts +4 -4
- package/dist/config-rendering.d.ts.map +1 -1
- package/dist/config-rendering.js +19 -18
- package/dist/config-rendering.js.map +1 -1
- package/dist/esm/ai/unified-prompts/reminders.js +4 -3
- package/dist/esm/ai/unified-prompts/reminders.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts +3 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js +215 -30
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.d.ts.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js +1 -0
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-index.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.d.ts +18 -6
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js +23 -8
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.js.map +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js +1 -1
- package/dist/esm/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.js.map +1 -1
- package/dist/esm/config/schema.d.ts +71 -71
- package/dist/esm/config/schema.d.ts.map +1 -1
- package/dist/esm/config/schema.js +5 -5
- package/dist/esm/config/schema.js.map +1 -1
- package/dist/esm/config-authoring.d.ts +4 -4
- package/dist/esm/config-authoring.d.ts.map +1 -1
- package/dist/esm/config-authoring.js +2 -2
- package/dist/esm/config-authoring.js.map +1 -1
- package/dist/esm/config-rendering.d.ts +4 -4
- package/dist/esm/config-rendering.d.ts.map +1 -1
- package/dist/esm/config-rendering.js +15 -14
- package/dist/esm/config-rendering.js.map +1 -1
- package/dist/esm/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/client-side.js.map +1 -1
- package/dist/esm/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/esm/helpers/vault/server-side.js.map +1 -1
- package/dist/esm/hexclave-config-file.d.ts +15 -0
- package/dist/esm/hexclave-config-file.d.ts.map +1 -0
- package/dist/esm/{stack-config-file.js → hexclave-config-file.js} +7 -7
- package/dist/esm/hexclave-config-file.js.map +1 -0
- package/dist/esm/hooks/use-async-callback.js +1 -1
- package/dist/esm/hooks/use-async-external-store.js +1 -1
- package/dist/esm/hooks/use-strict-memo.js +1 -1
- package/dist/esm/interface/admin-metrics.d.ts +4 -4
- package/dist/esm/interface/admin-metrics.js.map +1 -1
- package/dist/esm/interface/client-interface.js +2 -2
- package/dist/esm/interface/client-interface.js.map +1 -1
- package/dist/esm/interface/conversations.d.ts +13 -13
- package/dist/esm/interface/crud/current-user.d.ts +9 -9
- package/dist/esm/interface/crud/email-outbox.d.ts +112 -112
- package/dist/esm/interface/crud/products.d.ts +1 -1
- package/dist/esm/interface/crud/project-api-keys.d.ts +2 -2
- package/dist/esm/interface/crud/project-permissions.js +5 -5
- package/dist/esm/interface/crud/project-permissions.js.map +1 -1
- package/dist/esm/interface/crud/projects.d.ts +28 -28
- package/dist/esm/interface/crud/team-member-profiles.d.ts +22 -22
- package/dist/esm/interface/crud/team-permissions.js +5 -5
- package/dist/esm/interface/crud/team-permissions.js.map +1 -1
- package/dist/esm/interface/crud/users.d.ts +14 -14
- package/dist/esm/interface/page-component-versions.js +107 -107
- package/dist/esm/interface/page-component-versions.js.map +1 -1
- package/dist/esm/interface/webhooks.d.ts +4 -4
- package/dist/esm/known-errors.js +4 -4
- package/dist/esm/known-errors.js.map +1 -1
- package/dist/esm/schema-fields.d.ts +15 -5
- package/dist/esm/schema-fields.d.ts.map +1 -1
- package/dist/esm/schema-fields.js +26 -16
- package/dist/esm/schema-fields.js.map +1 -1
- package/dist/esm/utils/crypto.d.ts.map +1 -1
- package/dist/esm/utils/crypto.js.map +1 -1
- package/dist/esm/utils/currency-constants.d.ts +6 -0
- package/dist/esm/utils/currency-constants.d.ts.map +1 -1
- package/dist/esm/utils/currency-constants.js.map +1 -1
- package/dist/esm/utils/dev-env-state-path.d.ts +3 -3
- package/dist/esm/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/esm/utils/dev-env-state-path.js +4 -4
- package/dist/esm/utils/dev-env-state-path.js.map +1 -1
- package/dist/esm/utils/errors.js +2 -2
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/esbuild.d.ts.map +1 -1
- package/dist/esm/utils/esbuild.js +0 -1
- package/dist/esm/utils/esbuild.js.map +1 -1
- package/dist/esm/utils/featurebase.d.ts +3 -3
- package/dist/esm/utils/featurebase.d.ts.map +1 -1
- package/dist/esm/utils/featurebase.js +15 -15
- package/dist/esm/utils/featurebase.js.map +1 -1
- package/dist/esm/utils/fs.js +6 -6
- package/dist/esm/utils/fs.js.map +1 -1
- package/dist/esm/utils/globals.d.ts.map +1 -1
- package/dist/esm/utils/globals.js +10 -10
- package/dist/esm/utils/globals.js.map +1 -1
- package/dist/esm/utils/jwt.d.ts.map +1 -1
- package/dist/esm/utils/jwt.js +3 -3
- package/dist/esm/utils/jwt.js.map +1 -1
- package/dist/esm/utils/redirect-urls.d.ts +3 -3
- package/dist/esm/utils/redirect-urls.d.ts.map +1 -1
- package/dist/esm/utils/redirect-urls.js +5 -5
- package/dist/esm/utils/redirect-urls.js.map +1 -1
- package/dist/esm/utils/urls.js.map +1 -1
- package/dist/helpers/vault/client-side.d.ts.map +1 -1
- package/dist/helpers/vault/client-side.js.map +1 -1
- package/dist/helpers/vault/server-side.d.ts.map +1 -1
- package/dist/helpers/vault/server-side.js.map +1 -1
- package/dist/hexclave-config-file.d.ts +15 -0
- package/dist/hexclave-config-file.d.ts.map +1 -0
- package/dist/{stack-config-file.js → hexclave-config-file.js} +8 -8
- package/dist/hexclave-config-file.js.map +1 -0
- package/dist/hooks/use-async-callback.js +1 -1
- package/dist/hooks/use-async-external-store.js +1 -1
- package/dist/hooks/use-strict-memo.js +1 -1
- package/dist/interface/admin-metrics.d.ts +4 -4
- package/dist/interface/admin-metrics.js.map +1 -1
- package/dist/interface/client-interface.js +2 -2
- package/dist/interface/client-interface.js.map +1 -1
- package/dist/interface/conversations.d.ts +13 -13
- package/dist/interface/crud/current-user.d.ts +9 -9
- package/dist/interface/crud/email-outbox.d.ts +112 -112
- package/dist/interface/crud/products.d.ts +1 -1
- package/dist/interface/crud/project-api-keys.d.ts +2 -2
- package/dist/interface/crud/project-permissions.js +5 -5
- package/dist/interface/crud/project-permissions.js.map +1 -1
- package/dist/interface/crud/projects.d.ts +28 -28
- package/dist/interface/crud/team-member-profiles.d.ts +22 -22
- package/dist/interface/crud/team-permissions.js +5 -5
- package/dist/interface/crud/team-permissions.js.map +1 -1
- package/dist/interface/crud/users.d.ts +14 -14
- package/dist/interface/page-component-versions.js +107 -107
- package/dist/interface/page-component-versions.js.map +1 -1
- package/dist/interface/webhooks.d.ts +4 -4
- package/dist/known-errors.js +4 -4
- package/dist/known-errors.js.map +1 -1
- package/dist/schema-fields.d.ts +15 -5
- package/dist/schema-fields.d.ts.map +1 -1
- package/dist/schema-fields.js +26 -16
- package/dist/schema-fields.js.map +1 -1
- package/dist/utils/crypto.d.ts.map +1 -1
- package/dist/utils/crypto.js.map +1 -1
- package/dist/utils/currency-constants.d.ts +6 -0
- package/dist/utils/currency-constants.d.ts.map +1 -1
- package/dist/utils/currency-constants.js.map +1 -1
- package/dist/utils/dev-env-state-path.d.ts +3 -3
- package/dist/utils/dev-env-state-path.d.ts.map +1 -1
- package/dist/utils/dev-env-state-path.js +5 -5
- package/dist/utils/dev-env-state-path.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/esbuild.d.ts.map +1 -1
- package/dist/utils/esbuild.js +0 -1
- package/dist/utils/esbuild.js.map +1 -1
- package/dist/utils/featurebase.d.ts +3 -3
- package/dist/utils/featurebase.d.ts.map +1 -1
- package/dist/utils/featurebase.js +15 -15
- package/dist/utils/featurebase.js.map +1 -1
- package/dist/utils/fs.js.map +1 -1
- package/dist/utils/globals.d.ts.map +1 -1
- package/dist/utils/globals.js +10 -10
- package/dist/utils/globals.js.map +1 -1
- package/dist/utils/jwt.d.ts.map +1 -1
- package/dist/utils/jwt.js +3 -3
- package/dist/utils/jwt.js.map +1 -1
- package/dist/utils/redirect-urls.d.ts +3 -3
- package/dist/utils/redirect-urls.d.ts.map +1 -1
- package/dist/utils/redirect-urls.js +5 -5
- package/dist/utils/redirect-urls.js.map +1 -1
- package/dist/utils/urls.js.map +1 -1
- package/package.json +2 -1
- package/src/ai/image-limits.ts +62 -0
- package/src/ai/llms/llms.ts +36 -0
- package/src/ai/unified-prompts/reminders.ts +27 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/ai-setup-prompt.ts +1047 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/brain.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/cli-help.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/config-docs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/custom-components-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/dashboard-instructions.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-index.ts +80 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/docs-json.generated.ts +322 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/openapi-specs.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt-parts/sdk-interface-source.ts +5 -0
- package/src/ai/unified-prompts/skill-site-prompt.ts +72 -0
- package/src/apps/apps-config.ts +177 -0
- package/src/apps/apps-ui.tsx +112 -0
- package/src/config/README.md +154 -0
- package/src/config/db-sync-mappings.ts +2293 -0
- package/src/config/format.ts +388 -0
- package/src/config/migrate-catalogs-to-product-lines.ts +475 -0
- package/src/config/schema-fuzzer.test.ts +377 -0
- package/src/config/schema.ts +1507 -0
- package/src/config-authoring.test.ts +52 -0
- package/src/config-authoring.ts +37 -0
- package/src/config-rendering.ts +142 -0
- package/src/crud.tsx +275 -0
- package/src/global.d.ts +1 -0
- package/src/helpers/emails.ts +250 -0
- package/src/helpers/init-prompt.ts +39 -0
- package/src/helpers/password.ts +16 -0
- package/src/helpers/production-mode.ts +58 -0
- package/src/helpers/support-sla.test.ts +124 -0
- package/src/helpers/support-sla.ts +93 -0
- package/src/helpers/vault/client-side.ts +92 -0
- package/src/helpers/vault/server-side.ts +153 -0
- package/src/hexclave-config-file.ts +120 -0
- package/src/hooks/use-async-callback.tsx +43 -0
- package/src/hooks/use-async-external-store.tsx +24 -0
- package/src/hooks/use-hash.tsx +14 -0
- package/src/hooks/use-hover.tsx +88 -0
- package/src/hooks/use-strict-memo.tsx +157 -0
- package/src/index.ts +17 -0
- package/src/interface/admin-interface.ts +1043 -0
- package/src/interface/admin-metrics.ts +214 -0
- package/src/interface/client-interface.test.ts +765 -0
- package/src/interface/client-interface.ts +2586 -0
- package/src/interface/conversations.ts +109 -0
- package/src/interface/crud/analytics.ts +11 -0
- package/src/interface/crud/connected-accounts.ts +52 -0
- package/src/interface/crud/contact-channels.ts +74 -0
- package/src/interface/crud/current-user.ts +72 -0
- package/src/interface/crud/email-outbox.ts +263 -0
- package/src/interface/crud/email-templates.ts +50 -0
- package/src/interface/crud/emails.ts +22 -0
- package/src/interface/crud/internal-api-keys.ts +69 -0
- package/src/interface/crud/invoices.ts +36 -0
- package/src/interface/crud/items.ts +24 -0
- package/src/interface/crud/notification-preferences.ts +33 -0
- package/src/interface/crud/oauth-providers.ts +91 -0
- package/src/interface/crud/products.ts +42 -0
- package/src/interface/crud/project-api-keys.ts +100 -0
- package/src/interface/crud/project-permissions.ts +118 -0
- package/src/interface/crud/projects.ts +229 -0
- package/src/interface/crud/session-replays.ts +85 -0
- package/src/interface/crud/sessions.ts +66 -0
- package/src/interface/crud/sign-up-rules.ts +12 -0
- package/src/interface/crud/svix-token.ts +20 -0
- package/src/interface/crud/team-invitation-details.ts +22 -0
- package/src/interface/crud/team-invitation.ts +35 -0
- package/src/interface/crud/team-member-profiles.ts +61 -0
- package/src/interface/crud/team-memberships.ts +56 -0
- package/src/interface/crud/team-permissions.ts +119 -0
- package/src/interface/crud/teams.ts +142 -0
- package/src/interface/crud/transactions.ts +131 -0
- package/src/interface/crud/users.ts +185 -0
- package/src/interface/handler-urls.ts +74 -0
- package/src/interface/page-component-versions.ts +1702 -0
- package/src/interface/server-interface.ts +1146 -0
- package/src/interface/webhooks.ts +28 -0
- package/src/known-errors.tsx +2031 -0
- package/src/local-emulator.ts +5 -0
- package/src/payments/payment-methods.ts +167 -0
- package/src/payments/stripe-limits.ts +18 -0
- package/src/plans.ts +107 -0
- package/src/schema-fields.ts +944 -0
- package/src/sessions.ts +310 -0
- package/src/utils/api-keys.tsx +122 -0
- package/src/utils/arrays.tsx +248 -0
- package/src/utils/auth-methods.ts +8 -0
- package/src/utils/base64.tsx +24 -0
- package/src/utils/booleans.tsx +34 -0
- package/src/utils/browser-compat.tsx +17 -0
- package/src/utils/bytes.tsx +280 -0
- package/src/utils/caches.tsx +280 -0
- package/src/utils/cel-fields.ts +92 -0
- package/src/utils/cloud-hosts.tsx +92 -0
- package/src/utils/compile-time.tsx +11 -0
- package/src/utils/country-codes.ts +39 -0
- package/src/utils/crypto.tsx +174 -0
- package/src/utils/currencies.tsx +53 -0
- package/src/utils/currency-constants.tsx +51 -0
- package/src/utils/dates.tsx +246 -0
- package/src/utils/dev-env-state-path.ts +14 -0
- package/src/utils/dom.tsx +7 -0
- package/src/utils/env.tsx +117 -0
- package/src/utils/errors.tsx +284 -0
- package/src/utils/esbuild.tsx +235 -0
- package/src/utils/featurebase.tsx +247 -0
- package/src/utils/fs.tsx +33 -0
- package/src/utils/functions.tsx +22 -0
- package/src/utils/geo.tsx +16 -0
- package/src/utils/globals.tsx +48 -0
- package/src/utils/hashes.tsx +52 -0
- package/src/utils/html.tsx +60 -0
- package/src/utils/http.tsx +91 -0
- package/src/utils/ips.tsx +47 -0
- package/src/utils/json.tsx +200 -0
- package/src/utils/jsx-editable-transpiler.test.ts +156 -0
- package/src/utils/jsx-editable-transpiler.tsx +401 -0
- package/src/utils/jwt.tsx +170 -0
- package/src/utils/locks.tsx +71 -0
- package/src/utils/maps.tsx +407 -0
- package/src/utils/math.tsx +18 -0
- package/src/utils/monkey-patch.tsx +29 -0
- package/src/utils/node-http.tsx +47 -0
- package/src/utils/numbers.tsx +82 -0
- package/src/utils/oauth.tsx +9 -0
- package/src/utils/objects.tsx +581 -0
- package/src/utils/paginated-lists.test.ts +1039 -0
- package/src/utils/paginated-lists.tsx +479 -0
- package/src/utils/passkey.tsx +1 -0
- package/src/utils/promises.tsx +540 -0
- package/src/utils/proxies.tsx +212 -0
- package/src/utils/react.tsx +308 -0
- package/src/utils/redirect-urls.tsx +299 -0
- package/src/utils/regex.tsx +10 -0
- package/src/utils/results.tsx +411 -0
- package/src/utils/sentry.tsx +20 -0
- package/src/utils/stores.tsx +238 -0
- package/src/utils/strings.nicify.test.ts +253 -0
- package/src/utils/strings.tsx +723 -0
- package/src/utils/telemetry.tsx +37 -0
- package/src/utils/turnstile-browser.ts +106 -0
- package/src/utils/turnstile-flow.test.ts +101 -0
- package/src/utils/turnstile-flow.ts +266 -0
- package/src/utils/turnstile.ts +34 -0
- package/src/utils/typed-arrays.tsx +17 -0
- package/src/utils/types.tsx +208 -0
- package/src/utils/unicode.tsx +24 -0
- package/src/utils/urls.tsx +411 -0
- package/src/utils/uuids.tsx +47 -0
- package/dist/esm/stack-config-file.d.ts +0 -15
- package/dist/esm/stack-config-file.d.ts.map +0 -1
- package/dist/esm/stack-config-file.js.map +0 -1
- package/dist/stack-config-file.d.ts +0 -15
- package/dist/stack-config-file.d.ts.map +0 -1
- package/dist/stack-config-file.js.map +0 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { HexclaveAssertionError, captureError } from "./errors";
|
|
2
|
+
import { createUrlIfValid, isLocalhost, matchHostnamePattern } from "./urls";
|
|
3
|
+
|
|
4
|
+
type TrustedDomainConfig = {
|
|
5
|
+
allowLocalhost?: boolean,
|
|
6
|
+
trustedDomains: readonly (string | null | undefined)[],
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const defaultHostedHandlerDomainSuffix = ".built-with-stack-auth.com";
|
|
10
|
+
const hostedHandlerProjectIdPlaceholder = "{projectId}";
|
|
11
|
+
const hostedHandlerPathPlaceholder = "{hostedPath}";
|
|
12
|
+
const defaultPorts = new Map<string, string>([['https:', '443'], ['http:', '80']]);
|
|
13
|
+
const hostedHandlerTemplateProjectIdA = "00000000-0000-4000-8000-000000000000";
|
|
14
|
+
const hostedHandlerTemplateProjectIdB = "11111111-1111-4111-8111-111111111111";
|
|
15
|
+
|
|
16
|
+
function replaceHexclavePortPrefix(input: string | undefined, hexclavePortPrefix: string | undefined): string | undefined {
|
|
17
|
+
if (input == null) return undefined;
|
|
18
|
+
return hexclavePortPrefix ? input.replace(/\$\{NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81\}/g, hexclavePortPrefix) : input;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function getHostedHandlerUrlFromTemplate(template: string, projectId: string, hostedPath: string): string {
|
|
22
|
+
return template
|
|
23
|
+
.replaceAll(hostedHandlerProjectIdPlaceholder, projectId)
|
|
24
|
+
.replaceAll(hostedHandlerPathPlaceholder, hostedPath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertHostedHandlerTemplateHasProjectOrigin(template: string): void {
|
|
28
|
+
const projectUrlA = new URL(getHostedHandlerUrlFromTemplate(template, hostedHandlerTemplateProjectIdA, "handler"));
|
|
29
|
+
const projectUrlB = new URL(getHostedHandlerUrlFromTemplate(template, hostedHandlerTemplateProjectIdB, "handler"));
|
|
30
|
+
if (projectUrlA.origin === projectUrlB.origin || !projectUrlA.hostname.includes(hostedHandlerTemplateProjectIdA)) {
|
|
31
|
+
throw new HexclaveAssertionError("The hosted handler URL template must put {projectId} in the hostname.", {
|
|
32
|
+
hostedHandlerUrlTemplate: template,
|
|
33
|
+
hint: "Use a project-specific origin like 'https://{projectId}.built-with-stack-auth.com/{hostedPath}', not a shared-origin path like 'https://example.com/{projectId}/{hostedPath}'.",
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getHostedHandlerTrustedDomain(options: {
|
|
39
|
+
projectId: string,
|
|
40
|
+
hostedHandlerDomainSuffix?: string,
|
|
41
|
+
hostedHandlerUrlTemplate?: string,
|
|
42
|
+
hexclavePortPrefix?: string,
|
|
43
|
+
}): string {
|
|
44
|
+
return new URL(getHostedHandlerUrlFromConfig({
|
|
45
|
+
...options,
|
|
46
|
+
hostedPath: "handler",
|
|
47
|
+
})).origin;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getHostedHandlerUrlFromConfig(options: {
|
|
51
|
+
projectId: string,
|
|
52
|
+
hostedPath: string,
|
|
53
|
+
hostedHandlerDomainSuffix?: string,
|
|
54
|
+
hostedHandlerUrlTemplate?: string,
|
|
55
|
+
hexclavePortPrefix?: string,
|
|
56
|
+
}): string {
|
|
57
|
+
const configuredTemplate = replaceHexclavePortPrefix(options.hostedHandlerUrlTemplate, options.hexclavePortPrefix);
|
|
58
|
+
return configuredTemplate == null
|
|
59
|
+
? (() => {
|
|
60
|
+
const domainSuffix = replaceHexclavePortPrefix(options.hostedHandlerDomainSuffix, options.hexclavePortPrefix) ?? defaultHostedHandlerDomainSuffix;
|
|
61
|
+
if (!domainSuffix.startsWith(".")) {
|
|
62
|
+
throw new HexclaveAssertionError("The hosted handler domain suffix must start with a dot.", {
|
|
63
|
+
domainSuffix,
|
|
64
|
+
hint: "Set NEXT_PUBLIC_STACK_HOSTED_HANDLER_DOMAIN_SUFFIX to a value like '.built-with-stack-auth.com'.",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return `https://${options.projectId}${domainSuffix}/${options.hostedPath}`;
|
|
68
|
+
})()
|
|
69
|
+
: (() => {
|
|
70
|
+
if (!configuredTemplate.includes(hostedHandlerProjectIdPlaceholder) || !configuredTemplate.includes(hostedHandlerPathPlaceholder)) {
|
|
71
|
+
throw new HexclaveAssertionError("The hosted handler URL template must contain {projectId} and {hostedPath}.", {
|
|
72
|
+
hostedHandlerUrlTemplate: configuredTemplate,
|
|
73
|
+
hint: "Set NEXT_PUBLIC_STACK_HOSTED_HANDLER_URL_TEMPLATE to a value like 'https://{projectId}.built-with-stack-auth.com/{hostedPath}'.",
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
assertHostedHandlerTemplateHasProjectOrigin(configuredTemplate);
|
|
77
|
+
return getHostedHandlerUrlFromTemplate(configuredTemplate, options.projectId, options.hostedPath);
|
|
78
|
+
})();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getImplicitlyTrustedDomainsForProject(options: {
|
|
82
|
+
projectId: string,
|
|
83
|
+
hostedHandlerDomainSuffix?: string,
|
|
84
|
+
hostedHandlerUrlTemplate?: string,
|
|
85
|
+
hexclavePortPrefix?: string,
|
|
86
|
+
}): string[] {
|
|
87
|
+
return [getHostedHandlerTrustedDomain(options)];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizePort(url: URL): string {
|
|
91
|
+
const port = url.port || defaultPorts.get(url.protocol) || '';
|
|
92
|
+
return port ? `${url.hostname}:${port}` : url.hostname;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isDefaultPort(url: URL): boolean {
|
|
96
|
+
return !url.port ||
|
|
97
|
+
(url.protocol === 'https:' && url.port === '443') ||
|
|
98
|
+
(url.protocol === 'http:' && url.port === '80');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function portsMatch(url1: URL, url2: URL): boolean {
|
|
102
|
+
return normalizePort(url1) === normalizePort(url2);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function parseWildcardUrlPattern(pattern: string): { protocol: string, hostPattern: string } | null {
|
|
106
|
+
const protocolSeparatorIndex = pattern.indexOf("://");
|
|
107
|
+
if (protocolSeparatorIndex === -1) return null;
|
|
108
|
+
|
|
109
|
+
const protocol = `${pattern.slice(0, protocolSeparatorIndex)}:`;
|
|
110
|
+
const hostAndPath = pattern.slice(protocolSeparatorIndex + "://".length);
|
|
111
|
+
const pathStartIndex = hostAndPath.indexOf("/");
|
|
112
|
+
const hostPattern = pathStartIndex === -1 ? hostAndPath : hostAndPath.slice(0, pathStartIndex);
|
|
113
|
+
if (hostPattern === "") return null;
|
|
114
|
+
return { protocol, hostPattern };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hostPatternWithoutPort(hostPattern: string): string {
|
|
118
|
+
if (!hostPatternHasExplicitPort(hostPattern)) {
|
|
119
|
+
return hostPattern;
|
|
120
|
+
}
|
|
121
|
+
const portSeparatorIndex = hostPattern.lastIndexOf(":");
|
|
122
|
+
return hostPattern.slice(0, portSeparatorIndex);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function hostPatternHasExplicitPort(hostPattern: string): boolean {
|
|
126
|
+
const portSeparatorIndex = hostPattern.lastIndexOf(":");
|
|
127
|
+
if (portSeparatorIndex === -1) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
const port = hostPattern.slice(portSeparatorIndex + 1);
|
|
131
|
+
return port === "*" || (port !== "" && [...port].every(char => char >= "0" && char <= "9"));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function matchesTrustedDomain(testUrl: URL, pattern: string): boolean {
|
|
135
|
+
const baseUrl = createUrlIfValid(pattern);
|
|
136
|
+
|
|
137
|
+
if (baseUrl != null && !pattern.includes('*')) {
|
|
138
|
+
return baseUrl.protocol === testUrl.protocol &&
|
|
139
|
+
baseUrl.hostname === testUrl.hostname &&
|
|
140
|
+
portsMatch(baseUrl, testUrl);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const parsedPattern = parseWildcardUrlPattern(pattern);
|
|
144
|
+
if (parsedPattern == null) {
|
|
145
|
+
captureError("invalid-redirect-domain", new HexclaveAssertionError("Invalid domain pattern", { pattern }));
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (testUrl.protocol !== parsedPattern.protocol) {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const hasPortInPattern = hostPatternHasExplicitPort(parsedPattern.hostPattern);
|
|
154
|
+
return hasPortInPattern
|
|
155
|
+
? matchHostnamePattern(parsedPattern.hostPattern, normalizePort(testUrl))
|
|
156
|
+
: matchHostnamePattern(parsedPattern.hostPattern, testUrl.hostname) && isDefaultPort(testUrl);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function isAcceptedNativeAppUrl(urlOrString: string): boolean {
|
|
160
|
+
const url = createUrlIfValid(urlOrString);
|
|
161
|
+
if (!url) return false;
|
|
162
|
+
|
|
163
|
+
// Legacy scheme accepted indefinitely; baked into already-shipped Swift SDK binaries.
|
|
164
|
+
return url.protocol === 'stack-auth-mobile-oauth-url:'
|
|
165
|
+
|| url.protocol === 'hexclave-mobile-oauth-url:';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function validateRedirectUrl(
|
|
169
|
+
urlOrString: string | URL,
|
|
170
|
+
config: TrustedDomainConfig,
|
|
171
|
+
): boolean {
|
|
172
|
+
const url = createUrlIfValid(urlOrString);
|
|
173
|
+
if (!url) return false;
|
|
174
|
+
|
|
175
|
+
if (config.allowLocalhost === true && isLocalhost(url)) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return config.trustedDomains.some(domain => domain != null && matchesTrustedDomain(url, domain));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function getTrustedParentDomain(currentDomain: string, trustedDomains: readonly (string | null | undefined)[]): string | null {
|
|
183
|
+
const hostPatterns = trustedDomains
|
|
184
|
+
.filter((domain): domain is string => domain != null)
|
|
185
|
+
.map((domain) => {
|
|
186
|
+
const url = createUrlIfValid(domain);
|
|
187
|
+
if (url != null && !domain.includes("*")) {
|
|
188
|
+
return url.hostname.toLowerCase();
|
|
189
|
+
}
|
|
190
|
+
const parsedPattern = parseWildcardUrlPattern(domain);
|
|
191
|
+
return parsedPattern == null ? null : hostPatternWithoutPort(parsedPattern.hostPattern).toLowerCase();
|
|
192
|
+
})
|
|
193
|
+
.filter((domain): domain is string => domain != null);
|
|
194
|
+
|
|
195
|
+
const parts = currentDomain.toLowerCase().split('.');
|
|
196
|
+
for (let i = parts.length - 2; i >= 0; i--) {
|
|
197
|
+
const parentDomain = parts.slice(i).join('.');
|
|
198
|
+
if (hostPatterns.includes(parentDomain) && hostPatterns.includes(`**.${parentDomain}`)) {
|
|
199
|
+
return parentDomain;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
import.meta.vitest?.test("validateRedirectUrl matches exact and wildcard trusted domains", ({ expect }) => {
|
|
207
|
+
expect(validateRedirectUrl("https://example.com", {
|
|
208
|
+
allowLocalhost: false,
|
|
209
|
+
trustedDomains: ["https://example.com"],
|
|
210
|
+
})).toBe(true);
|
|
211
|
+
expect(validateRedirectUrl("https://api.example.com", {
|
|
212
|
+
allowLocalhost: false,
|
|
213
|
+
trustedDomains: ["https://*.example.com"],
|
|
214
|
+
})).toBe(true);
|
|
215
|
+
expect(validateRedirectUrl("https://api.v2.example.com", {
|
|
216
|
+
allowLocalhost: false,
|
|
217
|
+
trustedDomains: ["https://*.example.com"],
|
|
218
|
+
})).toBe(false);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
import.meta.vitest?.test("validateRedirectUrl trusts implicit hosted handler domains", ({ expect }) => {
|
|
222
|
+
const projectId = "12345678-1234-4234-8234-123456789abc";
|
|
223
|
+
expect(validateRedirectUrl(`https://${projectId}.built-with-stack-auth.com/anything`, {
|
|
224
|
+
allowLocalhost: false,
|
|
225
|
+
trustedDomains: getImplicitlyTrustedDomainsForProject({ projectId }),
|
|
226
|
+
})).toBe(true);
|
|
227
|
+
expect(validateRedirectUrl("https://other-project.built-with-stack-auth.com/anything", {
|
|
228
|
+
allowLocalhost: false,
|
|
229
|
+
trustedDomains: getImplicitlyTrustedDomainsForProject({ projectId }),
|
|
230
|
+
})).toBe(false);
|
|
231
|
+
expect(validateRedirectUrl(`http://${projectId}.localhost:9209/anything`, {
|
|
232
|
+
allowLocalhost: false,
|
|
233
|
+
trustedDomains: getImplicitlyTrustedDomainsForProject({
|
|
234
|
+
projectId,
|
|
235
|
+
hostedHandlerUrlTemplate: "http://{projectId}.localhost:${NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX:-81}09/{hostedPath}",
|
|
236
|
+
hexclavePortPrefix: "92",
|
|
237
|
+
}),
|
|
238
|
+
})).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
import.meta.vitest?.test("getImplicitlyTrustedDomainsForProject rejects shared-origin hosted templates", ({ expect }) => {
|
|
242
|
+
expect(() => getImplicitlyTrustedDomainsForProject({
|
|
243
|
+
projectId: "12345678-1234-4234-8234-123456789abc",
|
|
244
|
+
hostedHandlerUrlTemplate: "https://host.example.com/{projectId}/{hostedPath}",
|
|
245
|
+
})).toThrowErrorMatchingInlineSnapshot(`
|
|
246
|
+
[HexclaveAssertionError: The hosted handler URL template must put {projectId} in the hostname.
|
|
247
|
+
|
|
248
|
+
This is likely an error in Hexclave. Please make sure you are running the newest version and report it.]
|
|
249
|
+
`);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
import.meta.vitest?.test("validateRedirectUrl respects default and explicit ports", ({ expect }) => {
|
|
253
|
+
expect(validateRedirectUrl("https://example.com:443/path", {
|
|
254
|
+
allowLocalhost: false,
|
|
255
|
+
trustedDomains: ["https://example.com"],
|
|
256
|
+
})).toBe(true);
|
|
257
|
+
expect(validateRedirectUrl("http://api.example.com:3000", {
|
|
258
|
+
allowLocalhost: false,
|
|
259
|
+
trustedDomains: ["http://*.example.com:3000"],
|
|
260
|
+
})).toBe(true);
|
|
261
|
+
expect(validateRedirectUrl("http://api.example.com", {
|
|
262
|
+
allowLocalhost: false,
|
|
263
|
+
trustedDomains: ["http://*.example.com:3000"],
|
|
264
|
+
})).toBe(false);
|
|
265
|
+
expect(validateRedirectUrl("http://api.example.com:1234", {
|
|
266
|
+
allowLocalhost: false,
|
|
267
|
+
trustedDomains: ["http://*.example.com:*"],
|
|
268
|
+
})).toBe(true);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
import.meta.vitest?.test("validateRedirectUrl respects localhost allowance and invalid patterns", ({ expect }) => {
|
|
272
|
+
const originalConsoleError = console.error;
|
|
273
|
+
console.error = () => {};
|
|
274
|
+
try {
|
|
275
|
+
expect(validateRedirectUrl("http://localhost:3000", {
|
|
276
|
+
allowLocalhost: true,
|
|
277
|
+
trustedDomains: [],
|
|
278
|
+
})).toBe(true);
|
|
279
|
+
expect(validateRedirectUrl("http://localhost:3000", {
|
|
280
|
+
allowLocalhost: false,
|
|
281
|
+
trustedDomains: [],
|
|
282
|
+
})).toBe(false);
|
|
283
|
+
expect(validateRedirectUrl("https://example.com", {
|
|
284
|
+
allowLocalhost: false,
|
|
285
|
+
trustedDomains: ["not a url"],
|
|
286
|
+
})).toBe(false);
|
|
287
|
+
} finally {
|
|
288
|
+
console.error = originalConsoleError;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
import.meta.vitest?.test("getTrustedParentDomain ignores empty entries and strips ports", ({ expect }) => {
|
|
293
|
+
expect(getTrustedParentDomain("app.example.com", [
|
|
294
|
+
null,
|
|
295
|
+
undefined,
|
|
296
|
+
"https://example.com",
|
|
297
|
+
"https://**.example.com:*",
|
|
298
|
+
])).toBe("example.com");
|
|
299
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const cachedRegexes = new Map<string, RegExp>();
|
|
2
|
+
|
|
3
|
+
export function createCachedRegex(pattern: string) {
|
|
4
|
+
const cached = cachedRegexes.get(pattern);
|
|
5
|
+
if (cached) return cached;
|
|
6
|
+
|
|
7
|
+
const regex = new RegExp(pattern);
|
|
8
|
+
cachedRegexes.set(pattern, regex);
|
|
9
|
+
return regex;
|
|
10
|
+
}
|