Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 New features fix #396

Merged
merged 2 commits into from
Apr 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 4 additions & 3 deletions apps/client-ts/src/components/Connection/ConnectionTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ export default function ConnectionTable() {
}

const linkedConnections = (filter: string) => connections?.filter((connection) => connection.status == filter);

//console.log("connections are => "+ JSON.stringify(connections))
const ts = connections?.map((connection) => ({
organisation: nameOrg,
app: connection.provider_slug,
vertical: connection.vertical,
category: connection.token_type,
status: connection.status,
linkedUser: connection.id_linked_user,
date: new Date().toISOString(),
date: connection.created_at,
connectionToken: connection.connection_token!
}))

Expand All @@ -64,7 +65,7 @@ export default function ConnectionTable() {
<CardTitle>Linked</CardTitle>
</CardHeader>
<CardContent>
<p className="text-4xl font-bold">{linkedConnections("0")?.length}</p>
<p className="text-4xl font-bold">{linkedConnections("valid")?.length}</p>
</CardContent>

</Card>
Expand Down
43 changes: 32 additions & 11 deletions apps/client-ts/src/components/Connection/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,24 @@ function insertDots(originalString: string): string {
return originalString.substring(0, 50 - 3) + '...';
}

function formatISODate(ISOString: string): string {
const date = new Date(ISOString);
const options: Intl.DateTimeFormatOptions = {
weekday: 'long', // "Monday"
year: 'numeric', // "2024"
month: 'long', // "April"
day: 'numeric', // "27"
hour: '2-digit', // "02"
minute: '2-digit', // "58"
second: '2-digit', // "59"
timeZoneName: 'short' // "GMT"
};

// Create a formatter (using US English locale as an example)
const formatter = new Intl.DateTimeFormat('en-US', options);
return formatter.format(date);
}

export const columns: ColumnDef<Connection>[] = [
{
id: "select",
Expand All @@ -51,15 +69,15 @@ export const columns: ColumnDef<Connection>[] = [
enableSorting: false,
enableHiding: false,
},
{
/*{
accessorKey: "organisation",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Organisation" />
),
cell: ({ row }) => <div className="w-[80px]"><Badge variant="outline">{row.getValue("organisation") as string}</Badge></div>,
enableSorting: false,
enableHiding: false,
},
},*/
{
accessorKey: "app",
header: ({ column }) => (
Expand Down Expand Up @@ -103,6 +121,15 @@ export const columns: ColumnDef<Connection>[] = [
return value.includes(row.getValue(id))
},
},
{
accessorKey: "vertical",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Vertical" />
),
cell: ({ row }) => <div className="w-[80px]"><Badge variant="outline">{row.getValue("vertical") as string}</Badge></div>,
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "status",
header: ({ column }) => (
Expand Down Expand Up @@ -161,12 +188,12 @@ export const columns: ColumnDef<Connection>[] = [

return (
<div className="flex space-x-2">
<Badge variant="outline">{row.getValue("date")}</Badge>
<Badge variant="outline">{formatISODate(row.getValue("date"))}</Badge>
</div>
)
},
},
{
/*{
accessorKey: "connectionToken",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Connection Token" />
Expand All @@ -176,13 +203,7 @@ export const columns: ColumnDef<Connection>[] = [
<div className=" truncate mr-2">
<Badge variant="outline">{insertDots(row.getValue("connectionToken"))}</Badge>
</div>
<div
className="h-5 w-5 cursor-pointer mt-1"
onClick={() => navigator.clipboard.writeText(row.getValue("connectionToken"))}
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.6788 2.95419C10.0435 2.53694 9.18829 2.54594 8.51194 3.00541C8.35757 3.11027 8.1921 3.27257 7.7651 3.69957L7.14638 4.31829C6.95112 4.51355 6.63454 4.51355 6.43928 4.31829C6.24401 4.12303 6.24401 3.80645 6.43928 3.61119L7.058 2.99247C7.0725 2.97797 7.08679 2.96366 7.1009 2.94955C7.47044 2.57991 7.70691 2.34336 7.95001 2.17822C8.94398 1.50299 10.2377 1.46813 11.2277 2.11832C11.4692 2.27689 11.7002 2.508 12.0515 2.85942C12.0662 2.8741 12.081 2.88898 12.0961 2.90408C12.1112 2.91917 12.1261 2.93405 12.1408 2.94871C12.4922 3.30001 12.7233 3.53102 12.8819 3.77248C13.5321 4.76252 13.4972 6.05623 12.822 7.0502C12.6568 7.2933 12.4203 7.52976 12.0507 7.89929C12.0366 7.9134 12.0222 7.92771 12.0077 7.94221L11.389 8.56093C11.1938 8.7562 10.8772 8.7562 10.6819 8.56093C10.4867 8.36567 10.4867 8.04909 10.6819 7.85383L11.3006 7.23511C11.7276 6.80811 11.8899 6.64264 11.9948 6.48827C12.4543 5.81192 12.4633 4.95675 12.046 4.32141C11.9513 4.17714 11.8009 4.02307 11.389 3.61119C10.9771 3.1993 10.8231 3.04893 10.6788 2.95419ZM4.31796 6.43961C4.51322 6.63487 4.51322 6.95146 4.31796 7.14672L3.69924 7.76544C3.27224 8.19244 3.10993 8.35791 3.00507 8.51227C2.54561 9.18863 2.53661 10.0438 2.95385 10.6791C3.0486 10.8234 3.19896 10.9775 3.61085 11.3894C4.02274 11.8012 4.17681 11.9516 4.32107 12.0464C4.95642 12.4636 5.81158 12.4546 6.48794 11.9951C6.6423 11.8903 6.80777 11.728 7.23477 11.301L7.85349 10.6823C8.04875 10.487 8.36533 10.487 8.5606 10.6823C8.75586 10.8775 8.75586 11.1941 8.5606 11.3894L7.94188 12.0081C7.92738 12.0226 7.91307 12.0369 7.89897 12.051C7.52943 12.4206 7.29296 12.6572 7.04986 12.8223C6.05589 13.4976 4.76219 13.5324 3.77214 12.8822C3.53068 12.7237 3.29967 12.4925 2.94837 12.1411C2.93371 12.1264 2.91883 12.1116 2.90374 12.0965C2.88865 12.0814 2.87377 12.0665 2.8591 12.0518C2.50766 11.7005 2.27656 11.4695 2.11799 11.2281C1.4678 10.238 1.50265 8.94432 2.17788 7.95035C2.34303 7.70724 2.57957 7.47077 2.94922 7.10124C2.96333 7.08713 2.97763 7.07283 2.99213 7.05833L3.61085 6.43961C3.80611 6.24435 4.12269 6.24435 4.31796 6.43961Z" fill="currentColor" fillRule="evenodd" clipRule="evenodd"></path></svg>
</div>
</div>
},
}
}*/
]
3 changes: 2 additions & 1 deletion apps/client-ts/src/components/Connection/data/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ export const connectionSchema = z.object({
organisation: z.string(),
app: z.string(),
category: z.string(),
vertical: z.string(),
status: z.string(),
linkedUser: z.string(),
date: z.string(),
date: z.date(),
connectionToken: z.string()
})

Expand Down
6 changes: 5 additions & 1 deletion apps/client-ts/src/components/RootLayout/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,11 @@ export const RootLayout = () => {
projects: data.projects
//id_organization: data.id_organization as string,
})
setIdProject(data.projects[0].id_project)
console.log("data projects are => "+ JSON.stringify(data.projects));

if(data.projects && data.projects.length > 0 && data.projects[0].id_project){
setIdProject(data.projects[0].id_project)
}
}
}, [data, setIdProject, setProfile]);

Expand Down
6 changes: 4 additions & 2 deletions apps/client-ts/src/components/shared/team-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ export default function TeamSwitcher({ className, userId }: TeamSwitcherProps) {
)}
/>
</CommandItem>
)) : Array.from({ length: 6 }).map((_, index) => (
)) : projects && projects.length === 0 ? <p className="px-2 text-sm">No projects found !</p>

