@hubspot/app-connect-sdk 1.0.0-alpha.12 → 1.0.0-alpha.13
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/.turbo/turbo-format$colon$check.log +1 -1
- package/.turbo/turbo-test.log +79 -61
- package/.turbo/turbo-tsdown.log +119 -118
- package/build/tsconfig.browser.tsbuildinfo +1 -1
- package/build/tsconfig.server.tsbuildinfo +1 -1
- package/dist/browser/{HubSpotAppConnect-COQgPrFn.js → HubSpotAppConnect-DFe9b90e.js} +1 -2
- package/dist/browser/HubSpotAppConnect-DFe9b90e.js.map +1 -0
- package/dist/browser/create-crdncXsh.js.map +1 -1
- package/dist/browser/react/lovable.d.ts +1 -2
- package/dist/browser/react/lovable.js +1 -1
- package/dist/browser/react/lovable.js.map +1 -1
- package/dist/browser/react.d.ts +1 -2
- package/dist/browser/react.js +1 -1
- package/dist/server/api-client-core/apis/account/account-info.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/account/audit-logs.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/auth/oauth.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/automation/actions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/automation/sequences.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/business-units.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/authors.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/blog-settings.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/cms-content-audit.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/domains.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/hubdb.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/media-bridge.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/pages.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/posts.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/site-search.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/source-code.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/tags.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/url-mappings.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/cms/url-redirects.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/communication-preferences/subscriptions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/conversations/custom-channels.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/conversations/visitor-identification.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/conversations.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/app-uninstalls.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/appointments.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/associations-schema.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/associations.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/calling-extensions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/calls.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/carts.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/commerce-payments.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/commerce-subscriptions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/communications.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/companies.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/contacts.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/contracts.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/courses.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/crm-owners.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/custom-objects.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/deal-splits.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/deals.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/discounts.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/emails.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/exports.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/feedback-submissions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/fees.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/goal-targets.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/imports.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/invoices.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/leads.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/limits-tracking.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/line-items.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/listings.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/lists.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/meetings.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/notes.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/object-library.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/objects.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/orders.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/partner-clients.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/partner-services.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/pipelines.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/postal-mail.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/products.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/projects.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/properties.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/property-validations.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/public-app-crm-cards.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/public-app-feature-flags.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/quotes.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/schemas.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/services.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/tasks.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/taxes.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/tickets.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/timeline.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/transcriptions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/users.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/crm/video-conferencing-extension.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/events/manage-event-definitions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/events/send-event-completions.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/events.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/files.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/marketing/campaigns-public-api.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/marketing/marketing-emails.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/marketing/marketing-events.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/marketing/single-send.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/marketing/transactional-single-send.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/meta/origins.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/scheduler/meetings.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/settings/multicurrency.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/settings/tax-rates.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/settings/user-provisioning.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/webhooks-journal.generated.js.map +1 -1
- package/dist/server/api-client-core/apis/webhooks.generated.js.map +1 -1
- package/dist/server/api-client-core/binary-data.js.map +1 -1
- package/dist/server/api-client-core/client.js +5 -1
- package/dist/server/api-client-core/client.js.map +1 -1
- package/dist/server/api-client-core/codegen-helpers/file-op-wrappers.js.map +1 -1
- package/dist/server/api-client-core/errors.js.map +1 -1
- package/dist/server/api-client-core/op.js.map +1 -1
- package/dist/server/api-client-core/pagination.js.map +1 -1
- package/dist/server/api-client-core/plugins/fetch-transport.js +28 -8
- package/dist/server/api-client-core/plugins/fetch-transport.js.map +1 -1
- package/dist/server/constants.js.map +1 -1
- package/dist/server/deno/start.js.map +1 -1
- package/dist/server/hono/hono-request-handler.js +21 -17
- package/dist/server/hono/hono-request-handler.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/auth-complete.js +2 -1
- package/dist/server/hono/hubspot-connect-routes/auth-complete.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/auth-init-session.js +3 -1
- package/dist/server/hono/hubspot-connect-routes/auth-init-session.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/auth-logout.js +14 -8
- package/dist/server/hono/hubspot-connect-routes/auth-logout.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/auth-refresh.js +26 -18
- package/dist/server/hono/hubspot-connect-routes/auth-refresh.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/cimd-client-metadata-types.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/cimd-public-routes.js +4 -1
- package/dist/server/hono/hubspot-connect-routes/cimd-public-routes.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/fetch-hubspot-client-metadata.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/hubspot-connect-routes.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/load-hubspot-connect-routes-env.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/oauth-client.js.map +1 -1
- package/dist/server/hono/hubspot-connect-routes/utils.js +5 -5
- package/dist/server/hono/hubspot-connect-routes/utils.js.map +1 -1
- package/dist/server/hono/types.d.ts +0 -5
- package/dist/server/hono/utils/cookie-utils.js.map +1 -1
- package/dist/server/hono/utils/cors-middleware.js.map +1 -1
- package/dist/server/import-app-keys.js.map +1 -1
- package/dist/server/lovable/create-app-function-start.d.ts +1 -1
- package/dist/server/lovable/create-app-function-start.js +3 -9
- package/dist/server/lovable/create-app-function-start.js.map +1 -1
- package/dist/server/lovable/hubspot-connect/index.js.map +1 -1
- package/dist/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.js +4 -10
- package/dist/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.js.map +1 -1
- package/dist/server/sanitize-request.js +0 -11
- package/dist/server/sanitize-request.js.map +1 -1
- package/dist/server/secure-start-core.js.map +1 -1
- package/dist/server/shared/constants.js +2 -2
- package/dist/server/shared/constants.js.map +1 -1
- package/dist/server/shared/encoding/base64.js.map +1 -1
- package/dist/server/shared/encoding/sha256.js.map +1 -1
- package/dist/server/shared/logger.js.map +1 -1
- package/dist/server/types.d.ts +1 -35
- package/dist/server/utils/cookie-utils.js.map +1 -1
- package/dist/server/utils/dpop-utils.js.map +1 -1
- package/dist/server/utils/env-utils.js +1 -1
- package/dist/server/utils/env-utils.js.map +1 -1
- package/dist/server/utils/hubspot-dpop-auth-headers.js +38 -0
- package/dist/server/utils/hubspot-dpop-auth-headers.js.map +1 -0
- package/dist/server/utils/jwk-utils.js.map +1 -1
- package/dist/server/utils/jwt-utils.js.map +1 -1
- package/package.json +15 -15
- package/src/browser/app-connect-controller/init.test.ts +4 -4
- package/src/browser/app-connect-controller/init.ts +2 -2
- package/src/browser/react/components/ConnectButton/ConnectButton.tsx +0 -1
- package/src/server/api-client-core/client.ts +5 -1
- package/src/server/api-client-core/plugins/fetch-transport.test.ts +114 -0
- package/src/server/api-client-core/plugins/fetch-transport.ts +65 -11
- package/src/server/api-client-core/plugins/index.ts +1 -0
- package/src/server/hono/hono-request-handler.ts +33 -19
- package/src/server/hono/hubspot-connect-routes/auth-complete.test.ts +2 -2
- package/src/server/hono/hubspot-connect-routes/auth-complete.ts +1 -0
- package/src/server/hono/hubspot-connect-routes/auth-init-session.test.ts +2 -2
- package/src/server/hono/hubspot-connect-routes/auth-init-session.ts +2 -0
- package/src/server/hono/hubspot-connect-routes/auth-logout.ts +21 -10
- package/src/server/hono/hubspot-connect-routes/auth-refresh.ts +18 -9
- package/src/server/hono/hubspot-connect-routes/cimd-public-routes.test.ts +7 -6
- package/src/server/hono/hubspot-connect-routes/cimd-public-routes.ts +5 -1
- package/src/server/hono/hubspot-connect-routes/hubspot-connect-routes.ts +2 -1
- package/src/server/hono/hubspot-connect-routes/utils.test.ts +16 -46
- package/src/server/hono/hubspot-connect-routes/utils.ts +6 -6
- package/src/server/hono/types.ts +0 -5
- package/src/server/lovable/create-app-function-start.ts +2 -9
- package/src/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.ts +4 -11
- package/src/server/sanitize-request.ts +1 -12
- package/src/server/types.ts +0 -36
- package/src/server/utils/env-utils.ts +1 -1
- package/src/server/utils/hubspot-dpop-auth-headers.test.ts +43 -0
- package/src/server/utils/hubspot-dpop-auth-headers.ts +48 -0
- package/src/shared/constants.ts +1 -1
- package/dist/browser/HubSpotAppConnect-COQgPrFn.js.map +0 -1
- package/dist/server/proxy.js +0 -68
- package/dist/server/proxy.js.map +0 -1
- package/src/server/proxy.test.ts +0 -80
- package/src/server/proxy.ts +0 -119
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { OAUTH_CALLBACK_PATH } from "../../shared/constants.js";
|
|
2
2
|
import { serializeCookie } from "../utils/cookie-utils.js";
|
|
3
3
|
//#region src/server/hono/hubspot-connect-routes/utils.ts
|
|
4
4
|
function clearTempCookie(name) {
|
|
@@ -54,7 +54,7 @@ function parseAppOriginHeader(originHeader) {
|
|
|
54
54
|
* the OAuth token endpoint's `redirect_uri` check).
|
|
55
55
|
*/
|
|
56
56
|
function buildFrontendOAuthRedirectUri(appOrigin) {
|
|
57
|
-
return `${appOrigin}${
|
|
57
|
+
return `${appOrigin}${OAUTH_CALLBACK_PATH}`;
|
|
58
58
|
}
|
|
59
59
|
function isSafeReturnPath(rawPath) {
|
|
60
60
|
if (!rawPath.startsWith("/")) return false;
|
|
@@ -94,15 +94,15 @@ function buildHubSpotConnectRequestOrigin(options) {
|
|
|
94
94
|
* origin.
|
|
95
95
|
*/
|
|
96
96
|
function buildOAuthRedirectUriFromRequest(options) {
|
|
97
|
-
const
|
|
98
|
-
return `${
|
|
97
|
+
const { appOrigin } = options;
|
|
98
|
+
return `${appOrigin}${OAUTH_CALLBACK_PATH}`;
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
101
101
|
* CIMD `client_id` URL: `{origin}{basePath}/client.json`.
|
|
102
102
|
*/
|
|
103
103
|
function buildCimdClientIdUrlFromRequest(options) {
|
|
104
104
|
const trimmed = normalizeHubSpotConnectBasePath(options.basePath);
|
|
105
|
-
return `${buildHubSpotConnectRequestOrigin(options)}${trimmed}/client.json`;
|
|
105
|
+
return `${buildHubSpotConnectRequestOrigin(options)}${trimmed}/client.json?app_origin=${encodeURIComponent(options.appOrigin)}`;
|
|
106
106
|
}
|
|
107
107
|
/**
|
|
108
108
|
* App JWKS URL published in CIMD: `{origin}{basePath}/jwks.json`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../../../../src/server/hono/hubspot-connect-routes/utils.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"utils.js","names":[],"sources":["../../../../src/server/hono/hubspot-connect-routes/utils.ts"],"sourcesContent":["import { OAUTH_CALLBACK_PATH } from '../../../shared/constants.ts';\nimport { serializeCookie } from '../utils/cookie-utils.ts';\n\nexport function clearTempCookie(name: string): string {\n return serializeCookie({\n name,\n value: '',\n path: '/',\n sameSite: 'None',\n maxAge: 0,\n partitioned: true,\n });\n}\n\n/**\n * Parses the request `Origin` header into the canonical origin\n * string (`URL.origin`) or returns `null` when the header is\n * missing, malformed, or carries a scheme/host the SDK does not\n * accept.\n *\n * Accepted shapes:\n *\n * - `https://<host>` for production deployments.\n * - `http://localhost[:<port>]` and `http://127.0.0.1[:<port>]`\n * for local development; browsers exempt these from the `Secure`\n * cookie restriction.\n *\n * Rejects values with a path/query/hash component (the request\n * `Origin` header is by spec a bare origin, so anything else\n * indicates a malformed or hostile request).\n */\nexport function parseAppOriginHeader(\n originHeader: string | undefined\n): string | null {\n if (!originHeader) return null;\n let parsed: URL;\n try {\n parsed = new URL(originHeader);\n } catch {\n return null;\n }\n if (parsed.pathname !== '/' && parsed.pathname !== '') return null;\n if (parsed.search !== '' || parsed.hash !== '') return null;\n if (parsed.protocol === 'https:') return parsed.origin;\n if (\n parsed.protocol === 'http:' &&\n (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1')\n ) {\n return parsed.origin;\n }\n return null;\n}\n\n/**\n * OAuth `redirect_uri` for the cross-origin app shape: the OAuth\n * callback lands on the **frontend** origin (not the SDK's edge\n * function host), so all cookies set by `init-session` and read by\n * `auth/complete` live in the same `(frontend, edge)` CHIPS\n * partition.\n *\n * Used by `auth/init-session` (when building `authorization_url`)\n * and `auth/complete` (which must rebuild the same value to satisfy\n * the OAuth token endpoint's `redirect_uri` check).\n */\nexport function buildFrontendOAuthRedirectUri(appOrigin: string): string {\n return `${appOrigin}${OAUTH_CALLBACK_PATH}`;\n}\n\nexport function isSafeReturnPath(rawPath: string): boolean {\n if (!rawPath.startsWith('/')) return false;\n if (rawPath.includes('\\0')) return false;\n let decoded: string;\n try {\n decoded = decodeURIComponent(rawPath);\n } catch {\n return false;\n }\n if (!decoded.startsWith('/')) return false;\n const second = decoded.charAt(1);\n if (second === '/' || second === '\\\\') return false;\n return true;\n}\n\nexport function getRequestHost(requestUrl: string): string {\n return new URL(requestUrl).host;\n}\n\nexport interface GetRequestHostForHubspotConnectOptions {\n requestUrl: string;\n xForwardedHost?: string | undefined;\n /** `Host` when `X-Forwarded-Host` is absent (some proxies only set `X-Forwarded-Proto`). */\n requestHostHeader?: string | undefined;\n}\n\n/**\n * Host for CIMD `client_id` URLs when hubspot-connect sits behind a reverse\n * proxy (e.g. Vite → Deno): prefers `X-Forwarded-Host`, then `Host`, then the\n * request URL host.\n */\nexport function getRequestHostForHubspotConnect(\n options: GetRequestHostForHubspotConnectOptions\n): string {\n const rawForwarded = options.xForwardedHost?.split(',')[0]?.trim();\n if (rawForwarded) {\n try {\n return new URL(`https://${rawForwarded}`).host;\n } catch {\n /* invalid forwarded host */\n }\n }\n const rawHost = options.requestHostHeader?.split(',')[0]?.trim();\n if (rawHost) {\n try {\n return new URL(`https://${rawHost}`).host;\n } catch {\n /* invalid host header */\n }\n }\n return getRequestHost(options.requestUrl);\n}\n\nexport interface BuildOAuthRedirectUriFromRequestOptions {\n requestUrl: string;\n basePath: string;\n xForwardedProto?: string | undefined;\n xForwardedHost?: string | undefined;\n /** `Host` when `X-Forwarded-Host` is absent but `X-Forwarded-Proto` is set. */\n requestHostHeader?: string | undefined;\n appOrigin: string;\n}\n\nfunction normalizeHubSpotConnectBasePath(basePath: string): string {\n return basePath.endsWith('/') && basePath.length > 1\n ? basePath.slice(0, -1)\n : basePath;\n}\n\n/**\n * Public origin for hubspot-connect URLs (`redirect_uri`, CIMD `client_id`,\n * `jwks_uri`). Matches the host/proto rules used for the OAuth callback.\n */\nexport function buildHubSpotConnectRequestOrigin(\n options: BuildOAuthRedirectUriFromRequestOptions\n): string {\n const { requestUrl, xForwardedProto, xForwardedHost, requestHostHeader } =\n options;\n const proto = xForwardedProto?.split(',')[0]?.trim();\n if (proto && (proto === 'http' || proto === 'https')) {\n const forwardedHost = xForwardedHost?.split(',')[0]?.trim();\n const hostHeader = requestHostHeader?.split(',')[0]?.trim();\n const hostPart = forwardedHost || hostHeader || new URL(requestUrl).host;\n return `${proto}://${hostPart}`;\n }\n return new URL(requestUrl).origin;\n}\n\n/**\n * OAuth `redirect_uri` for the hubspot-connect callback. Uses\n * `X-Forwarded-Proto` with `X-Forwarded-Host`, then `Host`, then the request URL\n * host when the proto is forwarded (reverse proxy); otherwise the request URL\n * origin.\n */\nexport function buildOAuthRedirectUriFromRequest(\n options: BuildOAuthRedirectUriFromRequestOptions\n): string {\n const { appOrigin } = options;\n return `${appOrigin}${OAUTH_CALLBACK_PATH}`;\n}\n\n/**\n * CIMD `client_id` URL: `{origin}{basePath}/client.json`.\n */\nexport function buildCimdClientIdUrlFromRequest(\n options: BuildOAuthRedirectUriFromRequestOptions\n): string {\n const trimmed = normalizeHubSpotConnectBasePath(options.basePath);\n const origin = buildHubSpotConnectRequestOrigin(options);\n return `${origin}${trimmed}/client.json?app_origin=${encodeURIComponent(options.appOrigin)}`;\n}\n\n/**\n * App JWKS URL published in CIMD: `{origin}{basePath}/jwks.json`.\n */\nexport function buildHubSpotAppJwksUrlFromRequest(\n options: BuildOAuthRedirectUriFromRequestOptions\n): string {\n const trimmed = normalizeHubSpotConnectBasePath(options.basePath);\n const origin = buildHubSpotConnectRequestOrigin(options);\n return `${origin}${trimmed}/jwks.json`;\n}\n\nexport function isPositiveFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n"],"mappings":";;;AAGA,SAAgB,gBAAgB,MAAsB;CACpD,OAAO,gBAAgB;EACrB;EACA,OAAO;EACP,MAAM;EACN,UAAU;EACV,QAAQ;EACR,aAAa;CACf,CAAC;AACH;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,qBACd,cACe;CACf,IAAI,CAAC,cAAc,OAAO;CAC1B,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,YAAY;CAC/B,QAAQ;EACN,OAAO;CACT;CACA,IAAI,OAAO,aAAa,OAAO,OAAO,aAAa,IAAI,OAAO;CAC9D,IAAI,OAAO,WAAW,MAAM,OAAO,SAAS,IAAI,OAAO;CACvD,IAAI,OAAO,aAAa,UAAU,OAAO,OAAO;CAChD,IACE,OAAO,aAAa,YACnB,OAAO,aAAa,eAAe,OAAO,aAAa,cAExD,OAAO,OAAO;CAEhB,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,8BAA8B,WAA2B;CACvE,OAAO,GAAG,YAAY;AACxB;AAEA,SAAgB,iBAAiB,SAA0B;CACzD,IAAI,CAAC,QAAQ,WAAW,GAAG,GAAG,OAAO;CACrC,IAAI,QAAQ,SAAS,IAAI,GAAG,OAAO;CACnC,IAAI;CACJ,IAAI;EACF,UAAU,mBAAmB,OAAO;CACtC,QAAQ;EACN,OAAO;CACT;CACA,IAAI,CAAC,QAAQ,WAAW,GAAG,GAAG,OAAO;CACrC,MAAM,SAAS,QAAQ,OAAO,CAAC;CAC/B,IAAI,WAAW,OAAO,WAAW,MAAM,OAAO;CAC9C,OAAO;AACT;AAkDA,SAAS,gCAAgC,UAA0B;CACjE,OAAO,SAAS,SAAS,GAAG,KAAK,SAAS,SAAS,IAC/C,SAAS,MAAM,GAAG,EAAE,IACpB;AACN;;;;;AAMA,SAAgB,iCACd,SACQ;CACR,MAAM,EAAE,YAAY,iBAAiB,gBAAgB,sBACnD;CACF,MAAM,QAAQ,iBAAiB,MAAM,GAAG,EAAE,IAAI,KAAK;CACnD,IAAI,UAAU,UAAU,UAAU,UAAU,UAAU;EACpD,MAAM,gBAAgB,gBAAgB,MAAM,GAAG,EAAE,IAAI,KAAK;EAC1D,MAAM,aAAa,mBAAmB,MAAM,GAAG,EAAE,IAAI,KAAK;EAE1D,OAAO,GAAG,MAAM,KADC,iBAAiB,cAAc,IAAI,IAAI,UAAU,EAAE;CAEtE;CACA,OAAO,IAAI,IAAI,UAAU,EAAE;AAC7B;;;;;;;AAQA,SAAgB,iCACd,SACQ;CACR,MAAM,EAAE,cAAc;CACtB,OAAO,GAAG,YAAY;AACxB;;;;AAKA,SAAgB,gCACd,SACQ;CACR,MAAM,UAAU,gCAAgC,QAAQ,QAAQ;CAEhE,OAAO,GADQ,iCAAiC,OACjC,IAAI,QAAQ,0BAA0B,mBAAmB,QAAQ,SAAS;AAC3F;;;;AAKA,SAAgB,kCACd,SACQ;CACR,MAAM,UAAU,gCAAgC,QAAQ,QAAQ;CAEhE,OAAO,GADQ,iCAAiC,OACjC,IAAI,QAAQ;AAC7B;AAEA,SAAgB,uBAAuB,OAAiC;CACtE,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ;AACxE"}
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { HubSpotClient } from "../api-client-core/types.js";
|
|
2
|
-
import { HubSpotProxy } from "../types.js";
|
|
3
2
|
|
|
4
3
|
//#region src/server/hono/types.d.ts
|
|
5
4
|
interface AppConnectRequestContext {
|
|
6
|
-
/**
|
|
7
|
-
* HubSpot API proxy.
|
|
8
|
-
*/
|
|
9
|
-
proxy: HubSpotProxy;
|
|
10
5
|
/**
|
|
11
6
|
* HubSpot API client.
|
|
12
7
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookie-utils.js","names":[],"sources":["../../../../src/server/hono/utils/cookie-utils.ts"],"sourcesContent":["import type { Context } from 'hono';\n\nexport interface SetResponseCookieOptions {\n c: Context;\n value: string;\n}\n\n/**\n * Appends a `Set-Cookie` header to the response. Hono replaces single\n * headers by default, so this uses `{ append: true }` to emit multiple\n * cookies on the same response.\n */\nexport function setResponseCookie(options: SetResponseCookieOptions): void {\n const { c, value } = options;\n c.header('Set-Cookie', value, { append: true });\n}\n\nexport interface SerializeCookieOptions {\n name: string;\n value: string;\n /** `__Host-` prefix requires `Path=/` and is recommended for session cookies. */\n path: string;\n /**\n * Defaults to `Strict`.\n *\n * - `Strict`: only sent on same-site requests. Default for self-hosted\n * same-origin deployments.\n * - `Lax`: also sent on top-level cross-site GET navigations. Use for\n * short-lived OAuth temp cookies that need to survive a redirect.\n * - `None`: sent on all cross-site requests; **requires `Secure=true`\n * and is typically combined with `Partitioned=true`** for the\n * cross-origin Lovable / Supabase deployment shape.\n */\n sameSite?: 'Strict' | 'Lax' | 'None';\n /** Lifetime in seconds. `0` deletes the cookie. */\n maxAge: number;\n /** Defaults to `true`; only set `false` for tests or non-HTTPS dev hosts. */\n secure?: boolean;\n /** Defaults to `true`. */\n httpOnly?: boolean;\n /**\n * When `true`, appends the `Partitioned` attribute (CHIPS — Cookies\n * Having Independent Partitioned State). The browser then keys the\n * cookie by `(top-level site, cookie host)` instead of by cookie\n * host alone, which is required for the cross-origin SDK shape\n * where the React app and the SDK's edge functions live on\n * different sites and third-party cookies are blocked.\n *\n * Defaults to `false`. Browsers ignore `Partitioned` on cookies\n * without `Secure=true` and reject it on cookies without\n * `SameSite=None`.\n */\n partitioned?: boolean;\n}\n\n/**\n * Builds a `Set-Cookie` header value with HubSpot's default attributes\n * (HttpOnly, Secure, SameSite). Centralizes the serialization so all\n * cookies share the same policy.\n */\nexport function serializeCookie(options: SerializeCookieOptions): string {\n const {\n name,\n value,\n path,\n sameSite = 'Strict',\n maxAge,\n secure = true,\n httpOnly = true,\n partitioned = false,\n } = options;\n const parts: string[] = [`${name}=${value}`];\n if (httpOnly) parts.push('HttpOnly');\n if (secure) parts.push('Secure');\n parts.push(`SameSite=${sameSite}`);\n parts.push(`Path=${path}`);\n parts.push(`Max-Age=${maxAge}`);\n if (partitioned) parts.push('Partitioned');\n return parts.join('; ');\n}\n"],"mappings":";;;;;;AAYA,SAAgB,kBAAkB,SAAyC;CACzE,MAAM,EAAE,GAAG,UAAU;CACrB,EAAE,OAAO,cAAc,OAAO,EAAE,QAAQ,
|
|
1
|
+
{"version":3,"file":"cookie-utils.js","names":[],"sources":["../../../../src/server/hono/utils/cookie-utils.ts"],"sourcesContent":["import type { Context } from 'hono';\n\nexport interface SetResponseCookieOptions {\n c: Context;\n value: string;\n}\n\n/**\n * Appends a `Set-Cookie` header to the response. Hono replaces single\n * headers by default, so this uses `{ append: true }` to emit multiple\n * cookies on the same response.\n */\nexport function setResponseCookie(options: SetResponseCookieOptions): void {\n const { c, value } = options;\n c.header('Set-Cookie', value, { append: true });\n}\n\nexport interface SerializeCookieOptions {\n name: string;\n value: string;\n /** `__Host-` prefix requires `Path=/` and is recommended for session cookies. */\n path: string;\n /**\n * Defaults to `Strict`.\n *\n * - `Strict`: only sent on same-site requests. Default for self-hosted\n * same-origin deployments.\n * - `Lax`: also sent on top-level cross-site GET navigations. Use for\n * short-lived OAuth temp cookies that need to survive a redirect.\n * - `None`: sent on all cross-site requests; **requires `Secure=true`\n * and is typically combined with `Partitioned=true`** for the\n * cross-origin Lovable / Supabase deployment shape.\n */\n sameSite?: 'Strict' | 'Lax' | 'None';\n /** Lifetime in seconds. `0` deletes the cookie. */\n maxAge: number;\n /** Defaults to `true`; only set `false` for tests or non-HTTPS dev hosts. */\n secure?: boolean;\n /** Defaults to `true`. */\n httpOnly?: boolean;\n /**\n * When `true`, appends the `Partitioned` attribute (CHIPS — Cookies\n * Having Independent Partitioned State). The browser then keys the\n * cookie by `(top-level site, cookie host)` instead of by cookie\n * host alone, which is required for the cross-origin SDK shape\n * where the React app and the SDK's edge functions live on\n * different sites and third-party cookies are blocked.\n *\n * Defaults to `false`. Browsers ignore `Partitioned` on cookies\n * without `Secure=true` and reject it on cookies without\n * `SameSite=None`.\n */\n partitioned?: boolean;\n}\n\n/**\n * Builds a `Set-Cookie` header value with HubSpot's default attributes\n * (HttpOnly, Secure, SameSite). Centralizes the serialization so all\n * cookies share the same policy.\n */\nexport function serializeCookie(options: SerializeCookieOptions): string {\n const {\n name,\n value,\n path,\n sameSite = 'Strict',\n maxAge,\n secure = true,\n httpOnly = true,\n partitioned = false,\n } = options;\n const parts: string[] = [`${name}=${value}`];\n if (httpOnly) parts.push('HttpOnly');\n if (secure) parts.push('Secure');\n parts.push(`SameSite=${sameSite}`);\n parts.push(`Path=${path}`);\n parts.push(`Max-Age=${maxAge}`);\n if (partitioned) parts.push('Partitioned');\n return parts.join('; ');\n}\n"],"mappings":";;;;;;AAYA,SAAgB,kBAAkB,SAAyC;CACzE,MAAM,EAAE,GAAG,UAAU;CACrB,EAAE,OAAO,cAAc,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD;;;;;;AA6CA,SAAgB,gBAAgB,SAAyC;CACvE,MAAM,EACJ,MACA,OACA,MACA,WAAW,UACX,QACA,SAAS,MACT,WAAW,MACX,cAAc,UACZ;CACJ,MAAM,QAAkB,CAAC,GAAG,KAAK,GAAG,OAAO;CAC3C,IAAI,UAAU,MAAM,KAAK,UAAU;CACnC,IAAI,QAAQ,MAAM,KAAK,QAAQ;CAC/B,MAAM,KAAK,YAAY,UAAU;CACjC,MAAM,KAAK,QAAQ,MAAM;CACzB,MAAM,KAAK,WAAW,QAAQ;CAC9B,IAAI,aAAa,MAAM,KAAK,aAAa;CACzC,OAAO,MAAM,KAAK,IAAI;AACxB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors-middleware.js","names":[],"sources":["../../../../src/server/hono/utils/cors-middleware.ts"],"sourcesContent":["import type { Context, MiddlewareHandler } from 'hono';\n\nimport { HUBSPOT_APP_ORIGIN_COOKIE_NAME } from '../../constants.ts';\nimport { parseCookies } from '../../utils/cookie-utils.ts';\n\n/**\n * Comma-separated list of request headers the SDK accepts on\n * cross-site fetches. Mirrors the Supabase Edge Functions defaults\n * the Lovable AI agent emits today, plus `content-type` for the\n * `auth/complete` POST body and `accept` so JSON content negotiation\n * works.\n */\nconst ALLOWED_HEADERS = [\n 'authorization',\n 'x-client-info',\n 'apikey',\n 'content-type',\n 'accept',\n 'x-supabase-client-platform',\n 'x-supabase-client-platform-version',\n 'x-supabase-client-runtime',\n 'x-supabase-client-runtime-version',\n].join(', ');\n\nconst ALLOWED_METHODS = 'GET, POST, OPTIONS';\n\nconst PREFLIGHT_MAX_AGE_SECONDS = '600';\n\n/**\n * Reads the persisted app-origin cookie from the request, falling\n * back to the literal `Origin` request header. The cookie is the\n * authoritative pin once `auth/init-session` has run; on the very\n * first init-session call (no cookie yet) we just echo whatever\n * `Origin` the caller sent — the actual access decision is enforced\n * by cookie-based authentication on every other route, not by CORS.\n */\nfunction resolveAllowedOrigin(c: Context): string | null {\n const cookies = parseCookies(c.req.header('Cookie'));\n const pinned = cookies[HUBSPOT_APP_ORIGIN_COOKIE_NAME];\n if (pinned) return pinned;\n return c.req.header('Origin') ?? null;\n}\n\nfunction setSharedCorsHeaders(c: Context, allowOrigin: string): void {\n c.res.headers.set('Access-Control-Allow-Origin', allowOrigin);\n c.res.headers.set('Access-Control-Allow-Credentials', 'true');\n // `Origin` so caches differentiate per-caller responses; `Cookie`\n // because the allowed origin is derived from the persisted\n // `__Host-hs_app_origin` cookie.\n c.res.headers.set('Vary', 'Origin, Cookie');\n}\n\n/**\n * Hono middleware that emits credentialed CORS response headers for\n * the cross-origin Lovable / Supabase deployment shape.\n *\n * - On `OPTIONS` preflight: short-circuits with a 204 carrying\n * `Access-Control-Allow-*` headers. The browser will then send the\n * real request with cookies attached.\n * - On every other method: echoes the pinned `__Host-hs_app_origin`\n * cookie value (or, before init-session has run, the request\n * `Origin` header) as `Access-Control-Allow-Origin`, with\n * `Access-Control-Allow-Credentials: true`. The wildcard `*` is\n * forbidden by browsers when credentials are included, so the\n * middleware always echoes a concrete origin.\n *\n * Skips header emission entirely when the request has no `Origin`\n * (server-to-server calls, curl, etc.) so non-browser callers are\n * left untouched.\n */\nexport function corsMiddleware(): MiddlewareHandler {\n return async (c, next) => {\n const allowOrigin = resolveAllowedOrigin(c);\n\n if (c.req.method === 'OPTIONS') {\n const headers = new Headers();\n if (allowOrigin) {\n headers.set('Access-Control-Allow-Origin', allowOrigin);\n headers.set('Access-Control-Allow-Credentials', 'true');\n headers.set('Vary', 'Origin, Cookie');\n }\n headers.set('Access-Control-Allow-Methods', ALLOWED_METHODS);\n headers.set('Access-Control-Allow-Headers', ALLOWED_HEADERS);\n headers.set('Access-Control-Max-Age', PREFLIGHT_MAX_AGE_SECONDS);\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n\n if (allowOrigin) {\n setSharedCorsHeaders(c, allowOrigin);\n }\n return;\n };\n}\n"],"mappings":";;;;;;;;;;AAYA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;
|
|
1
|
+
{"version":3,"file":"cors-middleware.js","names":[],"sources":["../../../../src/server/hono/utils/cors-middleware.ts"],"sourcesContent":["import type { Context, MiddlewareHandler } from 'hono';\n\nimport { HUBSPOT_APP_ORIGIN_COOKIE_NAME } from '../../constants.ts';\nimport { parseCookies } from '../../utils/cookie-utils.ts';\n\n/**\n * Comma-separated list of request headers the SDK accepts on\n * cross-site fetches. Mirrors the Supabase Edge Functions defaults\n * the Lovable AI agent emits today, plus `content-type` for the\n * `auth/complete` POST body and `accept` so JSON content negotiation\n * works.\n */\nconst ALLOWED_HEADERS = [\n 'authorization',\n 'x-client-info',\n 'apikey',\n 'content-type',\n 'accept',\n 'x-supabase-client-platform',\n 'x-supabase-client-platform-version',\n 'x-supabase-client-runtime',\n 'x-supabase-client-runtime-version',\n].join(', ');\n\nconst ALLOWED_METHODS = 'GET, POST, OPTIONS';\n\nconst PREFLIGHT_MAX_AGE_SECONDS = '600';\n\n/**\n * Reads the persisted app-origin cookie from the request, falling\n * back to the literal `Origin` request header. The cookie is the\n * authoritative pin once `auth/init-session` has run; on the very\n * first init-session call (no cookie yet) we just echo whatever\n * `Origin` the caller sent — the actual access decision is enforced\n * by cookie-based authentication on every other route, not by CORS.\n */\nfunction resolveAllowedOrigin(c: Context): string | null {\n const cookies = parseCookies(c.req.header('Cookie'));\n const pinned = cookies[HUBSPOT_APP_ORIGIN_COOKIE_NAME];\n if (pinned) return pinned;\n return c.req.header('Origin') ?? null;\n}\n\nfunction setSharedCorsHeaders(c: Context, allowOrigin: string): void {\n c.res.headers.set('Access-Control-Allow-Origin', allowOrigin);\n c.res.headers.set('Access-Control-Allow-Credentials', 'true');\n // `Origin` so caches differentiate per-caller responses; `Cookie`\n // because the allowed origin is derived from the persisted\n // `__Host-hs_app_origin` cookie.\n c.res.headers.set('Vary', 'Origin, Cookie');\n}\n\n/**\n * Hono middleware that emits credentialed CORS response headers for\n * the cross-origin Lovable / Supabase deployment shape.\n *\n * - On `OPTIONS` preflight: short-circuits with a 204 carrying\n * `Access-Control-Allow-*` headers. The browser will then send the\n * real request with cookies attached.\n * - On every other method: echoes the pinned `__Host-hs_app_origin`\n * cookie value (or, before init-session has run, the request\n * `Origin` header) as `Access-Control-Allow-Origin`, with\n * `Access-Control-Allow-Credentials: true`. The wildcard `*` is\n * forbidden by browsers when credentials are included, so the\n * middleware always echoes a concrete origin.\n *\n * Skips header emission entirely when the request has no `Origin`\n * (server-to-server calls, curl, etc.) so non-browser callers are\n * left untouched.\n */\nexport function corsMiddleware(): MiddlewareHandler {\n return async (c, next) => {\n const allowOrigin = resolveAllowedOrigin(c);\n\n if (c.req.method === 'OPTIONS') {\n const headers = new Headers();\n if (allowOrigin) {\n headers.set('Access-Control-Allow-Origin', allowOrigin);\n headers.set('Access-Control-Allow-Credentials', 'true');\n headers.set('Vary', 'Origin, Cookie');\n }\n headers.set('Access-Control-Allow-Methods', ALLOWED_METHODS);\n headers.set('Access-Control-Allow-Headers', ALLOWED_HEADERS);\n headers.set('Access-Control-Max-Age', PREFLIGHT_MAX_AGE_SECONDS);\n return new Response(null, { status: 204, headers });\n }\n\n await next();\n\n if (allowOrigin) {\n setSharedCorsHeaders(c, allowOrigin);\n }\n return;\n };\n}\n"],"mappings":";;;;;;;;;;AAYA,MAAM,kBAAkB;CACtB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,EAAE,KAAK,IAAI;AAEX,MAAM,kBAAkB;AAExB,MAAM,4BAA4B;;;;;;;;;AAUlC,SAAS,qBAAqB,GAA2B;CAEvD,MAAM,SADU,aAAa,EAAE,IAAI,OAAO,QAAQ,CAC7B,EAAE;CACvB,IAAI,QAAQ,OAAO;CACnB,OAAO,EAAE,IAAI,OAAO,QAAQ,KAAK;AACnC;AAEA,SAAS,qBAAqB,GAAY,aAA2B;CACnE,EAAE,IAAI,QAAQ,IAAI,+BAA+B,WAAW;CAC5D,EAAE,IAAI,QAAQ,IAAI,oCAAoC,MAAM;CAI5D,EAAE,IAAI,QAAQ,IAAI,QAAQ,gBAAgB;AAC5C;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,iBAAoC;CAClD,OAAO,OAAO,GAAG,SAAS;EACxB,MAAM,cAAc,qBAAqB,CAAC;EAE1C,IAAI,EAAE,IAAI,WAAW,WAAW;GAC9B,MAAM,UAAU,IAAI,QAAQ;GAC5B,IAAI,aAAa;IACf,QAAQ,IAAI,+BAA+B,WAAW;IACtD,QAAQ,IAAI,oCAAoC,MAAM;IACtD,QAAQ,IAAI,QAAQ,gBAAgB;GACtC;GACA,QAAQ,IAAI,gCAAgC,eAAe;GAC3D,QAAQ,IAAI,gCAAgC,eAAe;GAC3D,QAAQ,IAAI,0BAA0B,yBAAyB;GAC/D,OAAO,IAAI,SAAS,MAAM;IAAE,QAAQ;IAAK;GAAQ,CAAC;EACpD;EAEA,MAAM,KAAK;EAEX,IAAI,aACF,qBAAqB,GAAG,WAAW;CAGvC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-app-keys.js","names":[],"sources":["../../src/server/import-app-keys.ts"],"sourcesContent":["import type { AppKeys } from './types.ts';\nimport { base64StandardToArrayBuffer } from './utils/base64-utils.ts';\n\n/**\n * Imports a base64-encoded PKCS8 ES256 private key into the\n * `AppKeys` shape used throughout the SDK.\n *\n * The function imports the key twice: once as **extractable** to\n * derive the public JWK (via `crypto.subtle.exportKey`), and once\n * as **non-extractable** so the long-lived `appPrivateKey` can never\n * be exfiltrated.\n *\n * @throws {Error} When `envKey` is empty/undefined or when the key\n * isn't an EC P-256 keypair.\n */\nexport async function importAppKeys(\n envKey: string | undefined\n): Promise<AppKeys> {\n const b64 = envKey?.trim() ?? '';\n if (!b64) {\n throw new Error('HUBSPOT_APP_PRIVATE_KEY is not set');\n }\n\n const pkcs8 = base64StandardToArrayBuffer(b64);\n const tempPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'ECDSA', namedCurve: 'P-256' },\n true,\n ['sign']\n );\n const privateJwk = await crypto.subtle.exportKey('jwk', tempPrivateKey);\n if (\n privateJwk.kty !== 'EC' ||\n privateJwk.crv !== 'P-256' ||\n typeof privateJwk.x !== 'string' ||\n typeof privateJwk.y !== 'string'\n ) {\n throw new Error('Expected P-256 EC private key JWK with x and y');\n }\n const appPublicKeyJwk: JsonWebKey = {\n kty: 'EC',\n crv: 'P-256',\n x: privateJwk.x,\n y: privateJwk.y,\n };\n const appPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'ECDSA', namedCurve: 'P-256' },\n false,\n ['sign']\n );\n return { appPrivateKey, appPublicKeyJwk };\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,eAAsB,cACpB,QACkB;CAClB,MAAM,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"import-app-keys.js","names":[],"sources":["../../src/server/import-app-keys.ts"],"sourcesContent":["import type { AppKeys } from './types.ts';\nimport { base64StandardToArrayBuffer } from './utils/base64-utils.ts';\n\n/**\n * Imports a base64-encoded PKCS8 ES256 private key into the\n * `AppKeys` shape used throughout the SDK.\n *\n * The function imports the key twice: once as **extractable** to\n * derive the public JWK (via `crypto.subtle.exportKey`), and once\n * as **non-extractable** so the long-lived `appPrivateKey` can never\n * be exfiltrated.\n *\n * @throws {Error} When `envKey` is empty/undefined or when the key\n * isn't an EC P-256 keypair.\n */\nexport async function importAppKeys(\n envKey: string | undefined\n): Promise<AppKeys> {\n const b64 = envKey?.trim() ?? '';\n if (!b64) {\n throw new Error('HUBSPOT_APP_PRIVATE_KEY is not set');\n }\n\n const pkcs8 = base64StandardToArrayBuffer(b64);\n const tempPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'ECDSA', namedCurve: 'P-256' },\n true,\n ['sign']\n );\n const privateJwk = await crypto.subtle.exportKey('jwk', tempPrivateKey);\n if (\n privateJwk.kty !== 'EC' ||\n privateJwk.crv !== 'P-256' ||\n typeof privateJwk.x !== 'string' ||\n typeof privateJwk.y !== 'string'\n ) {\n throw new Error('Expected P-256 EC private key JWK with x and y');\n }\n const appPublicKeyJwk: JsonWebKey = {\n kty: 'EC',\n crv: 'P-256',\n x: privateJwk.x,\n y: privateJwk.y,\n };\n const appPrivateKey = await crypto.subtle.importKey(\n 'pkcs8',\n pkcs8,\n { name: 'ECDSA', namedCurve: 'P-256' },\n false,\n ['sign']\n );\n return { appPrivateKey, appPublicKeyJwk };\n}\n"],"mappings":";;;;;;;;;;;;;;AAeA,eAAsB,cACpB,QACkB;CAClB,MAAM,MAAM,QAAQ,KAAK,KAAK;CAC9B,IAAI,CAAC,KACH,MAAM,IAAI,MAAM,oCAAoC;CAGtD,MAAM,QAAQ,4BAA4B,GAAG;CAC7C,MAAM,iBAAiB,MAAM,OAAO,OAAO,UACzC,SACA,OACA;EAAE,MAAM;EAAS,YAAY;CAAQ,GACrC,MACA,CAAC,MAAM,CACT;CACA,MAAM,aAAa,MAAM,OAAO,OAAO,UAAU,OAAO,cAAc;CACtE,IACE,WAAW,QAAQ,QACnB,WAAW,QAAQ,WACnB,OAAO,WAAW,MAAM,YACxB,OAAO,WAAW,MAAM,UAExB,MAAM,IAAI,MAAM,gDAAgD;CAElE,MAAM,kBAA8B;EAClC,KAAK;EACL,KAAK;EACL,GAAG,WAAW;EACd,GAAG,WAAW;CAChB;CAQA,OAAO;EAAE,eAAA,MAPmB,OAAO,OAAO,UACxC,SACA,OACA;GAAE,MAAM;GAAS,YAAY;EAAQ,GACrC,OACA,CAAC,MAAM,CACT;EACwB;CAAgB;AAC1C"}
|
|
@@ -17,7 +17,7 @@ type AppFunctionStart = (context: SecureStartContext) => Promise<void>;
|
|
|
17
17
|
/**
|
|
18
18
|
* Builds a Deno-style `start({ appKeys })` entry point that boots a
|
|
19
19
|
* Hono app under `basePath`, wires the SDK's per-request HubSpot
|
|
20
|
-
*
|
|
20
|
+
* client via `createAppConnectRequestHandler`, and serves it with
|
|
21
21
|
* `Deno.serve` on `PORT`.
|
|
22
22
|
*/
|
|
23
23
|
declare function createAppFunctionStart(options: CreateAppFunctionStartOptions): AppFunctionStart;
|
|
@@ -5,25 +5,19 @@ const serveOptions = typeof PORT === "string" ? { port: parseInt(PORT, 10) } : {
|
|
|
5
5
|
/**
|
|
6
6
|
* Builds a Deno-style `start({ appKeys })` entry point that boots a
|
|
7
7
|
* Hono app under `basePath`, wires the SDK's per-request HubSpot
|
|
8
|
-
*
|
|
8
|
+
* client via `createAppConnectRequestHandler`, and serves it with
|
|
9
9
|
* `Deno.serve` on `PORT`.
|
|
10
10
|
*/
|
|
11
11
|
function createAppFunctionStart(options) {
|
|
12
12
|
const { basePath, registerRoutes, logger } = options;
|
|
13
13
|
return async ({ appKeys }) => {
|
|
14
|
-
|
|
14
|
+
await Deno.serve(serveOptions, createAppConnectRequestHandler({
|
|
15
15
|
appKeys,
|
|
16
16
|
...logger !== void 0 ? { logger } : {},
|
|
17
17
|
registerRoutes: (app) => {
|
|
18
18
|
registerRoutes(app.basePath(basePath));
|
|
19
19
|
}
|
|
20
|
-
}));
|
|
21
|
-
try {
|
|
22
|
-
await server.finished;
|
|
23
|
-
} catch (error) {
|
|
24
|
-
if (error instanceof Deno.errors.AddrInUse) throw new Error(`Port ${PORT} is already in use`);
|
|
25
|
-
throw error;
|
|
26
|
-
}
|
|
20
|
+
})).finished;
|
|
27
21
|
};
|
|
28
22
|
}
|
|
29
23
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-app-function-start.js","names":[],"sources":["../../../src/server/lovable/create-app-function-start.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { Logger } from '../../shared/logger.ts';\nimport { createAppConnectRequestHandler } from '../hono/hono-request-handler.ts';\nimport type { AppConnectHonoEnv } from '../hono/types.ts';\nimport type { SecureStartContext } from '../secure-start-core.ts';\n\nconst PORT = Deno.env.get('PORT');\nconst serveOptions =\n typeof PORT === 'string' ? { port: parseInt(PORT, 10) } : {};\n\nexport type RegisterAppFunctionRoutesFunction = (\n app: Hono<AppConnectHonoEnv>\n) => void;\n\nexport interface CreateAppFunctionStartOptions {\n /** Base path the user's routes are mounted under (e.g. `/functions/v1/api`). */\n basePath: string;\n /** Attach app routes to the SDK-owned Hono instance. */\n registerRoutes: RegisterAppFunctionRoutesFunction;\n /** Optional logger forwarded to `createAppConnectRequestHandler`. */\n logger?: Logger;\n}\n\nexport type AppFunctionStart = (context: SecureStartContext) => Promise<void>;\n\n/**\n * Builds a Deno-style `start({ appKeys })` entry point that boots a\n * Hono app under `basePath`, wires the SDK's per-request HubSpot\n *
|
|
1
|
+
{"version":3,"file":"create-app-function-start.js","names":[],"sources":["../../../src/server/lovable/create-app-function-start.ts"],"sourcesContent":["import type { Hono } from 'hono';\n\nimport type { Logger } from '../../shared/logger.ts';\nimport { createAppConnectRequestHandler } from '../hono/hono-request-handler.ts';\nimport type { AppConnectHonoEnv } from '../hono/types.ts';\nimport type { SecureStartContext } from '../secure-start-core.ts';\n\nconst PORT = Deno.env.get('PORT');\nconst serveOptions =\n typeof PORT === 'string' ? { port: parseInt(PORT, 10) } : {};\n\nexport type RegisterAppFunctionRoutesFunction = (\n app: Hono<AppConnectHonoEnv>\n) => void;\n\nexport interface CreateAppFunctionStartOptions {\n /** Base path the user's routes are mounted under (e.g. `/functions/v1/api`). */\n basePath: string;\n /** Attach app routes to the SDK-owned Hono instance. */\n registerRoutes: RegisterAppFunctionRoutesFunction;\n /** Optional logger forwarded to `createAppConnectRequestHandler`. */\n logger?: Logger;\n}\n\nexport type AppFunctionStart = (context: SecureStartContext) => Promise<void>;\n\n/**\n * Builds a Deno-style `start({ appKeys })` entry point that boots a\n * Hono app under `basePath`, wires the SDK's per-request HubSpot\n * client via `createAppConnectRequestHandler`, and serves it with\n * `Deno.serve` on `PORT`.\n */\nexport function createAppFunctionStart(\n options: CreateAppFunctionStartOptions\n): AppFunctionStart {\n const { basePath, registerRoutes, logger } = options;\n\n return async ({ appKeys }) => {\n const server = Deno.serve(\n serveOptions,\n createAppConnectRequestHandler({\n appKeys,\n ...(logger !== undefined ? { logger } : {}),\n registerRoutes: (app) => {\n registerRoutes(app.basePath(basePath));\n },\n })\n );\n\n await server.finished;\n };\n}\n"],"mappings":";;AAOA,MAAM,OAAO,KAAK,IAAI,IAAI,MAAM;AAChC,MAAM,eACJ,OAAO,SAAS,WAAW,EAAE,MAAM,SAAS,MAAM,EAAE,EAAE,IAAI,CAAC;;;;;;;AAuB7D,SAAgB,uBACd,SACkB;CAClB,MAAM,EAAE,UAAU,gBAAgB,WAAW;CAE7C,OAAO,OAAO,EAAE,cAAc;EAY5B,MAXe,KAAK,MAClB,cACA,+BAA+B;GAC7B;GACA,GAAI,WAAW,KAAA,IAAY,EAAE,OAAO,IAAI,CAAC;GACzC,iBAAiB,QAAQ;IACvB,eAAe,IAAI,SAAS,QAAQ,CAAC;GACvC;EACF,CAAC,CAGQ,EAAE;CACf;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/server/lovable/hubspot-connect/index.ts"],"sourcesContent":["import { secureStart } from '../../deno/start.ts';\nimport { assertHubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport { runHubSpotConnectLovableServer } from './run-hubspot-connect-lovable-server.ts';\n\nexport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\n\nexport interface StartHubSpotConnectFunctionOptions {\n client: HubSpotConnectCimdClientMetadata;\n}\n\n/**\n * Lovable-style entry point for the hubspot-connect Deno function.\n * Loads `HUBSPOT_APP_PRIVATE_KEY` via `secureStart`, then serves OAuth\n * and CIMD routes under `/functions/v1/hubspot-connect`.\n */\nexport async function startHubSpotConnectFunction(\n options: StartHubSpotConnectFunctionOptions\n): Promise<void> {\n assertHubSpotConnectCimdClientMetadata(options.client);\n await secureStart(async () => ({\n start: (context) =>\n runHubSpotConnectLovableServer({\n ...context,\n cimdClientMetadata: options.client,\n }),\n }));\n}\n"],"mappings":";;;;;;;;;AAgBA,eAAsB,4BACpB,SACe;CACf,uCAAuC,QAAQ,
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/server/lovable/hubspot-connect/index.ts"],"sourcesContent":["import { secureStart } from '../../deno/start.ts';\nimport { assertHubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport { runHubSpotConnectLovableServer } from './run-hubspot-connect-lovable-server.ts';\n\nexport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\n\nexport interface StartHubSpotConnectFunctionOptions {\n client: HubSpotConnectCimdClientMetadata;\n}\n\n/**\n * Lovable-style entry point for the hubspot-connect Deno function.\n * Loads `HUBSPOT_APP_PRIVATE_KEY` via `secureStart`, then serves OAuth\n * and CIMD routes under `/functions/v1/hubspot-connect`.\n */\nexport async function startHubSpotConnectFunction(\n options: StartHubSpotConnectFunctionOptions\n): Promise<void> {\n assertHubSpotConnectCimdClientMetadata(options.client);\n await secureStart(async () => ({\n start: (context) =>\n runHubSpotConnectLovableServer({\n ...context,\n cimdClientMetadata: options.client,\n }),\n }));\n}\n"],"mappings":";;;;;;;;;AAgBA,eAAsB,4BACpB,SACe;CACf,uCAAuC,QAAQ,MAAM;CACrD,MAAM,YAAY,aAAa,EAC7B,QAAQ,YACN,+BAA+B;EAC7B,GAAG;EACH,oBAAoB,QAAQ;CAC9B,CAAC,EACL,EAAE;AACJ"}
|
|
@@ -5,28 +5,22 @@ import { Hono } from "hono";
|
|
|
5
5
|
//#region src/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.ts
|
|
6
6
|
const PORT = Deno.env.get("PORT");
|
|
7
7
|
const port = typeof PORT === "string" ? parseInt(PORT, 10) : void 0;
|
|
8
|
-
const
|
|
8
|
+
const PUBLIC_HUBSPOT_CONNECT_BASE_PATH = "/functions/v1/hubspot-connect";
|
|
9
9
|
async function runHubSpotConnectLovableServer(options) {
|
|
10
10
|
const { appKeys, cimdClientMetadata } = options;
|
|
11
11
|
const hubspotConnectEnv = loadHubSpotConnectRoutesEnv();
|
|
12
|
-
const app = new Hono().basePath(
|
|
12
|
+
const app = new Hono().basePath("/hubspot-connect");
|
|
13
13
|
registerHubSpotConnectRoutes({
|
|
14
14
|
app,
|
|
15
15
|
appKeys,
|
|
16
|
-
basePath:
|
|
16
|
+
basePath: PUBLIC_HUBSPOT_CONNECT_BASE_PATH,
|
|
17
17
|
hubspotConnectEnv,
|
|
18
18
|
cimdClientMetadata
|
|
19
19
|
});
|
|
20
20
|
const serveHandler = (request) => {
|
|
21
21
|
return app.fetch(request);
|
|
22
22
|
};
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
await server.finished;
|
|
26
|
-
} catch (error) {
|
|
27
|
-
if (error instanceof Deno.errors.AddrInUse) throw new Error(`Port ${PORT} is already in use`);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
23
|
+
await (port !== void 0 ? Deno.serve({ port }, serveHandler) : Deno.serve(serveHandler)).finished;
|
|
30
24
|
}
|
|
31
25
|
//#endregion
|
|
32
26
|
export { runHubSpotConnectLovableServer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-hubspot-connect-lovable-server.js","names":[],"sources":["../../../../src/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.ts"],"sourcesContent":["import { Hono } from 'hono';\n\nimport type { SecureStartContext } from '../../deno/start.ts';\nimport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport {\n loadHubSpotConnectRoutesEnv,\n registerHubSpotConnectRoutes,\n} from '../../hono/index.ts';\n\nconst PORT = Deno.env.get('PORT');\nconst port = typeof PORT === 'string' ? parseInt(PORT!, 10) : undefined;\n\nconst
|
|
1
|
+
{"version":3,"file":"run-hubspot-connect-lovable-server.js","names":[],"sources":["../../../../src/server/lovable/hubspot-connect/run-hubspot-connect-lovable-server.ts"],"sourcesContent":["import { Hono } from 'hono';\n\nimport type { SecureStartContext } from '../../deno/start.ts';\nimport type { HubSpotConnectCimdClientMetadata } from '../../hono/hubspot-connect-routes/cimd-client-metadata-types.ts';\nimport {\n loadHubSpotConnectRoutesEnv,\n registerHubSpotConnectRoutes,\n} from '../../hono/index.ts';\n\nconst PORT = Deno.env.get('PORT');\nconst port = typeof PORT === 'string' ? parseInt(PORT!, 10) : undefined;\n\nconst PUBLIC_HUBSPOT_CONNECT_BASE_PATH = '/functions/v1/hubspot-connect';\n\nexport interface RunHubSpotConnectLovableServerOptions extends SecureStartContext {\n cimdClientMetadata: HubSpotConnectCimdClientMetadata;\n}\n\nexport async function runHubSpotConnectLovableServer(\n options: RunHubSpotConnectLovableServerOptions\n): Promise<void> {\n const { appKeys, cimdClientMetadata } = options;\n const hubspotConnectEnv = loadHubSpotConnectRoutesEnv();\n\n const app = new Hono().basePath('/hubspot-connect');\n\n registerHubSpotConnectRoutes({\n app,\n appKeys,\n basePath: PUBLIC_HUBSPOT_CONNECT_BASE_PATH,\n hubspotConnectEnv,\n cimdClientMetadata,\n });\n\n const serveHandler = (request: Request): Response | Promise<Response> => {\n return app.fetch(request);\n };\n\n const server =\n port !== undefined\n ? Deno.serve({ port }, serveHandler)\n : Deno.serve(serveHandler);\n\n await server.finished;\n}\n"],"mappings":";;;;;AASA,MAAM,OAAO,KAAK,IAAI,IAAI,MAAM;AAChC,MAAM,OAAO,OAAO,SAAS,WAAW,SAAS,MAAO,EAAE,IAAI,KAAA;AAE9D,MAAM,mCAAmC;AAMzC,eAAsB,+BACpB,SACe;CACf,MAAM,EAAE,SAAS,uBAAuB;CACxC,MAAM,oBAAoB,4BAA4B;CAEtD,MAAM,MAAM,IAAI,KAAK,EAAE,SAAS,kBAAkB;CAElD,6BAA6B;EAC3B;EACA;EACA,UAAU;EACV;EACA;CACF,CAAC;CAED,MAAM,gBAAgB,YAAmD;EACvE,OAAO,IAAI,MAAM,OAAO;CAC1B;CAOA,OAJE,SAAS,KAAA,IACL,KAAK,MAAM,EAAE,KAAK,GAAG,YAAY,IACjC,KAAK,MAAM,YAAY,GAEhB;AACf"}
|
|
@@ -10,11 +10,6 @@ function serializeCookies(cookies) {
|
|
|
10
10
|
* Mutates `headers` in place: parses the `Cookie` header, drops every
|
|
11
11
|
* protected cookie (see {@link isProtectedCookieName}), and rewrites
|
|
12
12
|
* the header. Deletes the header entirely when nothing survives.
|
|
13
|
-
*
|
|
14
|
-
* Used by the SDK's auth middleware after it has read the access
|
|
15
|
-
* token / session ID, so user route handlers never see — and
|
|
16
|
-
* therefore cannot leak — those cookies, while CORS / auth
|
|
17
|
-
* middleware that ran first still got the raw values.
|
|
18
13
|
*/
|
|
19
14
|
function stripProtectedCookies(headers) {
|
|
20
15
|
const cookies = parseCookies(headers.get("Cookie"));
|
|
@@ -27,12 +22,6 @@ function stripProtectedCookies(headers) {
|
|
|
27
22
|
* Returns a clone of `original` whose `Cookie` header has every
|
|
28
23
|
* protected cookie removed (see {@link isProtectedCookieName}). When
|
|
29
24
|
* no other cookies remain, the header is dropped entirely.
|
|
30
|
-
*
|
|
31
|
-
* Standalone helper retained for callers that want a new Request
|
|
32
|
-
* (e.g. tests). Inside the SDK's per-request handler, the auth
|
|
33
|
-
* middleware uses {@link stripProtectedCookies} directly on
|
|
34
|
-
* `c.req.raw.headers` so the auth check itself can still read the
|
|
35
|
-
* protected cookies before they are stripped.
|
|
36
25
|
*/
|
|
37
26
|
function sanitizeRequest(original) {
|
|
38
27
|
const headers = new Headers(original.headers);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize-request.js","names":[],"sources":["../../src/server/sanitize-request.ts"],"sourcesContent":["import { isProtectedCookieName } from './constants.ts';\nimport { parseCookies } from './utils/cookie-utils.ts';\n\nfunction serializeCookies(cookies: Map<string, string>): string {\n const parts: string[] = [];\n for (const [name, value] of cookies) {\n parts.push(`${name}=${value}`);\n }\n return parts.join('; ');\n}\n\n/**\n * Mutates `headers` in place: parses the `Cookie` header, drops every\n * protected cookie (see {@link isProtectedCookieName}), and rewrites\n * the header. Deletes the header entirely when nothing survives.\n
|
|
1
|
+
{"version":3,"file":"sanitize-request.js","names":[],"sources":["../../src/server/sanitize-request.ts"],"sourcesContent":["import { isProtectedCookieName } from './constants.ts';\nimport { parseCookies } from './utils/cookie-utils.ts';\n\nfunction serializeCookies(cookies: Map<string, string>): string {\n const parts: string[] = [];\n for (const [name, value] of cookies) {\n parts.push(`${name}=${value}`);\n }\n return parts.join('; ');\n}\n\n/**\n * Mutates `headers` in place: parses the `Cookie` header, drops every\n * protected cookie (see {@link isProtectedCookieName}), and rewrites\n * the header. Deletes the header entirely when nothing survives.\n */\nfunction stripProtectedCookies(headers: Headers): void {\n const cookies = parseCookies(headers.get('Cookie'));\n const surviving = new Map<string, string>();\n for (const [name, value] of Object.entries(cookies)) {\n if (!isProtectedCookieName(name)) {\n surviving.set(name, value);\n }\n }\n\n if (surviving.size === 0) {\n headers.delete('cookie');\n } else {\n headers.set('cookie', serializeCookies(surviving));\n }\n}\n\n/**\n * Returns a clone of `original` whose `Cookie` header has every\n * protected cookie removed (see {@link isProtectedCookieName}). When\n * no other cookies remain, the header is dropped entirely.\n */\nexport function sanitizeRequest(original: Request): Request {\n const headers = new Headers(original.headers);\n stripProtectedCookies(headers);\n\n const init: RequestInit = {\n method: original.method,\n headers,\n redirect: original.redirect,\n signal: original.signal,\n };\n\n if (original.body !== null) {\n init.body = original.body;\n (init as RequestInit & { duplex: 'half' }).duplex = 'half';\n }\n\n return new Request(original.url, init);\n}\n"],"mappings":";;;AAGA,SAAS,iBAAiB,SAAsC;CAC9D,MAAM,QAAkB,CAAC;CACzB,KAAK,MAAM,CAAC,MAAM,UAAU,SAC1B,MAAM,KAAK,GAAG,KAAK,GAAG,OAAO;CAE/B,OAAO,MAAM,KAAK,IAAI;AACxB;;;;;;AAOA,SAAS,sBAAsB,SAAwB;CACrD,MAAM,UAAU,aAAa,QAAQ,IAAI,QAAQ,CAAC;CAClD,MAAM,4BAAY,IAAI,IAAoB;CAC1C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,GAChD,IAAI,CAAC,sBAAsB,IAAI,GAC7B,UAAU,IAAI,MAAM,KAAK;CAI7B,IAAI,UAAU,SAAS,GACrB,QAAQ,OAAO,QAAQ;MAEvB,QAAQ,IAAI,UAAU,iBAAiB,SAAS,CAAC;AAErD;;;;;;AAOA,SAAgB,gBAAgB,UAA4B;CAC1D,MAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;CAC5C,sBAAsB,OAAO;CAE7B,MAAM,OAAoB;EACxB,QAAQ,SAAS;EACjB;EACA,UAAU,SAAS;EACnB,QAAQ,SAAS;CACnB;CAEA,IAAI,SAAS,SAAS,MAAM;EAC1B,KAAK,OAAO,SAAS;EACrB,KAA2C,SAAS;CACtD;CAEA,OAAO,IAAI,QAAQ,SAAS,KAAK,IAAI;AACvC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secure-start-core.js","names":[],"sources":["../../src/server/secure-start-core.ts"],"sourcesContent":["import { importAppKeys } from './import-app-keys.ts';\nimport type { AppKeys } from './types.ts';\nimport {\n HUBSPOT_APP_PRIVATE_KEY_ENV,\n isHubspotAppPrivateKeyRequired,\n} from './utils/env-utils.ts';\n\n/**\n * Context passed to a function-style entrypoint loaded by `runSecureStart`.\n * Contains imported `AppKeys` when CIMD or DPoP is enabled; otherwise\n * `null` (traditional OAuth with `client_secret` and Bearer API calls).\n */\nexport interface SecureStartContext {\n appKeys: AppKeys | null;\n}\n\ninterface SecureEntryPointExports {\n start: (context: SecureStartContext) => Promise<void>;\n}\n\n/**\n * Loader returned to `runSecureStart` that produces the entrypoint\n * module. Always pass an `() => import('...')` arrow so the bundler\n * preserves dynamic-import semantics.\n */\nexport type SecureStartLoader = () => Promise<SecureEntryPointExports>;\n\n/**\n * Platform adapters injected by the runtime-specific shim\n * (`node/start.ts` or `deno/start.ts`). Keeping the env/exit/delete\n * functions out of the core lets the shared logic stay platform-free.\n */\nexport interface SecureStartAdapter {\n /** Reads the named env var, or returns `undefined` when unset. */\n readEnv: (key: string) => string | undefined;\n /** Removes the named env var so child processes cannot read it. */\n deleteEnv: (key: string) => void;\n /** Terminates the process with the given exit code. */\n exit: (code: number) => never;\n}\n\n/**\n * Loads `HUBSPOT_APP_PRIVATE_KEY` when CIMD or DPoP is enabled, deletes\n * it from the environment so later code cannot re-read it, imports the\n * entrypoint module via `loader`, and invokes `start` with `AppKeys` or\n * `null` when both features are off. On failure, logs the error and\n * exits with code 1.\n */\nexport async function runSecureStart(\n loader: SecureStartLoader,\n adapter: SecureStartAdapter\n): Promise<void> {\n let appKeys: AppKeys | null = null;\n if (isHubspotAppPrivateKeyRequired()) {\n const envKey = adapter.readEnv(HUBSPOT_APP_PRIVATE_KEY_ENV)?.trim();\n if (!envKey) {\n throw new Error(`${HUBSPOT_APP_PRIVATE_KEY_ENV} is not set`);\n }\n appKeys = await importAppKeys(envKey);\n adapter.deleteEnv(HUBSPOT_APP_PRIVATE_KEY_ENV);\n }\n\n const exports = await loader();\n return exports.start({ appKeys }).catch((error) => {\n console.error(error);\n return adapter.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;AAgDA,eAAsB,eACpB,QACA,SACe;CACf,IAAI,UAA0B;CAC9B,IAAI,
|
|
1
|
+
{"version":3,"file":"secure-start-core.js","names":[],"sources":["../../src/server/secure-start-core.ts"],"sourcesContent":["import { importAppKeys } from './import-app-keys.ts';\nimport type { AppKeys } from './types.ts';\nimport {\n HUBSPOT_APP_PRIVATE_KEY_ENV,\n isHubspotAppPrivateKeyRequired,\n} from './utils/env-utils.ts';\n\n/**\n * Context passed to a function-style entrypoint loaded by `runSecureStart`.\n * Contains imported `AppKeys` when CIMD or DPoP is enabled; otherwise\n * `null` (traditional OAuth with `client_secret` and Bearer API calls).\n */\nexport interface SecureStartContext {\n appKeys: AppKeys | null;\n}\n\ninterface SecureEntryPointExports {\n start: (context: SecureStartContext) => Promise<void>;\n}\n\n/**\n * Loader returned to `runSecureStart` that produces the entrypoint\n * module. Always pass an `() => import('...')` arrow so the bundler\n * preserves dynamic-import semantics.\n */\nexport type SecureStartLoader = () => Promise<SecureEntryPointExports>;\n\n/**\n * Platform adapters injected by the runtime-specific shim\n * (`node/start.ts` or `deno/start.ts`). Keeping the env/exit/delete\n * functions out of the core lets the shared logic stay platform-free.\n */\nexport interface SecureStartAdapter {\n /** Reads the named env var, or returns `undefined` when unset. */\n readEnv: (key: string) => string | undefined;\n /** Removes the named env var so child processes cannot read it. */\n deleteEnv: (key: string) => void;\n /** Terminates the process with the given exit code. */\n exit: (code: number) => never;\n}\n\n/**\n * Loads `HUBSPOT_APP_PRIVATE_KEY` when CIMD or DPoP is enabled, deletes\n * it from the environment so later code cannot re-read it, imports the\n * entrypoint module via `loader`, and invokes `start` with `AppKeys` or\n * `null` when both features are off. On failure, logs the error and\n * exits with code 1.\n */\nexport async function runSecureStart(\n loader: SecureStartLoader,\n adapter: SecureStartAdapter\n): Promise<void> {\n let appKeys: AppKeys | null = null;\n if (isHubspotAppPrivateKeyRequired()) {\n const envKey = adapter.readEnv(HUBSPOT_APP_PRIVATE_KEY_ENV)?.trim();\n if (!envKey) {\n throw new Error(`${HUBSPOT_APP_PRIVATE_KEY_ENV} is not set`);\n }\n appKeys = await importAppKeys(envKey);\n adapter.deleteEnv(HUBSPOT_APP_PRIVATE_KEY_ENV);\n }\n\n const exports = await loader();\n return exports.start({ appKeys }).catch((error) => {\n console.error(error);\n return adapter.exit(1);\n });\n}\n"],"mappings":";;;;;;;;;;AAgDA,eAAsB,eACpB,QACA,SACe;CACf,IAAI,UAA0B;CAC9B,IAAI,+BAA+B,GAAG;EACpC,MAAM,SAAS,QAAQ,QAAQ,2BAA2B,GAAG,KAAK;EAClE,IAAI,CAAC,QACH,MAAM,IAAI,MAAM,GAAG,4BAA4B,YAAY;EAE7D,UAAU,MAAM,cAAc,MAAM;EACpC,QAAQ,UAAU,2BAA2B;CAC/C;CAGA,QAAO,MADe,OAAO,GACd,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,UAAU;EACjD,QAAQ,MAAM,KAAK;EACnB,OAAO,QAAQ,KAAK,CAAC;CACvB,CAAC;AACH"}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* register `${app_origin}${HUBSPOT_FRONTEND_CALLBACK_PATH}` as a
|
|
14
14
|
* redirect URI in its HubSpot app settings.
|
|
15
15
|
*/
|
|
16
|
-
const
|
|
16
|
+
const OAUTH_CALLBACK_PATH = "/__hubspot_oauth_callback";
|
|
17
17
|
/**
|
|
18
18
|
* Query parameter on the `auth/complete` POST request carrying the
|
|
19
19
|
* authorization `code` HubSpot returned to the frontend callback.
|
|
@@ -25,6 +25,6 @@ const AUTH_COMPLETE_CODE_PARAM = "code";
|
|
|
25
25
|
*/
|
|
26
26
|
const AUTH_COMPLETE_STATE_PARAM = "state";
|
|
27
27
|
//#endregion
|
|
28
|
-
export { AUTH_COMPLETE_CODE_PARAM, AUTH_COMPLETE_STATE_PARAM,
|
|
28
|
+
export { AUTH_COMPLETE_CODE_PARAM, AUTH_COMPLETE_STATE_PARAM, OAUTH_CALLBACK_PATH };
|
|
29
29
|
|
|
30
30
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":[],"sources":["../../../src/shared/constants.ts"],"sourcesContent":["/**\n * Constants whose values are part of the contract between the browser\n * controller and the server-side hubspot-connect routes. Both halves\n * import from this module so the wire format stays in sync.\n */\n\n/**\n * Query parameter on the OAuth return URL that carries the new access\n * token's expiry (Unix epoch milliseconds). The browser controller\n * sets this in the URL after a successful `auth/complete` call and\n * then strips it during `initAppConnect` via `history.replaceState`.\n */\nexport const EXPIRES_AT_URL_PARAM = '__hs_expires_at';\n\n/**\n * Path the browser visits after HubSpot's authorize endpoint\n * redirects back to the app. Mounted on the **frontend** origin (not\n * the SDK's edge function host) so all OAuth-related cookies live in\n * the `(frontend, edge)` CHIPS partition.\n *\n * The SDK's `auth/init-session` builds the OAuth `redirect_uri` as\n * `${requestOrigin}${HUBSPOT_FRONTEND_CALLBACK_PATH}`. The browser\n * controller, on `start()`, recognizes this path on `window.location`\n * and forwards `?code` + `?state` to the SDK's `auth/complete`\n * endpoint via a credentialed cross-site fetch. The host app must\n * register `${app_origin}${HUBSPOT_FRONTEND_CALLBACK_PATH}` as a\n * redirect URI in its HubSpot app settings.\n */\nexport const
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../../src/shared/constants.ts"],"sourcesContent":["/**\n * Constants whose values are part of the contract between the browser\n * controller and the server-side hubspot-connect routes. Both halves\n * import from this module so the wire format stays in sync.\n */\n\n/**\n * Query parameter on the OAuth return URL that carries the new access\n * token's expiry (Unix epoch milliseconds). The browser controller\n * sets this in the URL after a successful `auth/complete` call and\n * then strips it during `initAppConnect` via `history.replaceState`.\n */\nexport const EXPIRES_AT_URL_PARAM = '__hs_expires_at';\n\n/**\n * Path the browser visits after HubSpot's authorize endpoint\n * redirects back to the app. Mounted on the **frontend** origin (not\n * the SDK's edge function host) so all OAuth-related cookies live in\n * the `(frontend, edge)` CHIPS partition.\n *\n * The SDK's `auth/init-session` builds the OAuth `redirect_uri` as\n * `${requestOrigin}${HUBSPOT_FRONTEND_CALLBACK_PATH}`. The browser\n * controller, on `start()`, recognizes this path on `window.location`\n * and forwards `?code` + `?state` to the SDK's `auth/complete`\n * endpoint via a credentialed cross-site fetch. The host app must\n * register `${app_origin}${HUBSPOT_FRONTEND_CALLBACK_PATH}` as a\n * redirect URI in its HubSpot app settings.\n */\nexport const OAUTH_CALLBACK_PATH = '/__hubspot_oauth_callback';\n\n/**\n * Query parameter on the `auth/complete` POST request carrying the\n * authorization `code` HubSpot returned to the frontend callback.\n */\nexport const AUTH_COMPLETE_CODE_PARAM = 'code';\n\n/**\n * Query parameter on the `auth/complete` POST request carrying the\n * OAuth `state` HubSpot echoed back to the frontend callback.\n */\nexport const AUTH_COMPLETE_STATE_PARAM = 'state';\n"],"mappings":";;;;;;;;;;;;;;;AA4BA,MAAa,sBAAsB;;;;;AAMnC,MAAa,2BAA2B;;;;;AAMxC,MAAa,4BAA4B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base64.js","names":[],"sources":["../../../../src/shared/encoding/base64.ts"],"sourcesContent":["/**\n * Base64url encoding and decoding helpers shared by the browser and\n * server halves of `@hubspot/app-connect-sdk`.\n *\n * Base64url (RFC 4648 §5) replaces `+`/`/` with `-`/`_` and drops `=`\n * padding. JWT, DPoP, and JWK thumbprints all use this variant.\n */\n\n/**\n * Encodes a binary buffer as a base64url string (RFC 4648 §5).\n */\nexport function base64url(input: ArrayBuffer | Uint8Array): string {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\n/**\n * Decodes a base64url string into a `Uint8Array`. Tolerates missing\n * padding by re-adding it before delegating to `atob`.\n */\nexport function base64urlDecode(encoded: string): Uint8Array<ArrayBuffer> {\n const padLen = (4 - (encoded.length % 4)) % 4;\n const padded =\n encoded.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padLen);\n const binary = atob(padded);\n const out = new Uint8Array(new ArrayBuffer(binary.length));\n for (let i = 0; i < binary.length; i++) {\n out[i] = binary.charCodeAt(i) ?? 0;\n }\n return out;\n}\n\n/**\n * Decodes a *standard* base64 string (with `+`/`/` and required `=`\n * padding) into an `ArrayBuffer`. Used for PKCS8-encoded private keys\n * stored in environment variables.\n */\nexport function base64StandardToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i) ?? 0;\n }\n return buf;\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,UAAU,OAAyC;CACjE,MAAM,QAAQ,iBAAiB,aAAa,QAAQ,IAAI,WAAW,
|
|
1
|
+
{"version":3,"file":"base64.js","names":[],"sources":["../../../../src/shared/encoding/base64.ts"],"sourcesContent":["/**\n * Base64url encoding and decoding helpers shared by the browser and\n * server halves of `@hubspot/app-connect-sdk`.\n *\n * Base64url (RFC 4648 §5) replaces `+`/`/` with `-`/`_` and drops `=`\n * padding. JWT, DPoP, and JWK thumbprints all use this variant.\n */\n\n/**\n * Encodes a binary buffer as a base64url string (RFC 4648 §5).\n */\nexport function base64url(input: ArrayBuffer | Uint8Array): string {\n const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);\n let binary = '';\n for (const byte of bytes) {\n binary += String.fromCharCode(byte);\n }\n return btoa(binary).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=/g, '');\n}\n\n/**\n * Decodes a base64url string into a `Uint8Array`. Tolerates missing\n * padding by re-adding it before delegating to `atob`.\n */\nexport function base64urlDecode(encoded: string): Uint8Array<ArrayBuffer> {\n const padLen = (4 - (encoded.length % 4)) % 4;\n const padded =\n encoded.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat(padLen);\n const binary = atob(padded);\n const out = new Uint8Array(new ArrayBuffer(binary.length));\n for (let i = 0; i < binary.length; i++) {\n out[i] = binary.charCodeAt(i) ?? 0;\n }\n return out;\n}\n\n/**\n * Decodes a *standard* base64 string (with `+`/`/` and required `=`\n * padding) into an `ArrayBuffer`. Used for PKCS8-encoded private keys\n * stored in environment variables.\n */\nexport function base64StandardToArrayBuffer(b64: string): ArrayBuffer {\n const bin = atob(b64);\n const buf = new ArrayBuffer(bin.length);\n const view = new Uint8Array(buf);\n for (let i = 0; i < bin.length; i++) {\n view[i] = bin.charCodeAt(i) ?? 0;\n }\n return buf;\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAgB,UAAU,OAAyC;CACjE,MAAM,QAAQ,iBAAiB,aAAa,QAAQ,IAAI,WAAW,KAAK;CACxE,IAAI,SAAS;CACb,KAAK,MAAM,QAAQ,OACjB,UAAU,OAAO,aAAa,IAAI;CAEpC,OAAO,KAAK,MAAM,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,OAAO,GAAG,EAAE,QAAQ,MAAM,EAAE;AAC9E;;;;;AAMA,SAAgB,gBAAgB,SAA0C;CACxE,MAAM,UAAU,IAAK,QAAQ,SAAS,KAAM;CAC5C,MAAM,SACJ,QAAQ,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,IAAI,IAAI,OAAO,MAAM;CACnE,MAAM,SAAS,KAAK,MAAM;CAC1B,MAAM,MAAM,IAAI,WAAW,IAAI,YAAY,OAAO,MAAM,CAAC;CACzD,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,KAAK,OAAO,WAAW,CAAC,KAAK;CAEnC,OAAO;AACT;;;;;;AAOA,SAAgB,4BAA4B,KAA0B;CACpE,MAAM,MAAM,KAAK,GAAG;CACpB,MAAM,MAAM,IAAI,YAAY,IAAI,MAAM;CACtC,MAAM,OAAO,IAAI,WAAW,GAAG;CAC/B,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAC9B,KAAK,KAAK,IAAI,WAAW,CAAC,KAAK;CAEjC,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sha256.js","names":[],"sources":["../../../../src/shared/encoding/sha256.ts"],"sourcesContent":["import { base64url } from './base64.ts';\n\n/**\n * Hashes a UTF-8 string with SHA-256 and encodes the digest as\n * base64url. Used to compute DPoP `ath`/`sid` claims, JWK thumbprints\n * (RFC 7638), and PKCE code challenges.\n */\nexport async function sha256base64url(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input)\n );\n return base64url(new Uint8Array(digest));\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,gBAAgB,OAAgC;CACpE,MAAM,SAAS,MAAM,OAAO,OAAO,OACjC,WACA,IAAI,
|
|
1
|
+
{"version":3,"file":"sha256.js","names":[],"sources":["../../../../src/shared/encoding/sha256.ts"],"sourcesContent":["import { base64url } from './base64.ts';\n\n/**\n * Hashes a UTF-8 string with SHA-256 and encodes the digest as\n * base64url. Used to compute DPoP `ath`/`sid` claims, JWK thumbprints\n * (RFC 7638), and PKCE code challenges.\n */\nexport async function sha256base64url(input: string): Promise<string> {\n const digest = await crypto.subtle.digest(\n 'SHA-256',\n new TextEncoder().encode(input)\n );\n return base64url(new Uint8Array(digest));\n}\n"],"mappings":";;;;;;;AAOA,eAAsB,gBAAgB,OAAgC;CACpE,MAAM,SAAS,MAAM,OAAO,OAAO,OACjC,WACA,IAAI,YAAY,EAAE,OAAO,KAAK,CAChC;CACA,OAAO,UAAU,IAAI,WAAW,MAAM,CAAC;AACzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/shared/logger.ts"],"sourcesContent":["/**\n * Pluggable logger contract used by the SDK on both the browser and\n * server. Consumers can pass `console`-like loggers, structured\n * loggers (pino / winston / etc.) or no-op stubs in tests.\n */\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\nfunction formatPrefix(name: string): string {\n return `[${name}]`;\n}\n\n/**\n * Creates a console-backed logger that prefixes every line with the\n * supplied `name`. Used as the default when no custom logger is\n * provided.\n */\nexport function createLogger(name: string): Logger {\n const prefix = formatPrefix(name);\n return {\n debug: (message, ...args) => {\n console.debug(prefix, message, ...args);\n },\n info: (message, ...args) => {\n console.info(prefix, message, ...args);\n },\n warn: (message, ...args) => {\n console.warn(prefix, message, ...args);\n },\n error: (message, ...args) => {\n console.error(prefix, message, ...args);\n },\n };\n}\n\n/**\n * Logger that swallows every message. Convenient for tests and for\n * the SDK's server-side handlers when no logger is provided by the\n * host application.\n */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n"],"mappings":";;;;;;AA4CA,MAAa,aAAqB;CAChC,aAAa;
|
|
1
|
+
{"version":3,"file":"logger.js","names":[],"sources":["../../../src/shared/logger.ts"],"sourcesContent":["/**\n * Pluggable logger contract used by the SDK on both the browser and\n * server. Consumers can pass `console`-like loggers, structured\n * loggers (pino / winston / etc.) or no-op stubs in tests.\n */\nexport interface Logger {\n debug: (message: string, ...args: unknown[]) => void;\n info: (message: string, ...args: unknown[]) => void;\n warn: (message: string, ...args: unknown[]) => void;\n error: (message: string, ...args: unknown[]) => void;\n}\n\nfunction formatPrefix(name: string): string {\n return `[${name}]`;\n}\n\n/**\n * Creates a console-backed logger that prefixes every line with the\n * supplied `name`. Used as the default when no custom logger is\n * provided.\n */\nexport function createLogger(name: string): Logger {\n const prefix = formatPrefix(name);\n return {\n debug: (message, ...args) => {\n console.debug(prefix, message, ...args);\n },\n info: (message, ...args) => {\n console.info(prefix, message, ...args);\n },\n warn: (message, ...args) => {\n console.warn(prefix, message, ...args);\n },\n error: (message, ...args) => {\n console.error(prefix, message, ...args);\n },\n };\n}\n\n/**\n * Logger that swallows every message. Convenient for tests and for\n * the SDK's server-side handlers when no logger is provided by the\n * host application.\n */\nexport const noopLogger: Logger = {\n debug: () => {},\n info: () => {},\n warn: () => {},\n error: () => {},\n};\n"],"mappings":";;;;;;AA4CA,MAAa,aAAqB;CAChC,aAAa,CAAC;CACd,YAAY,CAAC;CACb,YAAY,CAAC;CACb,aAAa,CAAC;AAChB"}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -10,40 +10,6 @@ interface AppKeys {
|
|
|
10
10
|
/** Public key in JWK form. Used to derive the JWK thumbprint and `cnf`. */
|
|
11
11
|
appPublicKeyJwk: JsonWebKey;
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
14
|
-
* Request shape accepted by `HubSpotProxy.fetch`. Only the `path`
|
|
15
|
-
* is required; everything else mirrors the equivalent fetch fields.
|
|
16
|
-
*/
|
|
17
|
-
interface HubSpotProxyRequest {
|
|
18
|
-
/** Path component of the upstream URL, including leading slash. */
|
|
19
|
-
path: string;
|
|
20
|
-
/** HTTP method. Defaults to `GET`. */
|
|
21
|
-
method?: string;
|
|
22
|
-
/**
|
|
23
|
-
* Extra request headers. The proxy adds `Authorization` itself
|
|
24
|
-
* (`DPoP` access token plus `DPoP` proof when DPoP is enabled and
|
|
25
|
-
* `appKeys` is non-null; otherwise `Bearer` only).
|
|
26
|
-
*/
|
|
27
|
-
headers?: Record<string, string>;
|
|
28
|
-
/** Optional request body. Pass `null`/`undefined` for empty bodies. */
|
|
29
|
-
body?: string | null | undefined;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Authenticated proxy returned by `createHubSpotProxy`. Use it
|
|
33
|
-
* inside Hono handlers (via `c.env.hubSpotProxy`) to call
|
|
34
|
-
* HubSpot's API on behalf of the browser session that issued the
|
|
35
|
-
* incoming request.
|
|
36
|
-
*/
|
|
37
|
-
interface HubSpotProxy {
|
|
38
|
-
/**
|
|
39
|
-
* `true` when the session cookies present on the inbound request
|
|
40
|
-
* yielded a usable access token. When `false`, every `fetch()` call
|
|
41
|
-
* returns a 401 without contacting the upstream.
|
|
42
|
-
*/
|
|
43
|
-
authenticated: boolean;
|
|
44
|
-
/** Performs an authenticated upstream request. */
|
|
45
|
-
fetch: (request: HubSpotProxyRequest) => Promise<Response>;
|
|
46
|
-
}
|
|
47
13
|
/**
|
|
48
14
|
* RFC 7517 JWK Set. Returned by HubSpot's `/oauth/v1/jwks` endpoint
|
|
49
15
|
* and used to verify access tokens on the resource server.
|
|
@@ -52,5 +18,5 @@ interface JwkSet {
|
|
|
52
18
|
keys: JsonWebKey[];
|
|
53
19
|
}
|
|
54
20
|
//#endregion
|
|
55
|
-
export { AppKeys,
|
|
21
|
+
export { AppKeys, JwkSet };
|
|
56
22
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cookie-utils.js","names":[],"sources":["../../../src/server/utils/cookie-utils.ts"],"sourcesContent":["/**\n * Parses an HTTP `Cookie` request header into a `name -> value` map.\n * Tolerates leading/trailing whitespace, missing `=` (treats the\n * cookie value as empty), and duplicate names (last write wins).\n *\n * Returns an empty object when `cookieHeader` is `null`/`undefined`\n * /empty so callers don't have to null-check the input.\n */\nexport function parseCookies(\n cookieHeader: string | null | undefined\n): Record<string, string> {\n if (!cookieHeader) return {};\n return Object.fromEntries(\n cookieHeader\n .split(';')\n .map((pair) => {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) return [pair.trim(), ''] as [string, string];\n return [pair.slice(0, eqIdx).trim(), pair.slice(eqIdx + 1).trim()] as [\n string,\n string,\n ];\n })\n .filter(([name]) => name.length > 0)\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,aACd,cACwB;CACxB,IAAI,CAAC,cAAc,OAAO,
|
|
1
|
+
{"version":3,"file":"cookie-utils.js","names":[],"sources":["../../../src/server/utils/cookie-utils.ts"],"sourcesContent":["/**\n * Parses an HTTP `Cookie` request header into a `name -> value` map.\n * Tolerates leading/trailing whitespace, missing `=` (treats the\n * cookie value as empty), and duplicate names (last write wins).\n *\n * Returns an empty object when `cookieHeader` is `null`/`undefined`\n * /empty so callers don't have to null-check the input.\n */\nexport function parseCookies(\n cookieHeader: string | null | undefined\n): Record<string, string> {\n if (!cookieHeader) return {};\n return Object.fromEntries(\n cookieHeader\n .split(';')\n .map((pair) => {\n const eqIdx = pair.indexOf('=');\n if (eqIdx === -1) return [pair.trim(), ''] as [string, string];\n return [pair.slice(0, eqIdx).trim(), pair.slice(eqIdx + 1).trim()] as [\n string,\n string,\n ];\n })\n .filter(([name]) => name.length > 0)\n );\n}\n"],"mappings":";;;;;;;;;AAQA,SAAgB,aACd,cACwB;CACxB,IAAI,CAAC,cAAc,OAAO,CAAC;CAC3B,OAAO,OAAO,YACZ,aACG,MAAM,GAAG,EACT,KAAK,SAAS;EACb,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,IAAI,UAAU,IAAI,OAAO,CAAC,KAAK,KAAK,GAAG,EAAE;EACzC,OAAO,CAAC,KAAK,MAAM,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK,MAAM,QAAQ,CAAC,EAAE,KAAK,CAAC;CAInE,CAAC,EACA,QAAQ,CAAC,UAAU,KAAK,SAAS,CAAC,CACvC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dpop-utils.js","names":[],"sources":["../../../src/server/utils/dpop-utils.ts"],"sourcesContent":["import { type AppKeys } from '../types.ts';\nimport { base64urlDecode } from './base64-utils.ts';\nimport { getJwkThumbprint } from './jwk-utils.ts';\nimport { decodeAndVerifyJwt, encodeAndSignJwt } from './jwt-utils.ts';\n\n/**\n * Claims that go into a DPoP proof JWT (RFC 9449 §4.2). Extra\n * properties pass through to the encoder unchanged so callers can\n * include implementation-specific claims.\n */\nexport interface DpopClaims {\n /** HTTP method of the protected request, uppercase. */\n htm: string;\n /** Target URI of the protected request, fully-qualified. */\n htu: string;\n /** Unique proof identifier (RFC 9449 §4.2, recommended UUID). */\n jti: string;\n /** Issuance time, Unix epoch seconds. */\n iat: number;\n /**\n * Hash of the access token presented alongside this proof, if any\n * (RFC 9449 §4.2). Required for resource-server DPoP.\n */\n ath?: string;\n /**\n * App session ID hash. Custom HubSpot extension that lets the auth\n * server bind tokens to the browser session that minted them.\n */\n sid?: string;\n [key: string]: unknown;\n}\n\nfunction ecPublicJwkForDpopHeader(jwk: JsonWebKey): JsonWebKey {\n if (\n jwk.kty !== 'EC' ||\n jwk.crv !== 'P-256' ||\n typeof jwk.x !== 'string' ||\n typeof jwk.y !== 'string'\n ) {\n throw new Error('Expected P-256 EC public JWK');\n }\n return {\n kty: 'EC',\n crv: 'P-256',\n x: jwk.x,\n y: jwk.y,\n };\n}\n\nexport interface SignDpopProofOptions {\n /** App key material produced by `secureStart`. */\n appKeys: AppKeys;\n /** Claims to include in the DPoP proof. */\n claims: DpopClaims;\n}\n\n/**\n * Mints a DPoP proof JWT (RFC 9449) signed with the app's private\n * key. The header is set to `typ=dpop+jwt`, `alg=ES256`, and embeds\n * the public JWK so the receiver can verify the signature without\n * out-of-band key distribution.\n */\nexport async function signDpopProof(\n options: SignDpopProofOptions\n): Promise<string> {\n const { appKeys, claims } = options;\n const publicJwk = ecPublicJwkForDpopHeader(appKeys.appPublicKeyJwk);\n return encodeAndSignJwt({\n header: { alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk },\n payload: claims,\n privateKey: appKeys.appPrivateKey,\n });\n}\n\n/**\n * Result of a successful {@link verifyDpopProof} call.\n */\nexport interface VerifiedDpopProof {\n /** Public JWK extracted from the proof's header. */\n publicKeyJwk: JsonWebKey;\n /** RFC 7638 JWK thumbprint of `publicKeyJwk`. */\n jkt: string;\n /** Decoded claims from the proof's payload. */\n claims: DpopClaims;\n}\n\nexport interface VerifyDpopProofOptions {\n /** The compact-serialized DPoP proof. */\n proof: string;\n /** Expected HTTP method (RFC 9449 `htm` claim). */\n htm: string;\n /** Expected request URI (RFC 9449 `htu` claim). */\n htu: string;\n /** Expected access-token hash (RFC 9449 `ath` claim). */\n ath?: string;\n /** Expected app-session-ID hash (`sid` claim). */\n sid?: string;\n}\n\ninterface DpopProofHeader {\n typ?: string;\n alg?: string;\n jwk?: JsonWebKey;\n}\n\n/**\n * Verifies a DPoP proof JWT and returns the embedded public JWK,\n * its thumbprint, and the decoded claims.\n *\n * Enforces RFC 9449's required checks:\n *\n * - `typ=dpop+jwt`, `alg=ES256`, and a JWK in the header.\n * - Signature is valid against the embedded JWK.\n * - `htm`, `htu`, and (when supplied) `ath`/`sid` match.\n * - `iat` is within ±5 minutes of \"now\".\n *\n * @throws {Error} If any of the above checks fail.\n */\nexport async function verifyDpopProof(\n options: VerifyDpopProofOptions\n): Promise<VerifiedDpopProof> {\n const { proof, htm, htu, ath, sid } = options;\n const parts = proof.split('.');\n if (parts.length !== 3) throw new Error('Invalid DPoP proof format');\n\n const encodedHeader = parts[0];\n if (!encodedHeader) throw new Error('Missing DPoP header');\n\n const header = JSON.parse(\n new TextDecoder().decode(base64urlDecode(encodedHeader))\n ) as DpopProofHeader;\n\n if (header.typ !== 'dpop+jwt') throw new Error('Invalid DPoP typ header');\n if (header.alg !== 'ES256') throw new Error('Unsupported DPoP algorithm');\n const publicKeyJwk = header.jwk;\n if (!publicKeyJwk) throw new Error('Missing jwk in DPoP header');\n\n const payload = await decodeAndVerifyJwt({ token: proof, publicKeyJwk });\n const claims = payload as unknown as DpopClaims;\n\n if (claims.htm !== htm) {\n throw new Error(`DPoP htm mismatch: expected ${htm}, got ${claims.htm}`);\n }\n if (claims.htu !== htu) {\n throw new Error(`DPoP htu mismatch: expected ${htu}, got ${claims.htu}`);\n }\n if (ath !== undefined && claims.ath !== ath) {\n throw new Error('DPoP ath mismatch');\n }\n if (sid !== undefined && claims.sid !== sid) {\n throw new Error('DPoP sid mismatch');\n }\n\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - claims.iat) > 300) {\n throw new Error('DPoP proof expired or too far in future');\n }\n\n const jkt = await getJwkThumbprint({ publicKeyJwk });\n return { publicKeyJwk, jkt, claims };\n}\n"],"mappings":";;;;AAgCA,SAAS,yBAAyB,KAA6B;CAC7D,IACE,IAAI,QAAQ,QACZ,IAAI,QAAQ,WACZ,OAAO,IAAI,MAAM,YACjB,OAAO,IAAI,MAAM,UAEjB,MAAM,IAAI,MAAM
|
|
1
|
+
{"version":3,"file":"dpop-utils.js","names":[],"sources":["../../../src/server/utils/dpop-utils.ts"],"sourcesContent":["import { type AppKeys } from '../types.ts';\nimport { base64urlDecode } from './base64-utils.ts';\nimport { getJwkThumbprint } from './jwk-utils.ts';\nimport { decodeAndVerifyJwt, encodeAndSignJwt } from './jwt-utils.ts';\n\n/**\n * Claims that go into a DPoP proof JWT (RFC 9449 §4.2). Extra\n * properties pass through to the encoder unchanged so callers can\n * include implementation-specific claims.\n */\nexport interface DpopClaims {\n /** HTTP method of the protected request, uppercase. */\n htm: string;\n /** Target URI of the protected request, fully-qualified. */\n htu: string;\n /** Unique proof identifier (RFC 9449 §4.2, recommended UUID). */\n jti: string;\n /** Issuance time, Unix epoch seconds. */\n iat: number;\n /**\n * Hash of the access token presented alongside this proof, if any\n * (RFC 9449 §4.2). Required for resource-server DPoP.\n */\n ath?: string;\n /**\n * App session ID hash. Custom HubSpot extension that lets the auth\n * server bind tokens to the browser session that minted them.\n */\n sid?: string;\n [key: string]: unknown;\n}\n\nfunction ecPublicJwkForDpopHeader(jwk: JsonWebKey): JsonWebKey {\n if (\n jwk.kty !== 'EC' ||\n jwk.crv !== 'P-256' ||\n typeof jwk.x !== 'string' ||\n typeof jwk.y !== 'string'\n ) {\n throw new Error('Expected P-256 EC public JWK');\n }\n return {\n kty: 'EC',\n crv: 'P-256',\n x: jwk.x,\n y: jwk.y,\n };\n}\n\nexport interface SignDpopProofOptions {\n /** App key material produced by `secureStart`. */\n appKeys: AppKeys;\n /** Claims to include in the DPoP proof. */\n claims: DpopClaims;\n}\n\n/**\n * Mints a DPoP proof JWT (RFC 9449) signed with the app's private\n * key. The header is set to `typ=dpop+jwt`, `alg=ES256`, and embeds\n * the public JWK so the receiver can verify the signature without\n * out-of-band key distribution.\n */\nexport async function signDpopProof(\n options: SignDpopProofOptions\n): Promise<string> {\n const { appKeys, claims } = options;\n const publicJwk = ecPublicJwkForDpopHeader(appKeys.appPublicKeyJwk);\n return encodeAndSignJwt({\n header: { alg: 'ES256', typ: 'dpop+jwt', jwk: publicJwk },\n payload: claims,\n privateKey: appKeys.appPrivateKey,\n });\n}\n\n/**\n * Result of a successful {@link verifyDpopProof} call.\n */\nexport interface VerifiedDpopProof {\n /** Public JWK extracted from the proof's header. */\n publicKeyJwk: JsonWebKey;\n /** RFC 7638 JWK thumbprint of `publicKeyJwk`. */\n jkt: string;\n /** Decoded claims from the proof's payload. */\n claims: DpopClaims;\n}\n\nexport interface VerifyDpopProofOptions {\n /** The compact-serialized DPoP proof. */\n proof: string;\n /** Expected HTTP method (RFC 9449 `htm` claim). */\n htm: string;\n /** Expected request URI (RFC 9449 `htu` claim). */\n htu: string;\n /** Expected access-token hash (RFC 9449 `ath` claim). */\n ath?: string;\n /** Expected app-session-ID hash (`sid` claim). */\n sid?: string;\n}\n\ninterface DpopProofHeader {\n typ?: string;\n alg?: string;\n jwk?: JsonWebKey;\n}\n\n/**\n * Verifies a DPoP proof JWT and returns the embedded public JWK,\n * its thumbprint, and the decoded claims.\n *\n * Enforces RFC 9449's required checks:\n *\n * - `typ=dpop+jwt`, `alg=ES256`, and a JWK in the header.\n * - Signature is valid against the embedded JWK.\n * - `htm`, `htu`, and (when supplied) `ath`/`sid` match.\n * - `iat` is within ±5 minutes of \"now\".\n *\n * @throws {Error} If any of the above checks fail.\n */\nexport async function verifyDpopProof(\n options: VerifyDpopProofOptions\n): Promise<VerifiedDpopProof> {\n const { proof, htm, htu, ath, sid } = options;\n const parts = proof.split('.');\n if (parts.length !== 3) throw new Error('Invalid DPoP proof format');\n\n const encodedHeader = parts[0];\n if (!encodedHeader) throw new Error('Missing DPoP header');\n\n const header = JSON.parse(\n new TextDecoder().decode(base64urlDecode(encodedHeader))\n ) as DpopProofHeader;\n\n if (header.typ !== 'dpop+jwt') throw new Error('Invalid DPoP typ header');\n if (header.alg !== 'ES256') throw new Error('Unsupported DPoP algorithm');\n const publicKeyJwk = header.jwk;\n if (!publicKeyJwk) throw new Error('Missing jwk in DPoP header');\n\n const payload = await decodeAndVerifyJwt({ token: proof, publicKeyJwk });\n const claims = payload as unknown as DpopClaims;\n\n if (claims.htm !== htm) {\n throw new Error(`DPoP htm mismatch: expected ${htm}, got ${claims.htm}`);\n }\n if (claims.htu !== htu) {\n throw new Error(`DPoP htu mismatch: expected ${htu}, got ${claims.htu}`);\n }\n if (ath !== undefined && claims.ath !== ath) {\n throw new Error('DPoP ath mismatch');\n }\n if (sid !== undefined && claims.sid !== sid) {\n throw new Error('DPoP sid mismatch');\n }\n\n const now = Math.floor(Date.now() / 1000);\n if (Math.abs(now - claims.iat) > 300) {\n throw new Error('DPoP proof expired or too far in future');\n }\n\n const jkt = await getJwkThumbprint({ publicKeyJwk });\n return { publicKeyJwk, jkt, claims };\n}\n"],"mappings":";;;;AAgCA,SAAS,yBAAyB,KAA6B;CAC7D,IACE,IAAI,QAAQ,QACZ,IAAI,QAAQ,WACZ,OAAO,IAAI,MAAM,YACjB,OAAO,IAAI,MAAM,UAEjB,MAAM,IAAI,MAAM,8BAA8B;CAEhD,OAAO;EACL,KAAK;EACL,KAAK;EACL,GAAG,IAAI;EACP,GAAG,IAAI;CACT;AACF;;;;;;;AAeA,eAAsB,cACpB,SACiB;CACjB,MAAM,EAAE,SAAS,WAAW;CAE5B,OAAO,iBAAiB;EACtB,QAAQ;GAAE,KAAK;GAAS,KAAK;GAAY,KAFzB,yBAAyB,QAAQ,eAEK;EAAE;EACxD,SAAS;EACT,YAAY,QAAQ;CACtB,CAAC;AACH;;;;;;;;;;;;;;AA8CA,eAAsB,gBACpB,SAC4B;CAC5B,MAAM,EAAE,OAAO,KAAK,KAAK,KAAK,QAAQ;CACtC,MAAM,QAAQ,MAAM,MAAM,GAAG;CAC7B,IAAI,MAAM,WAAW,GAAG,MAAM,IAAI,MAAM,2BAA2B;CAEnE,MAAM,gBAAgB,MAAM;CAC5B,IAAI,CAAC,eAAe,MAAM,IAAI,MAAM,qBAAqB;CAEzD,MAAM,SAAS,KAAK,MAClB,IAAI,YAAY,EAAE,OAAO,gBAAgB,aAAa,CAAC,CACzD;CAEA,IAAI,OAAO,QAAQ,YAAY,MAAM,IAAI,MAAM,yBAAyB;CACxE,IAAI,OAAO,QAAQ,SAAS,MAAM,IAAI,MAAM,4BAA4B;CACxE,MAAM,eAAe,OAAO;CAC5B,IAAI,CAAC,cAAc,MAAM,IAAI,MAAM,4BAA4B;CAG/D,MAAM,SAAS,MADO,mBAAmB;EAAE,OAAO;EAAO;CAAa,CAAC;CAGvE,IAAI,OAAO,QAAQ,KACjB,MAAM,IAAI,MAAM,+BAA+B,IAAI,QAAQ,OAAO,KAAK;CAEzE,IAAI,OAAO,QAAQ,KACjB,MAAM,IAAI,MAAM,+BAA+B,IAAI,QAAQ,OAAO,KAAK;CAEzE,IAAI,QAAQ,KAAA,KAAa,OAAO,QAAQ,KACtC,MAAM,IAAI,MAAM,mBAAmB;CAErC,IAAI,QAAQ,KAAA,KAAa,OAAO,QAAQ,KACtC,MAAM,IAAI,MAAM,mBAAmB;CAGrC,MAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;CACxC,IAAI,KAAK,IAAI,MAAM,OAAO,GAAG,IAAI,KAC/B,MAAM,IAAI,MAAM,yCAAyC;CAI3D,OAAO;EAAE;EAAc,KAAA,MADL,iBAAiB,EAAE,aAAa,CAAC;EACvB;CAAO;AACrC"}
|
|
@@ -39,7 +39,7 @@ function requireEnv(key) {
|
|
|
39
39
|
return value;
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
* HubSpot API origin used by
|
|
42
|
+
* HubSpot API origin used by the HubSpot API client transport. Defaults to
|
|
43
43
|
* `https://api.hubapi.com` when `HUBSPOT_API_ORIGIN` is unset.
|
|
44
44
|
*/
|
|
45
45
|
function getHubSpotApiOrigin() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"env-utils.js","names":[],"sources":["../../../src/server/utils/env-utils.ts"],"sourcesContent":["interface GlobalWithOptionalEnv {\n process?: { env?: Record<string, string | undefined> };\n Deno?: { env: { get(name: string): string | undefined } };\n}\n\nconst HUBSPOT_API_ORIGIN_DEFAULT = 'https://api.hubapi.com';\n\nconst HUBSPOT_OAUTH_API_ORIGIN_DEFAULT = 'https://api.hubapi.com';\n\nconst HUBSPOT_AUTHORIZATION_ENDPOINT_DEFAULT =\n 'https://app.hubspot.com/oauth/authorize';\n\n/** Environment variable name for the app private key loaded by `secureStart`. */\nexport const HUBSPOT_APP_PRIVATE_KEY_ENV = 'HUBSPOT_APP_PRIVATE_KEY';\n\n/**\n * Reads an environment variable in a way that works under both Node\n * (`process.env`) and Deno (`Deno.env.get`). Returns `undefined` when\n * the variable is unset or when neither runtime is available.\n */\nexport function getEnv(key: string): string | undefined {\n const g = globalThis as GlobalWithOptionalEnv;\n const proc = g.process;\n if (proc?.env) {\n return proc.env[key];\n }\n const deno = g.Deno;\n if (deno !== undefined) {\n return deno.env.get(key);\n }\n return undefined;\n}\n\n/**\n * Reads an environment variable, returning `defaultValue` when it is\n * unset or an empty string.\n */\nexport function getEnvWithDefault(key: string, defaultValue: string): string {\n const value = getEnv(key);\n if (!value) {\n return defaultValue;\n }\n return value;\n}\n\n/**\n * Reads an environment variable and throws when it is missing or empty.\n * Use for values the SDK cannot fall back on (e.g. upstream service\n * URLs).\n *\n * @throws {Error} When the environment variable is unset or an empty\n * string.\n */\nexport function requireEnv(key: string): string {\n const value = getEnv(key);\n if (!value) {\n throw new Error(`Missing required environment variable: ${key}`);\n }\n return value;\n}\n\n/**\n * HubSpot API origin used by
|
|
1
|
+
{"version":3,"file":"env-utils.js","names":[],"sources":["../../../src/server/utils/env-utils.ts"],"sourcesContent":["interface GlobalWithOptionalEnv {\n process?: { env?: Record<string, string | undefined> };\n Deno?: { env: { get(name: string): string | undefined } };\n}\n\nconst HUBSPOT_API_ORIGIN_DEFAULT = 'https://api.hubapi.com';\n\nconst HUBSPOT_OAUTH_API_ORIGIN_DEFAULT = 'https://api.hubapi.com';\n\nconst HUBSPOT_AUTHORIZATION_ENDPOINT_DEFAULT =\n 'https://app.hubspot.com/oauth/authorize';\n\n/** Environment variable name for the app private key loaded by `secureStart`. */\nexport const HUBSPOT_APP_PRIVATE_KEY_ENV = 'HUBSPOT_APP_PRIVATE_KEY';\n\n/**\n * Reads an environment variable in a way that works under both Node\n * (`process.env`) and Deno (`Deno.env.get`). Returns `undefined` when\n * the variable is unset or when neither runtime is available.\n */\nexport function getEnv(key: string): string | undefined {\n const g = globalThis as GlobalWithOptionalEnv;\n const proc = g.process;\n if (proc?.env) {\n return proc.env[key];\n }\n const deno = g.Deno;\n if (deno !== undefined) {\n return deno.env.get(key);\n }\n return undefined;\n}\n\n/**\n * Reads an environment variable, returning `defaultValue` when it is\n * unset or an empty string.\n */\nexport function getEnvWithDefault(key: string, defaultValue: string): string {\n const value = getEnv(key);\n if (!value) {\n return defaultValue;\n }\n return value;\n}\n\n/**\n * Reads an environment variable and throws when it is missing or empty.\n * Use for values the SDK cannot fall back on (e.g. upstream service\n * URLs).\n *\n * @throws {Error} When the environment variable is unset or an empty\n * string.\n */\nexport function requireEnv(key: string): string {\n const value = getEnv(key);\n if (!value) {\n throw new Error(`Missing required environment variable: ${key}`);\n }\n return value;\n}\n\n/**\n * HubSpot API origin used by the HubSpot API client transport. Defaults to\n * `https://api.hubapi.com` when `HUBSPOT_API_ORIGIN` is unset.\n */\nexport function getHubSpotApiOrigin(): string {\n return getEnvWithDefault('HUBSPOT_API_ORIGIN', HUBSPOT_API_ORIGIN_DEFAULT);\n}\n\n/**\n * Full OAuth authorize URL for hubspot-connect routes. Defaults to\n * `https://app.hubspot.com/oauth/authorize` when\n * `HUBSPOT_AUTHORIZATION_ENDPOINT` is unset.\n */\nexport function getHubSpotAuthorizationEndpoint(): string {\n return getEnvWithDefault(\n 'HUBSPOT_AUTHORIZATION_ENDPOINT',\n HUBSPOT_AUTHORIZATION_ENDPOINT_DEFAULT\n );\n}\n\n/**\n * HubSpot OAuth API origin (token, revoke, JWKS). Normalized to a URL\n * origin. Defaults to `https://api.hubapi.com` when\n * `HUBSPOT_OAUTH_API_ORIGIN` is unset.\n */\nexport function getHubSpotOAuthApiOrigin(): string {\n return new URL(\n getEnvWithDefault(\n 'HUBSPOT_OAUTH_API_ORIGIN',\n HUBSPOT_OAUTH_API_ORIGIN_DEFAULT\n )\n ).origin;\n}\n\n/**\n * Static OAuth client ID. Required when CIMD is disabled.\n *\n * @throws {Error} When `HUBSPOT_CLIENT_ID` is unset or empty.\n */\nexport function requireHubSpotClientId(): string {\n return requireEnv('HUBSPOT_CLIENT_ID');\n}\n\n/**\n * Static OAuth client secret. Required when CIMD is disabled.\n *\n * @throws {Error} When `HUBSPOT_CLIENT_SECRET` is unset or empty.\n */\nexport function requireHubSpotClientSecret(): string {\n return requireEnv('HUBSPOT_CLIENT_SECRET');\n}\n\n/**\n * Whether outbound HubSpot OAuth and API calls should attach DPoP on\n * the wire. Enabled only when `HUBSPOT_DPOP_ENABLED` is exactly the\n * string `\"true\"` (unset or any other value keeps DPoP disabled).\n */\nexport function isHubspotDpopEnabled(): boolean {\n return getEnv('HUBSPOT_DPOP_ENABLED') === 'true';\n}\n\n/**\n * Whether the SDK should use CIMD-style OAuth (client ID URL + JWT client\n * assertion). Enabled only when `HUBSPOT_CIMD_ENABLED` is exactly the\n * string `\"true\"` (unset or any other value keeps CIMD disabled).\n */\nexport function isHubspotCimdEnabled(): boolean {\n return getEnv('HUBSPOT_CIMD_ENABLED') === 'true';\n}\n\n/**\n * Whether `HUBSPOT_APP_PRIVATE_KEY` must be set for `secureStart`. False\n * when both CIMD and DPoP are disabled — the SDK then uses\n * `client_secret` for OAuth and Bearer tokens only for API calls.\n */\nexport function isHubspotAppPrivateKeyRequired(): boolean {\n return isHubspotCimdEnabled() || isHubspotDpopEnabled();\n}\n"],"mappings":";AAKA,MAAM,6BAA6B;AAEnC,MAAM,mCAAmC;AAEzC,MAAM,yCACJ;;AAGF,MAAa,8BAA8B;;;;;;AAO3C,SAAgB,OAAO,KAAiC;CACtD,MAAM,IAAI;CACV,MAAM,OAAO,EAAE;CACf,IAAI,MAAM,KACR,OAAO,KAAK,IAAI;CAElB,MAAM,OAAO,EAAE;CACf,IAAI,SAAS,KAAA,GACX,OAAO,KAAK,IAAI,IAAI,GAAG;AAG3B;;;;;AAMA,SAAgB,kBAAkB,KAAa,cAA8B;CAC3E,MAAM,QAAQ,OAAO,GAAG;CACxB,IAAI,CAAC,OACH,OAAO;CAET,OAAO;AACT;;;;;;;;;AAUA,SAAgB,WAAW,KAAqB;CAC9C,MAAM,QAAQ,OAAO,GAAG;CACxB,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,0CAA0C,KAAK;CAEjE,OAAO;AACT;;;;;AAMA,SAAgB,sBAA8B;CAC5C,OAAO,kBAAkB,sBAAsB,0BAA0B;AAC3E;;;;;;AAOA,SAAgB,kCAA0C;CACxD,OAAO,kBACL,kCACA,sCACF;AACF;;;;;;AAOA,SAAgB,2BAAmC;CACjD,OAAO,IAAI,IACT,kBACE,4BACA,gCACF,CACF,EAAE;AACJ;;;;;;AAOA,SAAgB,yBAAiC;CAC/C,OAAO,WAAW,mBAAmB;AACvC;;;;;;AAOA,SAAgB,6BAAqC;CACnD,OAAO,WAAW,uBAAuB;AAC3C;;;;;;AAOA,SAAgB,uBAAgC;CAC9C,OAAO,OAAO,sBAAsB,MAAM;AAC5C;;;;;;AAOA,SAAgB,uBAAgC;CAC9C,OAAO,OAAO,sBAAsB,MAAM;AAC5C;;;;;;AAOA,SAAgB,iCAA0C;CACxD,OAAO,qBAAqB,KAAK,qBAAqB;AACxD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { signDpopProof } from "./dpop-utils.js";
|
|
2
|
+
import { sha256base64url } from "../shared/encoding/sha256.js";
|
|
3
|
+
//#region src/server/utils/hubspot-dpop-auth-headers.ts
|
|
4
|
+
function getDpopHtuFromTargetUrl(targetUrl) {
|
|
5
|
+
const url = new URL(targetUrl);
|
|
6
|
+
url.search = "";
|
|
7
|
+
url.hash = "";
|
|
8
|
+
return url.toString();
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds `Authorization` and `DPoP` headers for an authenticated
|
|
12
|
+
* HubSpot API request when DPoP is enabled.
|
|
13
|
+
*/
|
|
14
|
+
async function buildHubSpotDpopAuthHeaders(options) {
|
|
15
|
+
const { accessToken, sessionId, appKeys, method, targetUrl } = options;
|
|
16
|
+
const htu = getDpopHtuFromTargetUrl(targetUrl);
|
|
17
|
+
const ath = await sha256base64url(accessToken);
|
|
18
|
+
const sid = await sha256base64url(sessionId);
|
|
19
|
+
const dpopProof = await signDpopProof({
|
|
20
|
+
appKeys,
|
|
21
|
+
claims: {
|
|
22
|
+
htm: method,
|
|
23
|
+
htu,
|
|
24
|
+
jti: crypto.randomUUID(),
|
|
25
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
26
|
+
ath,
|
|
27
|
+
sid
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
return {
|
|
31
|
+
Authorization: `DPoP ${accessToken}`,
|
|
32
|
+
DPoP: dpopProof
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
export { buildHubSpotDpopAuthHeaders };
|
|
37
|
+
|
|
38
|
+
//# sourceMappingURL=hubspot-dpop-auth-headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hubspot-dpop-auth-headers.js","names":[],"sources":["../../../src/server/utils/hubspot-dpop-auth-headers.ts"],"sourcesContent":["import { type AppKeys } from '../types.ts';\nimport { sha256base64url } from './crypto-utils.ts';\nimport { signDpopProof } from './dpop-utils.ts';\n\nexport interface BuildHubSpotDpopAuthHeadersOptions {\n accessToken: string;\n sessionId: string;\n appKeys: AppKeys;\n method: string;\n targetUrl: string;\n}\n\nfunction getDpopHtuFromTargetUrl(targetUrl: string): string {\n const url = new URL(targetUrl);\n url.search = '';\n url.hash = '';\n return url.toString();\n}\n\n/**\n * Builds `Authorization` and `DPoP` headers for an authenticated\n * HubSpot API request when DPoP is enabled.\n */\nexport async function buildHubSpotDpopAuthHeaders(\n options: BuildHubSpotDpopAuthHeadersOptions\n): Promise<Record<string, string>> {\n const { accessToken, sessionId, appKeys, method, targetUrl } = options;\n const htu = getDpopHtuFromTargetUrl(targetUrl);\n\n const ath = await sha256base64url(accessToken);\n const sid = await sha256base64url(sessionId);\n const dpopProof = await signDpopProof({\n appKeys,\n claims: {\n htm: method,\n htu,\n jti: crypto.randomUUID(),\n iat: Math.floor(Date.now() / 1000),\n ath,\n sid,\n },\n });\n\n return {\n Authorization: `DPoP ${accessToken}`,\n DPoP: dpopProof,\n };\n}\n"],"mappings":";;;AAYA,SAAS,wBAAwB,WAA2B;CAC1D,MAAM,MAAM,IAAI,IAAI,SAAS;CAC7B,IAAI,SAAS;CACb,IAAI,OAAO;CACX,OAAO,IAAI,SAAS;AACtB;;;;;AAMA,eAAsB,4BACpB,SACiC;CACjC,MAAM,EAAE,aAAa,WAAW,SAAS,QAAQ,cAAc;CAC/D,MAAM,MAAM,wBAAwB,SAAS;CAE7C,MAAM,MAAM,MAAM,gBAAgB,WAAW;CAC7C,MAAM,MAAM,MAAM,gBAAgB,SAAS;CAC3C,MAAM,YAAY,MAAM,cAAc;EACpC;EACA,QAAQ;GACN,KAAK;GACL;GACA,KAAK,OAAO,WAAW;GACvB,KAAK,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;GACjC;GACA;EACF;CACF,CAAC;CAED,OAAO;EACL,eAAe,QAAQ;EACvB,MAAM;CACR;AACF"}
|