n8n/packages/cli/src/modules/dynamic-credentials.ee/workflow-status.controller.ts

94 lines
3.6 KiB
TypeScript

import { WorkflowExecutionStatus } from '@n8n/api-types';
import { GlobalConfig } from '@n8n/config';
import { Time } from '@n8n/constants';
import { Get, Options, RestController } from '@n8n/decorators';
import { Container } from '@n8n/di';
import { Request, Response } from 'express';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
import { UrlService } from '@/services/url.service';
import { DynamicCredentialsConfig } from './dynamic-credentials.config';
import { CredentialResolverWorkflowService } from './services/credential-resolver-workflow.service';
import { DynamicCredentialCorsService } from './services/dynamic-credential-cors.service';
import { DynamicCredentialWebService } from './services/dynamic-credential-web.service';
import { getDynamicCredentialMiddlewares } from './utils';
const dynamicCredentialsConfig = Container.get(DynamicCredentialsConfig);
@RestController('/workflows')
export class WorkflowStatusController {
constructor(
private readonly credentialResolverWorkflowService: CredentialResolverWorkflowService,
private readonly urlService: UrlService,
private readonly globalConfig: GlobalConfig,
private readonly dynamicCredentialCorsService: DynamicCredentialCorsService,
private readonly dynamicCredentialWebService: DynamicCredentialWebService,
) {}
/**
* OPTIONS /workflows/:workflowId/execution-status
*
* Handles CORS preflight requests
*/
@Options('/:workflowId/execution-status', { skipAuth: true })
handlePreflightExecutionStatus(req: Request, res: Response): void {
this.dynamicCredentialCorsService.preflightHandler(req, res, ['get', 'options']);
}
/**
* GET /workflows/:workflowId/execution-status
*
* Checks if a workflow is ready to execute by validating all resolvable credentials.
* Requires Bearer token authentication in Authorization header.
*
* @returns Workflow execution status with credential details and authorization URLs
* @throws {BadRequestError} When authorization header is missing or malformed
*/
@Get('/:workflowId/execution-status', {
allowUnauthenticated: true,
middlewares: getDynamicCredentialMiddlewares(),
ipRateLimit: {
limit: dynamicCredentialsConfig.rateLimitPerMinute,
windowMs: 1 * Time.minutes.toMilliseconds,
},
})
async checkWorkflowForExecution(req: Request, res: Response): Promise<WorkflowExecutionStatus> {
this.dynamicCredentialCorsService.applyCorsHeadersIfEnabled(req, res, ['get', 'options']);
const workflowId = req.params['workflowId'];
const credentialContext = this.dynamicCredentialWebService.getCredentialContextFromRequest(req);
if (!workflowId) {
throw new BadRequestError('Workflow ID is missing');
}
const status = await this.credentialResolverWorkflowService.getWorkflowStatus(
workflowId,
credentialContext,
);
const isReady = status.every((s) => s.status === 'configured');
const basePath = this.urlService.getInstanceBaseUrl();
const restPath = this.globalConfig.endpoints.rest;
const executionStatus: WorkflowExecutionStatus = {
workflowId,
readyToExecute: isReady,
credentials: status.map((s) => ({
credentialId: s.credentialId,
credentialName: s.credentialName,
credentialStatus: s.status,
credentialType: s.credentialType,
...(s.resolverId
? {
authorizationUrl: `${basePath}/${restPath}/credentials/${s.credentialId}/authorize?resolverId=${encodeURIComponent(s.resolverId)}`,
revokeUrl: `${basePath}/${restPath}/credentials/${s.credentialId}/revoke?resolverId=${encodeURIComponent(s.resolverId)}`,
}
: {}),
})),
};
return executionStatus;
}
}