From 55e41705ae8479e815461e089d431ab8eddb09a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Wed, 11 Dec 2024 22:20:13 -0300 Subject: [PATCH 1/9] feat: add hubspot to the docs --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 03acf6c7..988c6d42 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,7 @@ It can also be set using environment variables: - GitHub - GitLab - Google +- Hubspot - Instagram - Keycloak - Linear From 4c09016307afd286fd0678ea202a45a94c44f6f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Wed, 11 Dec 2024 22:20:51 -0300 Subject: [PATCH 2/9] feat: add hubspot to playground --- playground/.env.example | 3 +++ playground/app.vue | 6 ++++++ playground/server/routes/auth/hubspot.get.ts | 15 +++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 playground/server/routes/auth/hubspot.get.ts diff --git a/playground/.env.example b/playground/.env.example index 7423af59..b08bcf47 100644 --- a/playground/.env.example +++ b/playground/.env.example @@ -96,3 +96,6 @@ NUXT_OAUTH_AUTHENTIK_DOMAIN= # Strava NUXT_OAUTH_STRAVA_CLIENT_ID= NUXT_OAUTH_STRAVA_CLIENT_SECRET= +# Hubspot +NUXT_OAUTH_HUBSPOT_CLIENT_ID= +NUXT_OAUTH_HUBSPOT_CLIENT_SECRET= \ No newline at end of file diff --git a/playground/app.vue b/playground/app.vue index ae36cd01..aeed5703 100644 --- a/playground/app.vue +++ b/playground/app.vue @@ -177,6 +177,12 @@ const providers = computed(() => disabled: Boolean(user.value?.strava), icon: 'i-simple-icons-strava', }, + { + label: user.value?.hubspot || 'HubSpot', + to: '/auth/hubspot', + disabled: Boolean(user.value?.hubspot), + icon: 'i-simple-icons-hubspot', + }, ].map(p => ({ ...p, prefetch: false, diff --git a/playground/server/routes/auth/hubspot.get.ts b/playground/server/routes/auth/hubspot.get.ts new file mode 100644 index 00000000..ea52e7e6 --- /dev/null +++ b/playground/server/routes/auth/hubspot.get.ts @@ -0,0 +1,15 @@ +export default defineOAuthHubspotEventHandler({ + config: { + scope: ['oauth'], + }, + async onSuccess(event, { user }) { + await setUserSession(event, { + user: { + hubspot: `${user.user}`, + }, + loggedInAt: Date.now(), + }) + + return sendRedirect(event, '/') + }, +}) From 7f61d1eb657117c453ba0670e30cf6ca4aa0736d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Wed, 11 Dec 2024 22:21:05 -0300 Subject: [PATCH 3/9] feat: add hubspot module config --- src/module.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/module.ts b/src/module.ts index f180b10c..acec624b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -347,5 +347,11 @@ export default defineNuxtModule({ clientSecret: '', redirectURL: '', }) + // Hubspot OAuth + runtimeConfig.oauth.hubspot = defu(runtimeConfig.oauth.hubspot, { + clientId: '', + clientSecret: '', + redirectURL: '', + }) }, }) From fffb86ac3a04d6aee43886729b0a27ecbea531b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Wed, 11 Dec 2024 22:22:19 -0300 Subject: [PATCH 4/9] feat: add hubspot to runtime config --- src/runtime/types/oauth-config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/types/oauth-config.ts b/src/runtime/types/oauth-config.ts index eaa803e5..188c03b3 100644 --- a/src/runtime/types/oauth-config.ts +++ b/src/runtime/types/oauth-config.ts @@ -1,6 +1,6 @@ import type { H3Event, H3Error } from 'h3' -export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {}) +export type OAuthProvider = 'auth0' | 'authentik' | 'battledotnet' | 'cognito' | 'discord' | 'dropbox' | 'facebook' | 'github' | 'gitlab' | 'google' | 'hubspot' | 'instagram' | 'keycloak' | 'linear' | 'linkedin' | 'microsoft' | 'paypal' | 'polar' | 'spotify' | 'seznam' | 'steam' | 'strava' | 'tiktok' | 'twitch' | 'vk' | 'workos' | 'x' | 'xsuaa' | 'yandex' | 'zitadel' | (string & {}) export type OnError = (event: H3Event, error: H3Error) => Promise | void From 43e5225306f058826c22851e12017f5861e6d6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Wed, 11 Dec 2024 23:40:13 -0300 Subject: [PATCH 5/9] feat: add auth handle --- playground/.env.example | 3 +- src/runtime/server/lib/oauth/hubspot.ts | 69 +++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/runtime/server/lib/oauth/hubspot.ts diff --git a/playground/.env.example b/playground/.env.example index b08bcf47..431e125e 100644 --- a/playground/.env.example +++ b/playground/.env.example @@ -98,4 +98,5 @@ NUXT_OAUTH_STRAVA_CLIENT_ID= NUXT_OAUTH_STRAVA_CLIENT_SECRET= # Hubspot NUXT_OAUTH_HUBSPOT_CLIENT_ID= -NUXT_OAUTH_HUBSPOT_CLIENT_SECRET= \ No newline at end of file +NUXT_OAUTH_HUBSPOT_CLIENT_SECRET= +NUXT_OAUTH_HUBSPOT_REDIRECT_URL= \ No newline at end of file diff --git a/src/runtime/server/lib/oauth/hubspot.ts b/src/runtime/server/lib/oauth/hubspot.ts new file mode 100644 index 00000000..2f091169 --- /dev/null +++ b/src/runtime/server/lib/oauth/hubspot.ts @@ -0,0 +1,69 @@ +import type { H3Event } from 'h3' +import { eventHandler, getQuery, sendRedirect } from 'h3' +import { withQuery } from 'ufo' +import defu from 'defu' +import { + getOAuthRedirectURL, + handleAccessTokenErrorResponse, + handleMissingConfiguration, + requestAccessToken, +} from '../utils' +import { useRuntimeConfig } from '#imports' +import type { OAuthConfig } from '#auth-utils' + +export interface OAuthHubspotConfig {} +export interface OAuthHubspotUser {} + +export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig) { + return eventHandler(async (event: H3Event) => { + config = defu(config, useRuntimeConfig(event).oauth?.hubspot) as OAuthHubspotConfig + + if (!config.clientId || !config.clientSecret || !config.redirectURL) { + return handleMissingConfiguration(event, 'hubspot', ['clientId', 'clientSecret', 'redirectURL'], onError) + } + + const query = getQuery<{ code?: string, state?: string, error?: string, error_description?: string }>(event) + const redirectURL = config.redirectURL || getOAuthRedirectURL(event) + + if (query.error) { + return handleAccessTokenErrorResponse(event, 'hubspot', query, onError) + } + + if (!query.code) { + return sendRedirect( + event, + withQuery('https://app.hubspot.com/oauth/authorize', { + client_id: config.clientId, + redirect_uri: redirectURL, + scope: config.scope || 'oauth', + }), + ) + } + + const tokens = await requestAccessToken( + 'https://api.hubapi.com/oauth/v1/token', { + body: { + client_id: config.clientId, + client_secret: config.clientSecret, + code: query.code as string, + redirect_uri: redirectURL, + grant_type: 'authorization_code', + }, + }) + + if (tokens.error) { + return handleAccessTokenErrorResponse(event, 'hubspot', tokens, onError) + } + + const profile: OAuthHubspotUser = await $fetch('https://api.hubapi.com/oauth/v1/access-tokens/' + tokens.access_token) + + return onSuccess(event, { + user: { + id: profile.user_id, + email: profile.user, + hubDomain: profile.hub_domain, + }, + tokens, + }) + }) +} From 2cab38f745c6656e83a43b4b304e8f0cae7513ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Thu, 12 Dec 2024 00:26:36 -0300 Subject: [PATCH 6/9] fix: provider name --- playground/server/routes/auth/hubspot.get.ts | 4 +++- src/runtime/server/lib/oauth/hubspot.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/playground/server/routes/auth/hubspot.get.ts b/playground/server/routes/auth/hubspot.get.ts index ea52e7e6..7ce1111c 100644 --- a/playground/server/routes/auth/hubspot.get.ts +++ b/playground/server/routes/auth/hubspot.get.ts @@ -5,7 +5,9 @@ export default defineOAuthHubspotEventHandler({ async onSuccess(event, { user }) { await setUserSession(event, { user: { - hubspot: `${user.user}`, + id: `${user.id}`, + email: `${user.email}`, + domain: `${user.domain}`, }, loggedInAt: Date.now(), }) diff --git a/src/runtime/server/lib/oauth/hubspot.ts b/src/runtime/server/lib/oauth/hubspot.ts index 2f091169..d9dc1298 100644 --- a/src/runtime/server/lib/oauth/hubspot.ts +++ b/src/runtime/server/lib/oauth/hubspot.ts @@ -14,7 +14,7 @@ import type { OAuthConfig } from '#auth-utils' export interface OAuthHubspotConfig {} export interface OAuthHubspotUser {} -export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAuthConfig) { +export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: OAuthConfig) { return eventHandler(async (event: H3Event) => { config = defu(config, useRuntimeConfig(event).oauth?.hubspot) as OAuthHubspotConfig @@ -51,6 +51,8 @@ export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAu }, }) + console.log('tokens', tokens) + if (tokens.error) { return handleAccessTokenErrorResponse(event, 'hubspot', tokens, onError) } @@ -61,7 +63,7 @@ export function defineOAuthAuth0EventHandler({ config, onSuccess, onError }: OAu user: { id: profile.user_id, email: profile.user, - hubDomain: profile.hub_domain, + domain: profile.hub_domain, }, tokens, }) From bc505c7f196718b5fcd20ee71bfb581d4ba99454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Thu, 12 Dec 2024 08:00:14 -0300 Subject: [PATCH 7/9] feat: add types --- playground/auth.d.ts | 1 + playground/server/routes/auth/hubspot.get.ts | 4 +- src/runtime/server/lib/oauth/hubspot.ts | 66 +++++++++++++++++--- 3 files changed, 60 insertions(+), 11 deletions(-) diff --git a/playground/auth.d.ts b/playground/auth.d.ts index 81d29b42..35c136e4 100644 --- a/playground/auth.d.ts +++ b/playground/auth.d.ts @@ -32,6 +32,7 @@ declare module '#auth-utils' { authentik?: string seznam?: string strava?: string + hubspot?: string } interface UserSession { diff --git a/playground/server/routes/auth/hubspot.get.ts b/playground/server/routes/auth/hubspot.get.ts index 7ce1111c..ddad1f94 100644 --- a/playground/server/routes/auth/hubspot.get.ts +++ b/playground/server/routes/auth/hubspot.get.ts @@ -5,9 +5,7 @@ export default defineOAuthHubspotEventHandler({ async onSuccess(event, { user }) { await setUserSession(event, { user: { - id: `${user.id}`, - email: `${user.email}`, - domain: `${user.domain}`, + hubspot: user.email, }, loggedInAt: Date.now(), }) diff --git a/src/runtime/server/lib/oauth/hubspot.ts b/src/runtime/server/lib/oauth/hubspot.ts index d9dc1298..ce4ecf83 100644 --- a/src/runtime/server/lib/oauth/hubspot.ts +++ b/src/runtime/server/lib/oauth/hubspot.ts @@ -11,8 +11,60 @@ import { import { useRuntimeConfig } from '#imports' import type { OAuthConfig } from '#auth-utils' -export interface OAuthHubspotConfig {} -export interface OAuthHubspotUser {} +export interface OAuthHubspotConfig { + /** + * Hubspot OAuth Client ID + * @default process.env.NUXT_OAUTH_HUBSPOT_CLIENT_ID + */ + clientId?: string + + /** + * Hubspot OAuth Client Secret + * @default process.env.NUXT_OAUTH_HUBSPOT_CLIENT_SECRET + */ + clientSecret?: string + + /** + * Hubspot OAuth Redirect URL + * @default process.env.NUXT_OAUTH_HUBSPOT_REDIRECT_URL + */ + redirectURL?: string + + /** + * Hubspot OAuth Scope + * @default ['oauth'] + * @see https://developers.hubspot.com/beta-docs/guides/apps/authentication/scopes + * @example ['accounting', 'automation', 'actions'] + */ + scope?: string[] +} +interface SignedAccessToken { + expiresAt: number + scopes: string + hubId: number + userId: number + appId: number + signature: string + scopeToScopeGroupPks?: string + newSignature?: string + hublet?: string + trialScopes?: string + trialScopeToScopeGroupPks?: string + isUserLevel: boolean +} + +interface OAuthHubspotAccessInfo { + token: string + user: string + hub_domain: string + scopes: string[] + signed_access_token: SignedAccessToken + hub_id: number + app_id: number + expires_in: number + user_id: number + token_type: string +} export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: OAuthConfig) { return eventHandler(async (event: H3Event) => { @@ -51,19 +103,17 @@ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: O }, }) - console.log('tokens', tokens) - if (tokens.error) { return handleAccessTokenErrorResponse(event, 'hubspot', tokens, onError) } - const profile: OAuthHubspotUser = await $fetch('https://api.hubapi.com/oauth/v1/access-tokens/' + tokens.access_token) + const info: OAuthHubspotAccessInfo = await $fetch('https://api.hubapi.com/oauth/v1/access-tokens/' + tokens.access_token) return onSuccess(event, { user: { - id: profile.user_id, - email: profile.user, - domain: profile.hub_domain, + id: info.user_id, + email: info.user, + domain: info.hub_domain, }, tokens, }) From d8457cc61e71a672b1168f761631b53ef3387a89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Thu, 12 Dec 2024 12:29:45 -0300 Subject: [PATCH 8/9] fix: scope --- src/runtime/server/lib/oauth/hubspot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/server/lib/oauth/hubspot.ts b/src/runtime/server/lib/oauth/hubspot.ts index ce4ecf83..452de2e1 100644 --- a/src/runtime/server/lib/oauth/hubspot.ts +++ b/src/runtime/server/lib/oauth/hubspot.ts @@ -87,7 +87,7 @@ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: O withQuery('https://app.hubspot.com/oauth/authorize', { client_id: config.clientId, redirect_uri: redirectURL, - scope: config.scope || 'oauth', + scope: config.scope?.join(',') || 'oauth', }), ) } From 0d453a1889bbe8552dedd50470d1a60724d5d87f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Guimar=C3=A3es=20Cazaroto?= Date: Thu, 12 Dec 2024 12:34:00 -0300 Subject: [PATCH 9/9] fix: use spaces for scopes separation --- src/runtime/server/lib/oauth/hubspot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/runtime/server/lib/oauth/hubspot.ts b/src/runtime/server/lib/oauth/hubspot.ts index 452de2e1..60a2cd3a 100644 --- a/src/runtime/server/lib/oauth/hubspot.ts +++ b/src/runtime/server/lib/oauth/hubspot.ts @@ -87,7 +87,7 @@ export function defineOAuthHubspotEventHandler({ config, onSuccess, onError }: O withQuery('https://app.hubspot.com/oauth/authorize', { client_id: config.clientId, redirect_uri: redirectURL, - scope: config.scope?.join(',') || 'oauth', + scope: config.scope?.join(' ') || 'oauth', }), ) }