: Array.from({ length: 6 }).map((_, index) => (
<CommandItem
key={index}
className="text-sm"
Expand All @@ -178,7 +180,7 @@ export default function TeamSwitcher({ className, userId }: TeamSwitcherProps) {
<CommandGroup>
{
config.DISTRIBUTION === "managed" && (
<h4>{profile ? profile.email : "no profile"}</h4>
<p className="px-2 text-sm mb-2">{profile ? profile.email : "no profile"}</p>
)
}
<DialogTrigger asChild>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,20 @@ export class ConnectionsStrategiesService {
switch (authStrategy) {
case AuthStrategy.oauth2:
attributes = ['client_id', 'client_secret', 'scope'];
if (needsSubdomain(provider, vertical)) {
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
attributes.push('subdomain');
}
break;
case AuthStrategy.api_key:
attributes = ['api_key'];

if (needsSubdomain(provider, vertical)) {
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
attributes.push('subdomain');
}
break;
case AuthStrategy.basic:
attributes = ['username', 'secret'];
if (needsSubdomain(provider, vertical)) {
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
attributes.push('subdomain');
}
break;
Expand Down Expand Up @@ -232,7 +232,12 @@ export class ConnectionsStrategiesService {
.scopes,
};
}
if (needsSubdomain(provider, vertical)) {
const isSubdomain = needsSubdomain(
provider.toLowerCase(),
vertical.toLowerCase(),
);
console.log('needs subdomain ??? ' + isSubdomain);
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
Comment on lines +235 to +240
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review the necessity of logging and potential information leakage.

The use of console.log to output whether a subdomain is needed could lead to information leakage in a production environment. Consider removing this log or replacing it with a more secure logging mechanism that filters sensitive information.

data = {
...data,
SUBDOMAIN: this.configService.get<string>(
Expand All @@ -247,7 +252,7 @@ export class ConnectionsStrategiesService {
`${provider.toUpperCase()}_${vertical.toUpperCase()}_${softwareMode.toUpperCase()}_API_KEY`,
),
};
if (needsSubdomain(provider, vertical)) {
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
data = {
...data,
SUBDOMAIN: this.configService.get<string>(
Expand All @@ -265,7 +270,7 @@ export class ConnectionsStrategiesService {
`${provider.toUpperCase()}_${vertical.toUpperCase()}_${softwareMode.toUpperCase()}_SECRET`,
),
};
if (needsSubdomain(provider, vertical)) {
if (needsSubdomain(provider.toLowerCase(), vertical.toLowerCase())) {
data = {
...data,
SUBDOMAIN: this.configService.get<string>(
Expand Down
21 changes: 20 additions & 1 deletion packages/api/src/@core/connections/connections.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,32 @@
case ProviderVertical.Unknown:
break;
}

res.redirect(returnUrl);
} catch (error) {
handleServiceError(error, this.logger);
}
}

@Get('gorgias/oauth/install')
handleGorgiasAuthUrl(
@Res() res: Response,
@Query('account') account: string,
@Query('response_type') response_type: string,
@Query('nonce') nonce: string,
@Query('scope') scope: string,
@Query('client_id') client_id: string,
@Query('redirect_uri') redirect_uri: string,
@Query('state') state: string,
) {
try {
if (!account) throw new Error('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}`);

Check warning

Code scanning / CodeQL

Server-side URL redirect Medium

Untrusted URL redirection depends on a
user-provided value
.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the account value is validated or restricted to prevent untrusted URL redirection, which can lead to security vulnerabilities.

} catch (error) {
handleServiceError(error, this.logger);
}
}

@ApiOperation({
operationId: 'getConnections',
summary: 'List Connections',
Expand Down
71 changes: 71 additions & 0 deletions packages/api/swagger/swagger-spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,77 @@
]
}
},
"/connections/gorgias/oauth/install": {
"get": {
"operationId": "ConnectionsController_handleGorgiasAuthUrl",
"parameters": [
{
"name": "account",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "response_type",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "nonce",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "scope",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "client_id",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "redirect_uri",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
},
{
"name": "state",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"connections"
]
}
},
"/connections": {
"get": {
"operationId": "getConnections",
Expand Down
8 changes: 5 additions & 3 deletions packages/shared/src/authUrl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,14 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => {
} = input;

const type = providerToType(providerName, vertical, authStrategy);

// 1. env if selfhost and no custom
// 2. backend if custom credentials
// same for authBaseUrl with subdomain
const DATA = await fetch(`${apiUrl}/connections-strategies/getCredentials?projectId=${projectId}&type=${type}`);
const data = await DATA.json() as OAuth2AuthData;

// console.log("Fetched Data ", JSON.stringify(data))
console.log("Fetched Data ", JSON.stringify(data))

const clientId = data.CLIENT_ID;
if (!clientId) throw new Error(`No client id for type ${type}`)
Expand All @@ -104,8 +104,10 @@ const handleOAuth2Url = async (input: HandleOAuth2Url) => {
if (!baseUrl) throw new Error(`No authBaseUrl found for type ${type}`)

// construct the baseAuthUrl based on the fact that client may use custom subdomain
const BASE_URL: string = data.SUBDOMAIN ? data.SUBDOMAIN + baseUrl : baseUrl;
const BASE_URL: string = providerName === "gorgias" ? `${apiUrl}${baseUrl}`:
data.SUBDOMAIN ? data.SUBDOMAIN + baseUrl : baseUrl;

console.log("BASE URL IS "+ BASE_URL)
if (!baseUrl || !BASE_URL) {
throw new Error(`Unsupported provider: ${providerName}`);
}
Expand Down
6 changes: 4 additions & 2 deletions packages/shared/src/envConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,11 @@ export function needsSubdomain(provider: string, vertical: string): boolean {
// Extract the provider's config
const providerConfig = providersConfig[vertical][provider];

const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.startsWith('/');
const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.startsWith('/');
const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.substring(0,1) === '/';
const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.substring(0,1) === '/';
Comment on lines +94 to +95
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider reverting the check for leading slashes to use startsWith('/') instead of substring(0,1) === '/' for better readability and idiomatic JavaScript.

- const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.substring(0,1) === '/';
- const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.substring(0,1) === '/';
+ const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.startsWith('/');
+ const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.startsWith('/');

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.substring(0,1) === '/';
const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.substring(0,1) === '/';
const authBaseUrlStartsWithSlash = providerConfig.urls.authBaseUrl!.startsWith('/');
const apiUrlStartsWithSlash = providerConfig.urls.apiUrl!.startsWith('/');

const apiUrlIsBlank = providerConfig.urls.apiUrl! === '';

//console.log("subdomain needed "+ authBaseUrlStartsWithSlash)

return authBaseUrlStartsWithSlash || apiUrlStartsWithSlash || apiUrlIsBlank;
}
4 changes: 2 additions & 2 deletions packages/shared/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export const providersConfig: ProvidersConfig = {
authStrategy: AuthStrategy.oauth2
},
'attio': {
// scopes: 'record_permission:read',
scopes: 'record_permission:read',
urls: {
docsUrl: 'https://developers.attio.com/reference',
authBaseUrl: 'https://app.attio.com/authorize',
Expand Down Expand Up @@ -349,7 +349,7 @@ export const providersConfig: ProvidersConfig = {
urls: {
docsUrl: 'https://developers.gorgias.com/reference/introduction',
apiUrl: '/api',
authBaseUrl: '/oauth/authorize',
authBaseUrl: `/connections/gorgias/oauth/install`,
},
logoPath: 'https://x5h8w2v3.rocketcdn.me/wp-content/uploads/2020/09/FS-AFFI-00660Gorgias.png',
description: 'Sync & Create accounts, tickets, comments, attachments, contacts, tags, teams and users',
Expand Down
Loading