Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion apps/sim/app/api/auth/oauth2/shopify/store/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { account } from '@sim/db/schema'
import { createLogger } from '@sim/logger'
import { and, eq } from 'drizzle-orm'
import { type NextRequest, NextResponse } from 'next/server'
import { shopifyStoreCookieSchema } from '@/lib/api/contracts/oauth-connections'
import {
shopifyShopDomainSchema,
shopifyStoreCookieSchema,
} from '@/lib/api/contracts/oauth-connections'
import { getSession } from '@/lib/auth'
import { getBaseUrl } from '@/lib/core/utils/urls'
import { isSameOrigin } from '@/lib/core/utils/validation'
Expand Down Expand Up @@ -38,6 +41,11 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
}
const { accessToken, shopDomain, scope, returnUrl } = parsedCookies.data

if (!shopifyShopDomainSchema.safeParse(shopDomain).success) {
logger.error('Invalid shop domain format in cookie', { shopDomain })
return NextResponse.redirect(`${baseUrl}/workspace?error=shopify_invalid_domain`)
}

const shopResponse = await fetch(`https://${shopDomain}/admin/api/2024-10/shop.json`, {
Comment thread
waleedlatif1 marked this conversation as resolved.
headers: {
'X-Shopify-Access-Token': accessToken,
Expand Down
7 changes: 7 additions & 0 deletions apps/sim/app/api/tools/tts/unified/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,13 @@ async function synthesizeWithAzure(
throw new Error('text and apiKey are required for Azure TTS')
}

const AZURE_REGION_RE = /^[a-z][a-z0-9-]{1,30}[a-z0-9]$/
if (!AZURE_REGION_RE.test(region)) {
throw new Error(
'Invalid Azure region: must match /^[a-z][a-z0-9-]{1,30}[a-z0-9]$/ (e.g. eastus, westeurope)'
)
}

let ssml = `<speak version='1.0' xml:lang='en-US' xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="https://www.w3.org/2001/mstts"><voice name='${voiceId}'>`

if (style) {
Expand Down
8 changes: 7 additions & 1 deletion apps/sim/lib/api/contracts/tools/media/tts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ export const ttsUnifiedToolBodySchema = z
volumeGainDb: z.coerce.number().optional(),
sampleRateHertz: z.coerce.number().optional(),
effectsProfileId: z.array(z.string()).optional(),
region: z.string().optional(),
region: z
.string()
.regex(
/^[a-z][a-z0-9-]{1,30}[a-z0-9]$/,
'region must be a valid Azure region identifier (e.g. eastus, westeurope)'
)
.optional(),
rate: z.string().optional(),
styleDegree: z.coerce.number().optional(),
role: z.string().optional(),
Expand Down
64 changes: 55 additions & 9 deletions apps/sim/lib/auth/auth.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createHash } from 'crypto'
import { cache } from 'react'
import { sso } from '@better-auth/sso'
import { stripe } from '@better-auth/stripe'
Expand Down Expand Up @@ -1611,27 +1612,71 @@ export const auth = betterAuth({
clientSecret: env.WEALTHBOX_CLIENT_SECRET as string,
authorizationUrl: 'https://app.crmworkspace.com/oauth/authorize',
tokenUrl: 'https://app.crmworkspace.com/oauth/token',
userInfoUrl: 'https://dummy-not-used.wealthbox.com', // Dummy URL since no user info endpoint exists
userInfoUrl: 'https://api.crmworkspace.com/v1/me',
scopes: getCanonicalScopesForProvider('wealthbox'),
responseType: 'code',
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/wealthbox`,
getUserInfo: async (_tokens) => {
getUserInfo: async (tokens) => {
try {
logger.info('Creating Wealthbox user profile from token data')
logger.info('Fetching Wealthbox user profile')

const response = await fetch('https://api.crmworkspace.com/v1/me', {
headers: {
Authorization: `Bearer ${tokens.accessToken}`,
},
})

const uniqueId = 'wealthbox-user'
const now = new Date()

if (response.ok) {
const data = await response.json()
const userId = data.id?.toString()
if (!userId) {
return null
}
const email =
data.email && typeof data.email === 'string'
? data.email
: `wealthbox-${userId}@wealthbox.user`
const name = data.name || data.full_name || data.username || 'Wealthbox User'

return {
id: `wealthbox-${userId}-${generateId()}`,
name,
email,
emailVerified: false,
createdAt: now,
updatedAt: now,
}
}

// Fallback: derive a stable identifier from the refresh token (long-lived)
// rather than the access token (rotates every ~2 hours) to avoid creating
// duplicate accounts on token refresh.
logger.warn(
'Wealthbox user info fetch failed, falling back to token-derived identity',
{
status: response.status,
}
)
const stableToken = tokens.refreshToken ?? tokens.accessToken
if (!stableToken) {
logger.error('Wealthbox fallback identity: no refresh or access token available')
return null
}
const tokenHash = createHash('sha256').update(stableToken).digest('hex').slice(0, 24)
return {
id: `${uniqueId}-${generateId()}`,
id: `wealthbox-${tokenHash}-${generateId()}`,
name: 'Wealthbox User',
email: `${uniqueId}@wealthbox.user`,
email: `wealthbox-${tokenHash}@wealthbox.user`,
emailVerified: false,
createdAt: now,
updatedAt: now,
}
} catch (error) {
logger.error('Error creating Wealthbox user profile:', { error })
logger.error('Error creating Wealthbox user profile:', {
error: toError(error).message,
})
return null
}
},
Expand Down Expand Up @@ -1730,11 +1775,12 @@ export const auth = betterAuth({
}

logger.info('HubSpot token metadata response:', {
hubId: data.hub_id,
hubDomain: data.hub_domain,
userId: data.user_id,
hasScopes: !!data.scopes,
scopesType: typeof data.scopes,
scopesIsArray: Array.isArray(data.scopes),
scopesValue: data.scopes,
fullResponse: data,
})

return {
Expand Down
Loading