Skip to content

Commit

Permalink
Merge branch 'main' into ticketing-linear
Browse files Browse the repository at this point in the history
  • Loading branch information
ganimtron-10 committed Aug 13, 2024
2 parents 3055309 + 0dd37c0 commit acea3dc
Show file tree
Hide file tree
Showing 55 changed files with 2,651 additions and 178 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ BOX_FILESTORAGE_CLOUD_CLIENT_ID=
BOX_FILESTORAGE_CLOUD_CLIENT_SECRET=



# ================================================
# Webapp settings
# Must be set in the perspective of the end user browser
Expand Down
2 changes: 1 addition & 1 deletion apps/magic-link/src/lib/ProviderModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface IBasicAuthFormData {

const domainFormats: { [key: string]: string } = {
microsoftdynamicssales: 'YOURORGNAME.api.crm12.dynamics.com',
bigcommerce: 'If your api domain is https://api.bigcommerce.com/stores/eubckcvkzg/v3 then store_hash is eubckcvkzg'
bigcommerce: 'If your api domain is https://api.bigcommerce.com/stores/eubckcvkzg/v3 then store_hash is eubckcvkzg',
};

const ProviderModal = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "
import { PasswordInput } from "@/components/ui/password-input"
import { z } from "zod"
import config from "@/lib/config"
import { AuthStrategy, providerToType, Provider, extractProvider, extractVertical, needsSubdomain, needsScope } from "@panora/shared"
import { AuthStrategy, providerToType, Provider,CONNECTORS_METADATA, extractProvider, extractVertical, needsSubdomain, needsScope } from "@panora/shared"
import { useEffect, useState } from "react"
import useProjectStore from "@/state/projectStore"
import { usePostHog } from 'posthog-js/react'
Expand Down Expand Up @@ -75,6 +75,14 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
},
})

// Extract oauth_attributes from the connector metadata
const oauthAttributes = CONNECTORS_METADATA[item?.vertical!][item?.name!].options?.oauth_attributes || [];

// Update the form schema to include dynamic fields
oauthAttributes.forEach((attr: string) => {
formSchema.shape[attr as keyof z.infer<typeof formSchema>] = z.string().optional(); // Add each attribute as an optional string
});

