Files
ever-gauzy/packages/plugins/integration-activepieces/src/lib/activepieces-authorization.controller.ts
T
Kifungo A 62be88a755 [Fix] AI suggestion stage (#8996)
* chore(cspell): remove typo from cspell dictionary

* chore(camshot): update delete command type

* refactor(camshot): include id in camshot not found error message

* docs(config): add jsdoc to activepieces property

* refactor(camshot): standardize find options type for findById

Replace TypeORM's FindOneOptions with the application's standardized
FindOptionsQueryDTO for consistency in query parameter handling.

* feat(integration-activepieces): use dto for activepieces query params

Create ActivepiecesQueryDto to define the structure, typing,
and validation for query parameters handled by the
ActivepiecesAuthorizationController.
Replace the 'any' type annotation with ActivepiecesQueryDto
in the authorize and callback methods. This improves type safety
and enables automatic validation and Swagger documentation
generation for the API endpoints.

* chore(integration-activepieces): remove empty entities array from plugin config

The `entities` array in the Activepieces VendurePlugin configuration was declared
but empty and served no purpose. Remove the array and the associated comment
to clean up the configuration.

* fix(activepieces): improve error handling for connection

Update error handling in `createActivePiecesConnection` to provide more specific exception types. Catch and re-throw `UnauthorizedException` explicitly, and wrap other errors in `InternalServerErrorException` for clearer reporting downstream.

* docs(common): fix typo in callback url documentation

* refactor(activepieces): remove unused ID import

* style(camshot): improve not found error message

Add 'id' before the variable for clarity.
Adjust formatting in the constructor.

* docs(camshot): add api docs for get camshot by id

add @ApiResponse decorators for status codes 200, 404, and 500
add @ApiQuery decorator for the optional options parameter

* refactor(activepieces): rename query dto and add validation

Rename ActivepiecesQueryDto to ActivepiecesQueryDTO for naming
consistency.
Add ValidationPipe to the authorize endpoint to ensure incoming query
parameters are validated against the DTO schema. This improves input
handling robustness.

* fix(activepieces): correctly handle boolean setting values

The previous implementation of `isIntegrationEnabled` only handled boolean values stored as JSON strings. This update adds support for boolean values stored as native booleans or other types, ensuring the check works correctly regardless of how the setting value is stored.

* fix(activepieces): fix error handling and reporting

Extract detailed error messages from the ActivePieces API response body
when handling errors during connection creation. This provides users
with more specific information about why a connection failed.
Also, ensure that specific HTTP exceptions caught within the HTTP request
observable's catchError are not subsequently wrapped in a BadRequestException
by the outer try-catch block. This preserves the original HTTP status code
and error type.
2025-06-11 22:45:10 +02:00

138 lines
4.6 KiB
TypeScript

import { Controller, Get, HttpException, HttpStatus, Query, Res, UsePipes, ValidationPipe } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { Response } from 'express';
import { ConfigService } from '@gauzy/config';
import { Public, IActivepiecesConfig } from '@gauzy/common';
import { IntegrationEnum } from '@gauzy/contracts';
import { buildQueryString } from '@gauzy/utils';
import { ACTIVEPIECES_OAUTH_AUTHORIZE_URL, ACTIVEPIECES_SCOPES, OAUTH_RESPONSE_TYPE } from './activepieces.config';
import { ActivepiecesQueryDTO } from './dto/activepieces-query.dto';
@ApiTags('ActivePieces Integration')
@Public()
@Controller('/integration/activepieces')
export class ActivepiecesAuthorizationController {
constructor(private readonly configService: ConfigService) {}
/**
* Generate a random state parameter for CSRF protection
*
* @returns {string} Random state string
*/
private generateState(): string {
return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
}
/**
* Initiate OAuth authorization flow with ActivePieces
*
* @param {any} query - Query parameters including state
* @param {Response} response - Express Response object
*/
@ApiOperation({ summary: 'Initiate OAuth flow with ActivePieces' })
@ApiResponse({
status: 200,
description: 'Returns the ActivePieces authorization URL',
schema: {
type: 'object',
properties: {
authorizationUrl: {
type: 'string',
description: 'The URL to redirect the user to for authorization'
},
state: {
type: 'string',
description: 'The state parameter for CSRF protection'
}
}
}
})
@Get('/authorize')
@UsePipes(new ValidationPipe({ whitelist: true, transform: true }))
async authorize(@Query() query: ActivepiecesQueryDTO, @Res() response: Response) {
try {
// Get ActivePieces configuration
const activepiecesConfig = this.configService.get('activepieces') as IActivepiecesConfig;
if (!activepiecesConfig?.clientId || !activepiecesConfig?.callbackUrl) {
throw new HttpException('ActivePieces configuration is incomplete', HttpStatus.INTERNAL_SERVER_ERROR);
}
// Generate state parameter for CSRF protection if not provided
const state = query.state || this.generateState();
// Build authorization URL parameters
const authParams = new URLSearchParams({
client_id: activepiecesConfig.clientId,
redirect_uri: activepiecesConfig.callbackUrl,
response_type: OAUTH_RESPONSE_TYPE,
scope: ACTIVEPIECES_SCOPES,
state: state
});
// Construct the full authorization URL
const authorizationUrl = `${ACTIVEPIECES_OAUTH_AUTHORIZE_URL}?${authParams.toString()}`;
// Return the authorization URL in JSON format instead of redirecting
return response.json({
authorizationUrl,
state
});
} catch (error: any) {
throw new HttpException(
`Failed to initiate ${IntegrationEnum.ACTIVE_PIECES} authorization: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
/**
* Handle the callback from ActivePieces after user authorization
*
* @param {any} query - The query parameters from the callback
* @param {Response} response - Express Response object
*/
@ApiOperation({ summary: 'Handle ActivePieces OAuth callback' })
@ApiResponse({
status: 302,
description: 'Redirects to the application with authorization code'
})
@Get('/callback')
async callback(@Query() query: ActivepiecesQueryDTO, @Res() response: Response) {
try {
// Validate the input data
if (!query || !query.code || !query.state) {
throw new HttpException('Invalid query parameters', HttpStatus.BAD_REQUEST);
}
// Get ActivePieces configuration for post-install URL
const activepiecesConfig = this.configService.get('activepieces') as IActivepiecesConfig;
if (!activepiecesConfig?.postInstallUrl) {
throw new HttpException(
'ActivePieces post-install URL is not configured',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
// Convert query params object to string
const queryParamsString = buildQueryString({
code: query.code,
state: query.state,
integration: IntegrationEnum.ACTIVE_PIECES
});
// Combine post install URL with query params
const redirectUrl = [activepiecesConfig.postInstallUrl, queryParamsString].filter(Boolean).join('?');
// Redirect to the application with the authorization code
return response.redirect(redirectUrl);
} catch (error: any) {
// Handle errors and return an appropriate error response
throw new HttpException(
`Failed to handle ${IntegrationEnum.ACTIVE_PIECES} callback: ${error.message}`,
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
}