const handleCopy = async () => {
try {
await navigator.clipboard.writeText(`${config.API_URL}/connections/oauth/callback`)
Expand All @@ -94,6 +102,7 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
function onSubmit(values: z.infer<typeof formSchema>) {
const { client_id, client_secret, scope, api_key, secret, username, subdomain } = values;
const performUpdate = mappingConnectionStrategies && mappingConnectionStrategies.length > 0;
const dynamicAttributes = oauthAttributes.map((attr: string) => values[attr as keyof z.infer<typeof formSchema>]).filter((value: any) => value !== undefined);
switch (item?.authStrategy.strategy) {
case AuthStrategy.oauth2:
const needs_subdomain = needsSubdomain(item.name.toLowerCase(), item.vertical!.toLowerCase());
Expand All @@ -116,8 +125,8 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
if (needs_scope && scope === "") {
form.setError("scope", { "message": "Please Enter the scope" });
}
let ATTRIBUTES = ["client_id", "client_secret"];
let VALUES = [client_id!, client_secret!];
let ATTRIBUTES = ["client_id", "client_secret", ...oauthAttributes];
let VALUES = [client_id!, client_secret!, ...dynamicAttributes];
if(needs_subdomain){
ATTRIBUTES.push("subdomain")
VALUES.push(subdomain!)
Expand All @@ -134,7 +143,7 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
updateToggle: false,
status: dataToUpdate.status,
attributes: ATTRIBUTES,
values: VALUES
values: VALUES as string[]
}),
{
loading: 'Loading...',
Expand Down Expand Up @@ -163,7 +172,7 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
createCsPromise({
type: providerToType(item?.name, item?.vertical!, AuthStrategy.oauth2),
attributes: ATTRIBUTES,
values: VALUES
values: VALUES as string[]
}),
{
loading: 'Loading...',
Expand Down Expand Up @@ -343,19 +352,32 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
if (mappingConnectionStrategies && mappingConnectionStrategies.length > 0) {
fetchCredentials({
type: mappingConnectionStrategies[0].type,
attributes: item?.authStrategy.strategy === AuthStrategy.oauth2 ? needsSubdomain(item.name.toLowerCase(), item.vertical!.toLowerCase()) ? ["subdomain", "client_id", "client_secret"] : needsScope(item.name.toLowerCase(), item.vertical!.toLowerCase()) ? ["client_id", "client_secret","scope", ] : ["client_id", "client_secret"]
attributes: item?.authStrategy.strategy === AuthStrategy.oauth2 ? needsSubdomain(item.name.toLowerCase(), item.vertical!.toLowerCase()) ? ["subdomain", "client_id", "client_secret", ...oauthAttributes] : needsScope(item.name.toLowerCase(), item.vertical!.toLowerCase()) ? ["client_id", "client_secret","scope", ...oauthAttributes] : ["client_id", "client_secret", ...oauthAttributes]
: item?.authStrategy.strategy === AuthStrategy.api_key ? ["api_key"] : ["username", "secret"]
}, {
onSuccess(data) {
if (item?.authStrategy.strategy === AuthStrategy.oauth2) {
let i = 0;
if(needsSubdomain(item.name.toLowerCase(), item.vertical!.toLowerCase())){
if (needsSubdomain(item.name.toLowerCase(), item.vertical?.toLowerCase()!)) {
form.setValue("subdomain", data[i]);
i = 1;
i += 1;
}

// Set client_id and client_secret
form.setValue("client_id", data[i]);
form.setValue("client_secret", data[i + 1]);
form.setValue("scope", data[i + 2]);
i += 2; // Increment i after setting client_id and client_secret

// Check if scope is needed and set the value if so
if (needsScope(item.name.toLowerCase(), item.vertical?.toLowerCase()!)) {
form.setValue("scope", data[i]);
i += 1;
}

// Set any additional OAuth attributes
oauthAttributes.forEach((attr: string, index: number) => {
form.setValue(attr as keyof z.infer<typeof formSchema>, data[i + index]);
});
}
if (item?.authStrategy.strategy === AuthStrategy.api_key) {
form.setValue("api_key", data[0]);
Expand Down Expand Up @@ -501,6 +523,23 @@ export function ConnectorDisplay({ item }: ItemDisplayProps) {
)}
/>
</div>
{oauthAttributes.map((attr: string) => (
<div className="flex flex-col" key={attr}>
<FormField
name={attr as keyof z.infer<typeof formSchema>}
control={form.control}
render={({ field }) => (
<FormItem>
<FormLabel className="flex flex-col">{attr}</FormLabel>
<FormControl>
<Input {...field} placeholder={`Enter ${attr}`} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
))}
<div className="flex flex-col">
<FormLabel className="flex flex-col">Redirect URI</FormLabel>
<div className="flex gap-2 mt-1">
Expand Down
13 changes: 13 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,12 +165,25 @@ services:
IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET: ${IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_CLIENT_ID: ${EBAY_ECOMMERCE_CLOUD_CLIENT_ID}
EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_RUVALUE: ${EBAY_ECOMMERCE_CLOUD_RUVALUE}
FAIRE_ECOMMERCE_CLOUD_CLIENT_ID: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_ID}
FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_CLIENT_ID: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_ID}
AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID: ${AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID}
EMAIL_SENDING_ADDRESS: ${EMAIL_SENDING_ADDRESS}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
SMTP_USER: ${SMTP_USER}
SMTP_PASSWORD: ${SMTP_PASSWORD}


restart: unless-stopped
ports:
- 3000:3000
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.source.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,18 @@ services:
IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET: ${IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_CLIENT_ID: ${EBAY_ECOMMERCE_CLOUD_CLIENT_ID}
EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_RUVALUE: ${EBAY_ECOMMERCE_CLOUD_RUVALUE}
FAIRE_ECOMMERCE_CLOUD_CLIENT_ID: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_ID}
FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_CLIENT_ID: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_ID}
AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID: ${AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID}
EMAIL_SENDING_ADDRESS: ${EMAIL_SENDING_ADDRESS}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ services:
IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET: ${IRONCLAD_TICKETING_CLOUD_CLIENT_SECRET}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_ID}
SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SHOPIFY_ECOMMERCE_CLOUD_CLIENT_SECRET}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_ID}
SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${SQUARESPACE_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_CLIENT_ID: ${EBAY_ECOMMERCE_CLOUD_CLIENT_ID}
EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET: ${EBAY_ECOMMERCE_CLOUD_CLIENT_SECRET}
EBAY_ECOMMERCE_CLOUD_RUVALUE: ${EBAY_ECOMMERCE_CLOUD_RUVALUE}
FAIRE_ECOMMERCE_CLOUD_CLIENT_ID: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_ID}
FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET: ${FAIRE_ECOMMERCE_CLOUD_CLIENT_SECRET}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_ID}
WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET: ${WEBFLOW_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_CLIENT_ID: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_ID}
AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET: ${AMAZON_ECOMMERCE_CLOUD_CLIENT_SECRET}
AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID: ${AMAZON_ECOMMERCE_CLOUD_APPLICATION_ID}
EMAIL_SENDING_ADDRESS: ${EMAIL_SENDING_ADDRESS}
SMTP_HOST: ${SMTP_HOST}
SMTP_PORT: ${SMTP_PORT}
Expand Down
9 changes: 6 additions & 3 deletions packages/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ model api_keys {
projects projects @relation(fields: [id_project], references: [id_project], onDelete: NoAction, onUpdate: NoAction, map: "fk_7")
users users @relation(fields: [id_user], references: [id_user], onDelete: NoAction, onUpdate: NoAction, map: "fk_8")
@@index([id_user], map: "fk_2")
@@index([id_project], map: "fk_api_keys_projects")
@@index([id_user], map: "fk_2")
}

/// This model or at least one of its fields has comments in the database, and requires an additional setup for migrations: Read more: https://pris.ly/d/database-comments
Expand Down Expand Up @@ -678,6 +678,9 @@ model connector_sets {
crm_zendesk Boolean?
crm_close Boolean?
fs_box Boolean?
tcg_github Boolean?
ecom_woocommerce Boolean?
ecom_shopify Boolean?
projects projects[]
}

Expand Down Expand Up @@ -1748,9 +1751,9 @@ model ecom_addresses {
modified_at DateTime @db.Timestamptz(6)
created_at DateTime @db.Timestamptz(6)
remote_deleted Boolean
id_ecom_order String @db.Uuid
id_ecom_order String? @db.Uuid
ecom_customers ecom_customers @relation(fields: [id_ecom_customer], references: [id_ecom_customer], onDelete: NoAction, onUpdate: NoAction, map: "fk_ecom_customer_customeraddress")
ecom_orders ecom_orders @relation(fields: [id_ecom_order], references: [id_ecom_order], onDelete: NoAction, onUpdate: NoAction, map: "fk_ecom_order_address")
ecom_orders ecom_orders? @relation(fields: [id_ecom_order], references: [id_ecom_order], onDelete: NoAction, onUpdate: NoAction, map: "fk_ecom_order_address")
@@index([id_ecom_customer], map: "fk_index_ecom_customer_customeraddress")
@@index([id_ecom_order], map: "fk_index_fk_ecom_order_address")
Expand Down
8 changes: 5 additions & 3 deletions packages/api/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,10 @@ CREATE TABLE connector_sets
crm_zendesk boolean NULL,
crm_close boolean NULL,
fs_box boolean NULL,
tcg_github boolean NULL,
tcg_linear boolean NULL,
tcg_github boolean NULL,
ecom_woocommerce boolean NULL,
ecom_shopify boolean NULL,
tcg_linear boolean NULL,
CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set )
);

Expand Down Expand Up @@ -1532,7 +1534,7 @@ CREATE TABLE ecom_addresses
modified_at timestamp with time zone NOT NULL,
created_at timestamp with time zone NOT NULL,
remote_deleted boolean NOT NULL,
id_ecom_order uuid NOT NULL,
id_ecom_order uuid NULL,
CONSTRAINT PK_ecom_customer_addresses PRIMARY KEY ( id_ecom_address ),
CONSTRAINT FK_ecom_customer_customeraddress FOREIGN KEY ( id_ecom_customer ) REFERENCES ecom_customers ( id_ecom_customer ),
CONSTRAINT FK_ecom_order_address FOREIGN KEY ( id_ecom_order ) REFERENCES ecom_orders ( id_ecom_order )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,10 @@ export class ConnectionsStrategiesService {
let attributes: string[] = [];
switch (authStrategy) {
case AuthStrategy.oauth2:
attributes = ['client_id', 'client_secret'];
const dynamic_attributes =
CONNECTORS_METADATA[vertical.toLowerCase()][provider.toLowerCase()]
?.options?.oauth_attributes || [];
attributes = ['client_id', 'client_secret', ...dynamic_attributes];
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
attributes.push('subdomain');
}
Expand Down Expand Up @@ -280,6 +283,19 @@ export class ConnectionsStrategiesService {
),
};
}
const object =
CONNECTORS_METADATA[vertical.toLowerCase()][provider.toLowerCase()];
if (object.options && object.options.oauth_attributes) {
const dynamic_attributes = object.options.oauth_attributes;
for (const attr of dynamic_attributes) {
data = {
...data,
[attr.toUpperCase()]: this.configService.get<string>(
`${provider.toUpperCase()}_${vertical.toUpperCase()}_${softwareMode.toUpperCase()}_${attr.toUpperCase()}`,
),
};
}
}
return data;
case AuthStrategy.api_key:
data = {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/@core/connections/@utils/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { connections as Connection2 } from '@prisma/client';
export type OAuthCallbackParams = {
projectId: string;
linkedUserId: string;
code: string;
code?: string;
[key: string]: any;
};

Expand Down
49 changes: 41 additions & 8 deletions packages/api/src/@core/connections/connections.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,11 +66,14 @@ export class ConnectionsController {
@Get('oauth/callback')
async handleOAuthCallback(@Res() res: Response, @Query() query: any) {
try {
const { state, code, ...otherParams } = query;
if (!code) {
const { state, code, spapi_oauth_code, ...otherQueryParams } = query;

if (!code && !spapi_oauth_code) {
throw new ConnectionsError({
name: 'OAUTH_CALLBACK_CODE_NOT_FOUND_ERROR',
message: `No Callback Params found for code, found ${code}`,
message: `No Callback Params found for code, found ${
code || spapi_oauth_code
}`,
});
}

Expand All @@ -81,27 +84,58 @@ export class ConnectionsController {
});
}

const stateData: StateDataType = JSON.parse(decodeURIComponent(state));
let stateData: StateDataType;

// Step 1: Check for HTML entities
if (state.includes('&quot;') || state.includes('&amp;')) {
// Step 2: Replace HTML entities
const decodedState = state
.replace(/&quot;/g, '"') // Replace &quot; with "
.replace(/&amp;/g, '&'); // Replace &amp; with &

// Step 3: Parse the JSON
stateData = JSON.parse(decodedState);
} else if (state.includes('squarespace_delimiter')) {
// squarespace asks for a random alphanumeric value
// Split the random part and the base64 part
const [randomPart, base64Part] = decodeURIComponent(state).split(
'squarespace_delimiter',
);
// Decode the base64 part to get the original JSON
const jsonString = Buffer.from(base64Part, 'base64').toString('utf-8');
stateData = JSON.parse(jsonString);
} else {
// If no HTML entities are present, parse directly
stateData = JSON.parse(state);
}

const {
projectId,
vertical,
linkedUserId,
providerName,
returnUrl,
resource,
...dynamicStateParams
} = stateData;

const service = this.categoryConnectionRegistry.getService(
vertical.toLowerCase(),
);
await service.handleCallBack(
providerName,
{ linkedUserId, projectId, code, otherParams, resource },
{
linkedUserId,
projectId,
code,
spapi_oauth_code,
...otherQueryParams,
...dynamicStateParams,
},
'oauth2',
);
if (providerName == 'shopify') {
// we must redirect using shop and host to get a valid session on shopify server
service.redirectUponConnection(res, otherParams);
service.redirectUponConnection(res, ...otherQueryParams);
} else {
res.redirect(returnUrl);
}
Expand Down Expand Up @@ -134,7 +168,6 @@ export class ConnectionsController {
@Query('state') state: string,
) {
try {
console.log(client_id)
if (!account) throw new ReferenceError('account prop not found');
const params = `?client_id=${client_id}&response_type=${response_type}&redirect_uri=${redirect_uri}&state=${state}&nonce=${nonce}&scope=${scope}`;
res.redirect(`https://${account}.gorgias.com/oauth/authorize${params}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export class AttioConnectionService extends AbstractBaseConnectionService {
vertical: 'crm',
},
});
if (isNotUnique) return;

//reconstruct the redirect URI that was passed in the frontend it must be the same
const REDIRECT_URI = `${this.env.getPanoraBaseUrl()}/connections/oauth/callback`;
const CREDENTIALS = (await this.cService.getCredentials(
Expand Down
Loading

0 comments on commit acea3dc

Please sign in to comment.