POC successful for protect with hf
This commit is contained in:
parent
9f6db09edf
commit
6e7e28a2e6
|
|
@ -1,4 +1,4 @@
|
||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.n8n/.env
|
.n8n
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
import {
|
|
||||||
ICredentialTestRequest,
|
|
||||||
ICredentialType,
|
|
||||||
INodeProperties,
|
|
||||||
} from 'n8n-workflow';
|
|
||||||
|
|
||||||
export class SecloreProtectApi implements ICredentialType {
|
|
||||||
name = 'secloreProtectApi';
|
|
||||||
displayName = 'Seclore Protect API';
|
|
||||||
documentationUrl = 'https://docs.seclore.com/';
|
|
||||||
properties: INodeProperties[] = [
|
|
||||||
{
|
|
||||||
displayName: 'Base URL',
|
|
||||||
name: 'baseUrl',
|
|
||||||
type: 'string',
|
|
||||||
default: 'https://api.seclore.com',
|
|
||||||
placeholder: 'https://api.seclore.com',
|
|
||||||
description: 'The base URL of your Seclore API instance',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Tenant ID',
|
|
||||||
name: 'tenantId',
|
|
||||||
type: 'string',
|
|
||||||
default: '',
|
|
||||||
placeholder: 'your-tenant-id',
|
|
||||||
description: 'Your Seclore tenant ID',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
displayName: 'Tenant Secret',
|
|
||||||
name: 'tenantSecret',
|
|
||||||
type: 'string',
|
|
||||||
typeOptions: {
|
|
||||||
password: true,
|
|
||||||
},
|
|
||||||
default: '',
|
|
||||||
description: 'Your Seclore tenant secret',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Optional: Add credential test
|
|
||||||
test: ICredentialTestRequest = {
|
|
||||||
request: {
|
|
||||||
baseURL: '={{$credentials.baseUrl}}',
|
|
||||||
url: '/seclore/drm/1.0/auth/login',
|
|
||||||
method: 'POST',
|
|
||||||
body: {
|
|
||||||
tenantId: '={{$credentials.tenantId}}',
|
|
||||||
tenantSecret: '={{$credentials.tenantSecret}}',
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
import { ICredentialTestRequest, ICredentialType, INodeProperties, Icon } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export class SecloreProtectApi implements ICredentialType {
|
||||||
|
name = 'secloreProtectApi';
|
||||||
|
displayName = 'Seclore Protect API';
|
||||||
|
documentationUrl = 'https://docs.seclore.com/';
|
||||||
|
icon: Icon = {
|
||||||
|
light: 'file:../icons/SecloreProtect.light.svg',
|
||||||
|
dark: 'file:../icons/SecloreProtect.dark.svg',
|
||||||
|
};
|
||||||
|
properties: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Base URL',
|
||||||
|
name: 'baseUrl',
|
||||||
|
type: 'string',
|
||||||
|
default: 'https://api.seclore.com',
|
||||||
|
placeholder: 'https://api.seclore.com',
|
||||||
|
description: 'The base URL of your Seclore API instance',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tenant ID',
|
||||||
|
name: 'tenantId',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
placeholder: 'your-tenant-id',
|
||||||
|
description: 'Your Seclore tenant ID',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Tenant Secret',
|
||||||
|
name: 'tenantSecret',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: {
|
||||||
|
password: true,
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
description: 'Your Seclore tenant secret',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Optional: Add credential test
|
||||||
|
test: ICredentialTestRequest = {
|
||||||
|
request: {
|
||||||
|
baseURL: '={{$credentials.baseUrl}}',
|
||||||
|
url: '/seclore/drm/1.0/auth/login',
|
||||||
|
method: 'POST',
|
||||||
|
body: {
|
||||||
|
tenantId: '={{$credentials.tenantId}}',
|
||||||
|
tenantSecret: '={{$credentials.tenantSecret}}',
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 506 B After Width: | Height: | Size: 506 B |
|
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1920 1080">
|
||||||
|
<defs>
|
||||||
|
<style>
|
||||||
|
.cls-1 {
|
||||||
|
fill: #e6244e;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</defs>
|
||||||
|
<path class="cls-1" d="M1410.69,1080H336.23v-225.18h983.63v-196.85H538.82c-135.08-7.21-202.59-53.13-202.59-137.81V128.33C336.23,49.07,403.68,6.3,538.62,0H1583.77V230.56H596.13v199.54h813.03c108.4,0,166.61,37.75,174.6,113.27v404.49c-8,88.11-65.68,132.14-173.08,132.14Z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 506 B |
|
|
@ -1,3 +0,0 @@
|
||||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0165 0C8.94791 0 0 9.01388 0 20.1653C0 29.0792 5.73324 36.6246 13.6868 39.2952C14.6812 39.496 15.0454 38.8613 15.0454 38.3274C15.0454 37.8599 15.0126 36.2575 15.0126 34.5879C9.4445 35.79 8.28498 32.1841 8.28498 32.1841C7.39015 29.847 6.06429 29.2463 6.06429 29.2463C4.24185 28.011 6.19704 28.011 6.19704 28.011C8.21861 28.1446 9.27938 30.081 9.27938 30.081C11.0686 33.1522 13.9518 32.2844 15.1118 31.7502C15.2773 30.4481 15.8079 29.5467 16.3713 29.046C11.9303 28.5785 7.25781 26.8425 7.25781 19.0967C7.25781 16.8932 8.05267 15.0905 9.31216 13.6884C9.11344 13.1877 8.41732 11.1174 9.51128 8.34644C9.51128 8.34644 11.2014 7.81217 15.0122 10.4164C16.6438 9.97495 18.3263 9.7504 20.0165 9.74851C21.7067 9.74851 23.4295 9.98246 25.0205 10.4164C28.8317 7.81217 30.5218 8.34644 30.5218 8.34644C31.6158 11.1174 30.9192 13.1877 30.7205 13.6884C32.0132 15.0905 32.7753 16.8932 32.7753 19.0967C32.7753 26.8425 28.1028 28.5449 23.6287 29.046C24.358 29.6802 24.9873 30.882 24.9873 32.7851C24.9873 35.4893 24.9545 37.6596 24.9545 38.327C24.9545 38.8613 25.3192 39.496 26.3132 39.2956C34.2667 36.6242 39.9999 29.0792 39.9999 20.1653C40.0327 9.01388 31.052 0 20.0165 0Z" fill="white"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1,3 +0,0 @@
|
||||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0165 0C8.94791 0 0 9.01388 0 20.1653C0 29.0792 5.73324 36.6246 13.6868 39.2952C14.6812 39.496 15.0454 38.8613 15.0454 38.3274C15.0454 37.8599 15.0126 36.2575 15.0126 34.5879C9.4445 35.79 8.28498 32.1841 8.28498 32.1841C7.39015 29.847 6.06429 29.2463 6.06429 29.2463C4.24185 28.011 6.19704 28.011 6.19704 28.011C8.21861 28.1446 9.27938 30.081 9.27938 30.081C11.0686 33.1522 13.9518 32.2844 15.1118 31.7502C15.2773 30.4481 15.8079 29.5467 16.3713 29.046C11.9303 28.5785 7.25781 26.8425 7.25781 19.0967C7.25781 16.8932 8.05267 15.0905 9.31216 13.6884C9.11344 13.1877 8.41732 11.1174 9.51128 8.34644C9.51128 8.34644 11.2014 7.81217 15.0122 10.4164C16.6438 9.97495 18.3263 9.7504 20.0165 9.74851C21.7067 9.74851 23.4295 9.98246 25.0205 10.4164C28.8317 7.81217 30.5218 8.34644 30.5218 8.34644C31.6158 11.1174 30.9192 13.1877 30.7205 13.6884C32.0132 15.0905 32.7753 16.8932 32.7753 19.0967C32.7753 26.8425 28.1028 28.5449 23.6287 29.046C24.358 29.6802 24.9873 30.882 24.9873 32.7851C24.9873 35.4893 24.9545 37.6596 24.9545 38.327C24.9545 38.8613 25.3192 39.496 26.3132 39.2956C34.2667 36.6242 39.9999 29.0792 39.9999 20.1653C40.0327 9.01388 31.052 0 20.0165 0Z" fill="#24292F"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -12,7 +12,8 @@ export class SecloreProtect implements INodeType {
|
||||||
description: INodeTypeDescription = {
|
description: INodeTypeDescription = {
|
||||||
displayName: 'Seclore Protect',
|
displayName: 'Seclore Protect',
|
||||||
name: 'secloreProtect',
|
name: 'secloreProtect',
|
||||||
icon: 'file:SecloreProtect.svg',
|
icon: 'file:../../icons/SecloreProtect.light.svg',
|
||||||
|
usableAsTool: true, // TODO: make it false/ don't allow it to be used as a tool
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: 1,
|
version: 1,
|
||||||
subtitle: '={{$parameter["operation"]}}',
|
subtitle: '={{$parameter["operation"]}}',
|
||||||
|
|
@ -39,7 +40,7 @@ export class SecloreProtect implements INodeType {
|
||||||
name: 'Protect File with HotFolder',
|
name: 'Protect File with HotFolder',
|
||||||
value: 'protectWithHotFolder',
|
value: 'protectWithHotFolder',
|
||||||
description: 'Protect a file using HotFolder ID configuration',
|
description: 'Protect a file using HotFolder ID configuration',
|
||||||
action: 'Protect file with HotFolder',
|
action: 'Protect file with hotfolder',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
default: 'protectWithHotFolder',
|
default: 'protectWithHotFolder',
|
||||||
|
|
@ -129,6 +130,8 @@ export class SecloreProtect implements INodeType {
|
||||||
const operation = this.getNodeParameter('operation', 0) as string;
|
const operation = this.getNodeParameter('operation', 0) as string;
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
console.log('Data');
|
||||||
|
console.log(items[i]);
|
||||||
try {
|
try {
|
||||||
if (operation === 'protectWithHotFolder') {
|
if (operation === 'protectWithHotFolder') {
|
||||||
// Get parameters for this item
|
// Get parameters for this item
|
||||||
|
|
@ -148,10 +151,16 @@ export class SecloreProtect implements INodeType {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('assertBinaryData');
|
||||||
// Get input binary data
|
// Get input binary data
|
||||||
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
|
||||||
|
|
||||||
|
console.log('getBinaryDataBuffer');
|
||||||
const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
const fileBuffer = await this.helpers.getBinaryDataBuffer(i, binaryPropertyName);
|
||||||
|
|
||||||
|
console.log('fileBuffer', fileBuffer);
|
||||||
|
console.log('binaryData.fileName', binaryData.fileName);
|
||||||
|
|
||||||
// Upload the file first
|
// Upload the file first
|
||||||
const uploadResult = await fileService.uploadFile(
|
const uploadResult = await fileService.uploadFile(
|
||||||
new Uint8Array(fileBuffer),
|
new Uint8Array(fileBuffer),
|
||||||
|
|
@ -160,6 +169,8 @@ export class SecloreProtect implements INodeType {
|
||||||
retryCount,
|
retryCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('File upload response', uploadResult);
|
||||||
|
|
||||||
// Protect the uploaded file with HotFolder
|
// Protect the uploaded file with HotFolder
|
||||||
const protectResult = await fileService.protectWithHotFolder(
|
const protectResult = await fileService.protectWithHotFolder(
|
||||||
{
|
{
|
||||||
|
|
@ -170,6 +181,8 @@ export class SecloreProtect implements INodeType {
|
||||||
retryCount,
|
retryCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('File protect response', protectResult);
|
||||||
|
|
||||||
// Download the protected file
|
// Download the protected file
|
||||||
const protectedFileData = await fileService.downloadFile(
|
const protectedFileData = await fileService.downloadFile(
|
||||||
protectResult.fileStorageId,
|
protectResult.fileStorageId,
|
||||||
|
|
@ -177,6 +190,8 @@ export class SecloreProtect implements INodeType {
|
||||||
retryCount,
|
retryCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
console.log('Protected file data', protectedFileData);
|
||||||
|
|
||||||
// Create output binary data
|
// Create output binary data
|
||||||
const outputBinaryData = await this.helpers.prepareBinaryData(
|
const outputBinaryData = await this.helpers.prepareBinaryData(
|
||||||
Buffer.from(protectedFileData),
|
Buffer.from(protectedFileData),
|
||||||
|
|
|
||||||
|
|
@ -1,367 +1,387 @@
|
||||||
import { IExecuteFunctions, IHttpRequestOptions } from 'n8n-workflow';
|
import { IExecuteFunctions, IHttpRequestOptions, NodeApiError } from 'n8n-workflow';
|
||||||
import { ILoginRequest, ILoginResponse, IRefreshTokenRequest } from './Interfaces/LoginInterfaces';
|
|
||||||
import { IErrorResponse } from './Interfaces/ErrorInterfaces';
|
import { IErrorResponse } from './Interfaces/ErrorInterfaces';
|
||||||
import { IProtectWithExternalRefIdRequest, IProtectWithExternalRefIdResponse, IProtectWithFileIdRequest, IProtectWithFileIdResponse, IProtectWithHotFolderRequest, IProtectWithHotFolderResponse } from './Interfaces/ProtectInterfaces';
|
|
||||||
import FormData from 'form-data';
|
|
||||||
import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
|
|
||||||
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
|
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
|
||||||
|
import { ILoginRequest, ILoginResponse, IRefreshTokenRequest } from './Interfaces/LoginInterfaces';
|
||||||
|
import {
|
||||||
|
IProtectWithExternalRefIdRequest,
|
||||||
|
IProtectWithExternalRefIdResponse,
|
||||||
|
IProtectWithFileIdRequest,
|
||||||
|
IProtectWithFileIdResponse,
|
||||||
|
IProtectWithHotFolderRequest,
|
||||||
|
IProtectWithHotFolderResponse,
|
||||||
|
} from './Interfaces/ProtectInterfaces';
|
||||||
|
import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
|
||||||
|
|
||||||
export class SecloreDRMApiService {
|
export class SecloreDRMApiService {
|
||||||
private baseUrl: string;
|
private baseUrl: string;
|
||||||
|
|
||||||
constructor(private context: IExecuteFunctions, baseUrl: string) {
|
constructor(
|
||||||
this.baseUrl = baseUrl;
|
private context: IExecuteFunctions,
|
||||||
}
|
baseUrl: string,
|
||||||
|
) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Common error handler for HTTP responses
|
* Common error handler for HTTP responses
|
||||||
* @param error - The error object from httpRequest
|
* @param error - The error object from httpRequest
|
||||||
* @param customMessages - Optional custom error messages for specific status codes
|
* @param customMessages - Optional custom error messages for specific status codes
|
||||||
*/
|
*/
|
||||||
private handleHttpError(error: any, customMessages?: { [statusCode: number]: string }): never {
|
private handleHttpError(
|
||||||
const statusCode = error.statusCode;
|
error: NodeApiError,
|
||||||
const errorResponse = error.response?.body as IErrorResponse;
|
customMessages?: { [statusCode: number]: string },
|
||||||
|
): never {
|
||||||
|
const statusCode: number = parseInt(error.httpCode ?? '0');
|
||||||
|
const errorResponse = error.errorResponse as unknown as IErrorResponse;
|
||||||
|
|
||||||
if (customMessages && customMessages[statusCode]) {
|
if (customMessages && customMessages[statusCode]) {
|
||||||
throw new Error(`${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`);
|
throw new Error(
|
||||||
}
|
`${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Default error handling
|
// Default error handling
|
||||||
switch (statusCode) {
|
switch (statusCode) {
|
||||||
case 400:
|
case 400:
|
||||||
throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
|
throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
|
||||||
case 401:
|
case 401:
|
||||||
throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
|
throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
|
||||||
case 413:
|
case 413:
|
||||||
throw new Error(`Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`);
|
throw new Error(
|
||||||
case 500:
|
`Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`,
|
||||||
throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
|
);
|
||||||
default:
|
case 500:
|
||||||
throw error;
|
throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
|
||||||
}
|
default:
|
||||||
}
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
|
* Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
|
||||||
* Upon successful login, all the existing previous tokens for that tenant will be invalidated.
|
* Upon successful login, all the existing previous tokens for that tenant will be invalidated.
|
||||||
*
|
*
|
||||||
* @param tenantId - The tenant ID
|
* @param tenantId - The tenant ID
|
||||||
* @param tenantSecret - The tenant secret
|
* @param tenantSecret - The tenant secret
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<ILoginResponse> - Access token and refresh token
|
* @returns Promise<ILoginResponse> - Access token and refresh token
|
||||||
* @throws Error on authentication failure or server error
|
* @throws Error on authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async login(tenantId: string, tenantSecret: string, correlationId?: string): Promise<ILoginResponse> {
|
async login(
|
||||||
const requestBody: ILoginRequest = {
|
tenantId: string,
|
||||||
tenantId,
|
tenantSecret: string,
|
||||||
tenantSecret,
|
correlationId?: string,
|
||||||
};
|
): Promise<ILoginResponse> {
|
||||||
|
const requestBody: ILoginRequest = {
|
||||||
|
tenantId,
|
||||||
|
tenantSecret,
|
||||||
|
};
|
||||||
|
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
|
url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
|
||||||
headers,
|
headers,
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as ILoginResponse;
|
return response as ILoginResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
|
* Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
|
||||||
* Upon successful response, all the previous existing Access Tokens and Refresh Tokens of that tenant will be invalidated.
|
* Upon successful response, all the previous existing Access Tokens and Refresh Tokens of that tenant will be invalidated.
|
||||||
*
|
*
|
||||||
* @param refreshToken - The existing valid refresh token
|
* @param refreshToken - The existing valid refresh token
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<ILoginResponse> - New access token and refresh token
|
* @returns Promise<ILoginResponse> - New access token and refresh token
|
||||||
* @throws Error on authentication failure or server error
|
* @throws Error on authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async refreshToken(refreshToken: string, correlationId?: string): Promise<ILoginResponse> {
|
async refreshToken(refreshToken: string, correlationId?: string): Promise<ILoginResponse> {
|
||||||
const requestBody: IRefreshTokenRequest = {
|
const requestBody: IRefreshTokenRequest = {
|
||||||
refreshToken,
|
refreshToken,
|
||||||
};
|
};
|
||||||
|
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
|
url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
|
||||||
headers,
|
headers,
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as ILoginResponse;
|
return response as ILoginResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error, { 401: 'Unauthorized' });
|
this.handleHttpError(error as NodeApiError, { 401: 'Unauthorized' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
|
* Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
|
||||||
*
|
*
|
||||||
* @param protectRequest - The protection request details
|
* @param protectRequest - The protection request details
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<IProtectWithExternalRefIdResponse> - File storage ID and Seclore file ID
|
* @returns Promise<IProtectWithExternalRefIdResponse> - File storage ID and Seclore file ID
|
||||||
* @throws Error on bad request, authentication failure or server error
|
* @throws Error on bad request, authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async protectWithExternalRefId(
|
async protectWithExternalRefId(
|
||||||
protectRequest: IProtectWithExternalRefIdRequest,
|
protectRequest: IProtectWithExternalRefIdRequest,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<IProtectWithExternalRefIdResponse> {
|
): Promise<IProtectWithExternalRefIdResponse> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
|
||||||
headers,
|
headers,
|
||||||
body: protectRequest,
|
body: protectRequest,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as IProtectWithExternalRefIdResponse;
|
return response as IProtectWithExternalRefIdResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
|
* Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
|
||||||
*
|
*
|
||||||
* @param protectRequest - The protection request details with existing protected file ID
|
* @param protectRequest - The protection request details with existing protected file ID
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<IProtectWithFileIdResponse> - File storage ID and Seclore file ID
|
* @returns Promise<IProtectWithFileIdResponse> - File storage ID and Seclore file ID
|
||||||
* @throws Error on bad request, authentication failure or server error
|
* @throws Error on bad request, authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async protectWithFileId(
|
async protectWithFileId(
|
||||||
protectRequest: IProtectWithFileIdRequest,
|
protectRequest: IProtectWithFileIdRequest,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<IProtectWithFileIdResponse> {
|
): Promise<IProtectWithFileIdResponse> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
|
||||||
headers,
|
headers,
|
||||||
body: protectRequest,
|
body: protectRequest,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as IProtectWithFileIdResponse;
|
return response as IProtectWithFileIdResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
|
* Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
|
||||||
*
|
*
|
||||||
* @param protectRequest - The protection request details with hotfolder ID
|
* @param protectRequest - The protection request details with hotfolder ID
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<IProtectWithHotFolderResponse> - File storage ID and Seclore file ID
|
* @returns Promise<IProtectWithHotFolderResponse> - File storage ID and Seclore file ID
|
||||||
* @throws Error on bad request, authentication failure or server error
|
* @throws Error on bad request, authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async protectWithHotFolder(
|
async protectWithHotFolder(
|
||||||
protectRequest: IProtectWithHotFolderRequest,
|
protectRequest: IProtectWithHotFolderRequest,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<IProtectWithHotFolderResponse> {
|
): Promise<IProtectWithHotFolderResponse> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
|
||||||
headers,
|
headers,
|
||||||
body: protectRequest,
|
body: protectRequest,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as IProtectWithHotFolderResponse;
|
return response as IProtectWithHotFolderResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unprotects file with PS configured against the logged in Tenant in application.
|
* Unprotects file with PS configured against the logged in Tenant in application.
|
||||||
*
|
*
|
||||||
* @param unprotectRequest - The unprotect request details with file storage ID
|
* @param unprotectRequest - The unprotect request details with file storage ID
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<IUnprotectResponse> - File storage ID of unprotected file
|
* @returns Promise<IUnprotectResponse> - File storage ID of unprotected file
|
||||||
* @throws Error on bad request, authentication failure or server error
|
* @throws Error on bad request, authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async unprotect(
|
async unprotect(
|
||||||
unprotectRequest: IUnprotectRequest,
|
unprotectRequest: IUnprotectRequest,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<IUnprotectResponse> {
|
): Promise<IUnprotectResponse> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
|
url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
|
||||||
headers,
|
headers,
|
||||||
body: unprotectRequest,
|
body: unprotectRequest,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as IUnprotectResponse;
|
return response as IUnprotectResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new file to the file storage for currently logged in Tenant.
|
* Adds a new file to the file storage for currently logged in Tenant.
|
||||||
*
|
*
|
||||||
* @param fileBuffer - The file buffer data
|
* @param fileBuffer - The file buffer data
|
||||||
* @param fileName - The name of the file
|
* @param fileName - The name of the file
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<IFileUploadResponse> - File storage details including file ID and metadata
|
* @returns Promise<IFileUploadResponse> - File storage details including file ID and metadata
|
||||||
* @throws Error on authentication failure, payload too large, or server error
|
* @throws Error on authentication failure, payload too large, or server error
|
||||||
*/
|
*/
|
||||||
async uploadFile(
|
async uploadFile(
|
||||||
fileBuffer: Uint8Array,
|
fileBuffer: Uint8Array,
|
||||||
fileName: string,
|
fileName: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<IFileUploadResponse> {
|
): Promise<IFileUploadResponse> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create FormData for multipart/form-data upload
|
// Create FormData for multipart/form-data upload
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', fileBuffer, fileName);
|
const file = new Blob([fileBuffer], { type: 'application/octet-stream' });
|
||||||
|
formData.append('file', file, fileName);
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
|
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
|
||||||
headers,
|
headers,
|
||||||
body: formData,
|
body: formData,
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
return response as IFileUploadResponse;
|
return response as IFileUploadResponse;
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
this.handleHttpError(error);
|
this.handleHttpError(error as NodeApiError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Downloads file with fileStorageId from file storage of currently logged in Tenant.
|
* Downloads file with fileStorageId from file storage of currently logged in Tenant.
|
||||||
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
||||||
*
|
*
|
||||||
* @param fileStorageId - Storage ID of the file to be retrieved
|
* @param fileStorageId - Storage ID of the file to be retrieved
|
||||||
* @param accessToken - JWT access token for authorization
|
* @param accessToken - JWT access token for authorization
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
* @returns Promise<Uint8Array> - The downloaded file data
|
* @returns Promise<Uint8Array> - The downloaded file data
|
||||||
* @throws Error on authentication failure or server error
|
* @throws Error on authentication failure or server error
|
||||||
*/
|
*/
|
||||||
async downloadFile(
|
async downloadFile(
|
||||||
fileStorageId: string,
|
fileStorageId: string,
|
||||||
accessToken: string,
|
accessToken: string,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
const headers: { [key: string]: string } = {
|
const headers: { [key: string]: string } = {
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
Authorization: `Bearer ${accessToken}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add correlation ID if provided
|
// Add correlation ID if provided
|
||||||
if (correlationId) {
|
if (correlationId) {
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const options: IHttpRequestOptions = {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
|
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
|
||||||
headers,
|
headers,
|
||||||
encoding: 'arraybuffer',
|
encoding: 'arraybuffer',
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
|
||||||
return new Uint8Array(response as ArrayBuffer);
|
|
||||||
} catch (error: any) {
|
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
|
return new Uint8Array(response as ArrayBuffer);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,298 +1,303 @@
|
||||||
import { IExecuteFunctions } from 'n8n-workflow';
|
import { IExecuteFunctions } from 'n8n-workflow';
|
||||||
import { SecloreDRMApiService } from './SecloreDRMApiService';
|
|
||||||
import { IProtectWithExternalRefIdRequest, IProtectWithExternalRefIdResponse, IProtectWithFileIdRequest, IProtectWithFileIdResponse, IProtectWithHotFolderRequest, IProtectWithHotFolderResponse } from './Interfaces/ProtectInterfaces';
|
|
||||||
import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
|
|
||||||
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
|
import { IFileUploadResponse } from './Interfaces/FileStorageInterfaces';
|
||||||
|
import {
|
||||||
|
IProtectWithExternalRefIdRequest,
|
||||||
|
IProtectWithExternalRefIdResponse,
|
||||||
|
IProtectWithFileIdRequest,
|
||||||
|
IProtectWithFileIdResponse,
|
||||||
|
IProtectWithHotFolderRequest,
|
||||||
|
IProtectWithHotFolderResponse,
|
||||||
|
} from './Interfaces/ProtectInterfaces';
|
||||||
|
import { IUnprotectRequest, IUnprotectResponse } from './Interfaces/UnprotectInterfaces';
|
||||||
|
import { SecloreDRMApiService } from './SecloreDRMApiService';
|
||||||
|
|
||||||
export class SecloreDRMFileService {
|
export class SecloreDRMFileService {
|
||||||
private apiService: SecloreDRMApiService;
|
private apiService: SecloreDRMApiService;
|
||||||
private tenantId: string;
|
private tenantId: string;
|
||||||
private tenantSecret: string;
|
private tenantSecret: string;
|
||||||
private accessToken?: string;
|
private accessToken?: string;
|
||||||
private refreshToken?: string;
|
private refreshToken?: string;
|
||||||
private tokenExpiry?: Date;
|
private tokenExpiry?: Date;
|
||||||
private refreshPromise?: Promise<void>;
|
private refreshPromise?: Promise<void>;
|
||||||
private loginPromise?: Promise<void>;
|
private loginPromise?: Promise<void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
context: IExecuteFunctions,
|
context: IExecuteFunctions,
|
||||||
baseUrl: string,
|
baseUrl: string,
|
||||||
tenantId: string,
|
tenantId: string,
|
||||||
tenantSecret: string,
|
tenantSecret: string,
|
||||||
private defaultRetryCount: number = 3
|
private defaultRetryCount: number = 3,
|
||||||
) {
|
) {
|
||||||
this.apiService = new SecloreDRMApiService(context, baseUrl);
|
this.apiService = new SecloreDRMApiService(context, baseUrl);
|
||||||
this.tenantId = tenantId;
|
this.tenantId = tenantId;
|
||||||
this.tenantSecret = tenantSecret;
|
this.tenantSecret = tenantSecret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensures we have a valid access token, logging in if necessary
|
* Ensures we have a valid access token, logging in if necessary
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async ensureAuthenticated(correlationId?: string): Promise<void> {
|
private async ensureAuthenticated(correlationId?: string): Promise<void> {
|
||||||
// If we don't have a token or it's expired, login
|
// If we don't have a token or it's expired, login
|
||||||
if (!this.accessToken || (this.tokenExpiry && new Date() >= this.tokenExpiry)) {
|
if (!this.accessToken || (this.tokenExpiry && new Date() >= this.tokenExpiry)) {
|
||||||
// If there's already a login in progress, wait for it
|
// If there's already a login in progress, wait for it
|
||||||
if (this.loginPromise) {
|
if (this.loginPromise) {
|
||||||
await this.loginPromise;
|
await this.loginPromise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start login and store the promise
|
// Start login and store the promise
|
||||||
this.loginPromise = this.login(correlationId);
|
this.loginPromise = this.login(correlationId);
|
||||||
try {
|
try {
|
||||||
await this.loginPromise;
|
await this.loginPromise;
|
||||||
} finally {
|
} finally {
|
||||||
this.loginPromise = undefined;
|
this.loginPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs login and stores tokens
|
* Performs login and stores tokens
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async login(correlationId?: string): Promise<void> {
|
private async login(correlationId?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const loginResponse = await this.apiService.login(
|
const loginResponse = await this.apiService.login(
|
||||||
this.tenantId,
|
this.tenantId,
|
||||||
this.tenantSecret,
|
this.tenantSecret,
|
||||||
correlationId
|
correlationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.accessToken = loginResponse.accessToken;
|
this.accessToken = loginResponse.accessToken;
|
||||||
this.refreshToken = loginResponse.refreshToken;
|
this.refreshToken = loginResponse.refreshToken;
|
||||||
|
|
||||||
// Set token expiry to 50 minutes from now (assuming 1 hour token life)
|
// Set token expiry to 50 minutes from now (assuming 1 hour token life)
|
||||||
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.clearTokens();
|
this.clearTokens();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to refresh the access token with concurrency protection
|
* Attempts to refresh the access token with concurrency protection
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async refreshAccessToken(correlationId?: string): Promise<void> {
|
private async refreshAccessToken(correlationId?: string): Promise<void> {
|
||||||
// If there's already a refresh in progress, wait for it
|
// If there's already a refresh in progress, wait for it
|
||||||
if (this.refreshPromise) {
|
if (this.refreshPromise) {
|
||||||
await this.refreshPromise;
|
await this.refreshPromise;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.refreshToken) {
|
if (!this.refreshToken) {
|
||||||
throw new Error('No refresh token available');
|
throw new Error('No refresh token available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start refresh and store the promise
|
// Start refresh and store the promise
|
||||||
this.refreshPromise = this.performTokenRefresh(correlationId);
|
this.refreshPromise = this.performTokenRefresh(correlationId);
|
||||||
try {
|
try {
|
||||||
await this.refreshPromise;
|
await this.refreshPromise;
|
||||||
} finally {
|
} finally {
|
||||||
this.refreshPromise = undefined;
|
this.refreshPromise = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the actual token refresh
|
* Performs the actual token refresh
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async performTokenRefresh(correlationId?: string): Promise<void> {
|
private async performTokenRefresh(correlationId?: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const refreshResponse = await this.apiService.refreshToken(
|
const refreshResponse = await this.apiService.refreshToken(this.refreshToken!, correlationId);
|
||||||
this.refreshToken!,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
|
|
||||||
this.accessToken = refreshResponse.accessToken;
|
this.accessToken = refreshResponse.accessToken;
|
||||||
this.refreshToken = refreshResponse.refreshToken;
|
this.refreshToken = refreshResponse.refreshToken;
|
||||||
|
|
||||||
// Set token expiry to 50 minutes from now
|
// Set token expiry to 50 minutes from now
|
||||||
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.clearTokens();
|
this.clearTokens();
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears stored tokens and pending promises
|
* Clears stored tokens and pending promises
|
||||||
*/
|
*/
|
||||||
private clearTokens(): void {
|
private clearTokens(): void {
|
||||||
this.accessToken = undefined;
|
this.accessToken = undefined;
|
||||||
this.refreshToken = undefined;
|
this.refreshToken = undefined;
|
||||||
this.tokenExpiry = undefined;
|
this.tokenExpiry = undefined;
|
||||||
this.refreshPromise = undefined;
|
this.refreshPromise = undefined;
|
||||||
this.loginPromise = undefined;
|
this.loginPromise = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes an API call with automatic authentication and retry logic
|
* Executes an API call with automatic authentication and retry logic
|
||||||
* @param apiCall - The API call function to execute
|
* @param apiCall - The API call function to execute
|
||||||
* @param retryCount - Number of retries (defaults to class default)
|
* @param retryCount - Number of retries (defaults to class default)
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async executeWithRetry<T>(
|
private async executeWithRetry<T>(
|
||||||
apiCall: (accessToken: string) => Promise<T>,
|
apiCall: (accessToken: string) => Promise<T>,
|
||||||
retryCount: number = this.defaultRetryCount,
|
retryCount: number = this.defaultRetryCount,
|
||||||
correlationId?: string
|
correlationId?: string,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
let lastError: Error;
|
let lastError: Error;
|
||||||
|
|
||||||
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
||||||
try {
|
try {
|
||||||
// Ensure we have a valid token
|
// Ensure we have a valid token
|
||||||
await this.ensureAuthenticated(correlationId);
|
await this.ensureAuthenticated(correlationId);
|
||||||
|
|
||||||
if (!this.accessToken) {
|
if (!this.accessToken) {
|
||||||
throw new Error('Failed to obtain access token');
|
throw new Error('Failed to obtain access token');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the API call
|
// Execute the API call
|
||||||
return await apiCall(this.accessToken);
|
return await apiCall(this.accessToken);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
lastError = error as Error;
|
||||||
|
// If it's an authentication error and we have retries left, try to refresh token
|
||||||
|
if ((error as Error).message.includes('Unauthorized') && attempt < retryCount) {
|
||||||
|
try {
|
||||||
|
await this.refreshAccessToken(correlationId);
|
||||||
|
continue; // Retry with new token
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(error);
|
||||||
|
// If refresh fails, clear tokens and try full login on next attempt
|
||||||
|
this.clearTokens();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error: any) {
|
// If it's the last attempt or not an auth error, throw
|
||||||
lastError = error;
|
if (attempt === retryCount) {
|
||||||
|
throw lastError;
|
||||||
|
}
|
||||||
|
|
||||||
// If it's an authentication error and we have retries left, try to refresh token
|
// Wait before retry (exponential backoff)
|
||||||
if (error.message.includes('Unauthorized') && attempt < retryCount) {
|
await new Promise((resolve) => {
|
||||||
try {
|
const delay = Math.pow(2, attempt) * 1000;
|
||||||
await this.refreshAccessToken(correlationId);
|
// Use a simple delay implementation
|
||||||
continue; // Retry with new token
|
const start = Date.now();
|
||||||
} catch (refreshError) {
|
while (Date.now() - start < delay) {
|
||||||
// If refresh fails, clear tokens and try full login on next attempt
|
// Busy wait for delay
|
||||||
this.clearTokens();
|
}
|
||||||
}
|
resolve(undefined);
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If it's the last attempt or not an auth error, throw
|
throw lastError!;
|
||||||
if (attempt === retryCount) {
|
}
|
||||||
throw lastError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait before retry (exponential backoff)
|
/**
|
||||||
await new Promise(resolve => {
|
* Protect file using external identifier with automatic authentication and retry
|
||||||
const delay = Math.pow(2, attempt) * 1000;
|
*/
|
||||||
// Use a simple delay implementation
|
async protectWithExternalRefId(
|
||||||
const start = Date.now();
|
protectRequest: IProtectWithExternalRefIdRequest,
|
||||||
while (Date.now() - start < delay) {
|
correlationId?: string,
|
||||||
// Busy wait for delay
|
retryCount?: number,
|
||||||
}
|
): Promise<IProtectWithExternalRefIdResponse> {
|
||||||
resolve(undefined);
|
return this.executeWithRetry(
|
||||||
});
|
(accessToken) =>
|
||||||
}
|
this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
|
||||||
}
|
retryCount,
|
||||||
|
correlationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
throw lastError!;
|
/**
|
||||||
}
|
* Protect file using existing protected file ID with automatic authentication and retry
|
||||||
|
*/
|
||||||
|
async protectWithFileId(
|
||||||
|
protectRequest: IProtectWithFileIdRequest,
|
||||||
|
correlationId?: string,
|
||||||
|
retryCount?: number,
|
||||||
|
): Promise<IProtectWithFileIdResponse> {
|
||||||
|
return this.executeWithRetry(
|
||||||
|
(accessToken) =>
|
||||||
|
this.apiService.protectWithFileId(protectRequest, accessToken, correlationId),
|
||||||
|
retryCount,
|
||||||
|
correlationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using external identifier with automatic authentication and retry
|
* Protect file using HotFolder ID with automatic authentication and retry
|
||||||
*/
|
*/
|
||||||
async protectWithExternalRefId(
|
async protectWithHotFolder(
|
||||||
protectRequest: IProtectWithExternalRefIdRequest,
|
protectRequest: IProtectWithHotFolderRequest,
|
||||||
correlationId?: string,
|
correlationId?: string,
|
||||||
retryCount?: number
|
retryCount?: number,
|
||||||
): Promise<IProtectWithExternalRefIdResponse> {
|
): Promise<IProtectWithHotFolderResponse> {
|
||||||
return this.executeWithRetry(
|
return this.executeWithRetry(
|
||||||
(accessToken) => this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
|
(accessToken) =>
|
||||||
retryCount,
|
this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
|
||||||
correlationId
|
retryCount,
|
||||||
);
|
correlationId,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using existing protected file ID with automatic authentication and retry
|
* Unprotect file with automatic authentication and retry
|
||||||
*/
|
*/
|
||||||
async protectWithFileId(
|
async unprotect(
|
||||||
protectRequest: IProtectWithFileIdRequest,
|
unprotectRequest: IUnprotectRequest,
|
||||||
correlationId?: string,
|
correlationId?: string,
|
||||||
retryCount?: number
|
retryCount?: number,
|
||||||
): Promise<IProtectWithFileIdResponse> {
|
): Promise<IUnprotectResponse> {
|
||||||
return this.executeWithRetry(
|
return this.executeWithRetry(
|
||||||
(accessToken) => this.apiService.protectWithFileId(protectRequest, accessToken, correlationId),
|
(accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
|
||||||
retryCount,
|
retryCount,
|
||||||
correlationId
|
correlationId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using HotFolder ID with automatic authentication and retry
|
* Upload file with automatic authentication and retry
|
||||||
*/
|
*/
|
||||||
async protectWithHotFolder(
|
async uploadFile(
|
||||||
protectRequest: IProtectWithHotFolderRequest,
|
fileBuffer: Uint8Array,
|
||||||
correlationId?: string,
|
fileName: string,
|
||||||
retryCount?: number
|
correlationId?: string,
|
||||||
): Promise<IProtectWithHotFolderResponse> {
|
retryCount?: number,
|
||||||
return this.executeWithRetry(
|
): Promise<IFileUploadResponse> {
|
||||||
(accessToken) => this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
|
return this.executeWithRetry(
|
||||||
retryCount,
|
(accessToken) => this.apiService.uploadFile(fileBuffer, fileName, accessToken, correlationId),
|
||||||
correlationId
|
retryCount,
|
||||||
);
|
correlationId,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unprotect file with automatic authentication and retry
|
* Download file with automatic authentication and retry
|
||||||
*/
|
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
||||||
async unprotect(
|
*/
|
||||||
unprotectRequest: IUnprotectRequest,
|
async downloadFile(
|
||||||
correlationId?: string,
|
fileStorageId: string,
|
||||||
retryCount?: number
|
correlationId?: string,
|
||||||
): Promise<IUnprotectResponse> {
|
retryCount?: number,
|
||||||
return this.executeWithRetry(
|
): Promise<Uint8Array> {
|
||||||
(accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
|
return this.executeWithRetry(
|
||||||
retryCount,
|
(accessToken) => this.apiService.downloadFile(fileStorageId, accessToken, correlationId),
|
||||||
correlationId
|
retryCount,
|
||||||
);
|
correlationId,
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload file with automatic authentication and retry
|
* Get current access token (for debugging/monitoring)
|
||||||
*/
|
*/
|
||||||
async uploadFile(
|
getAccessToken(): string | undefined {
|
||||||
fileBuffer: Uint8Array,
|
return this.accessToken;
|
||||||
fileName: string,
|
}
|
||||||
correlationId?: string,
|
|
||||||
retryCount?: number
|
|
||||||
): Promise<IFileUploadResponse> {
|
|
||||||
return this.executeWithRetry(
|
|
||||||
(accessToken) => this.apiService.uploadFile(fileBuffer, fileName, accessToken, correlationId),
|
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Download file with automatic authentication and retry
|
|
||||||
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
|
||||||
*/
|
|
||||||
async downloadFile(
|
|
||||||
fileStorageId: string,
|
|
||||||
correlationId?: string,
|
|
||||||
retryCount?: number
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
return this.executeWithRetry(
|
|
||||||
(accessToken) => this.apiService.downloadFile(fileStorageId, accessToken, correlationId),
|
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current access token (for debugging/monitoring)
|
|
||||||
*/
|
|
||||||
getAccessToken(): string | undefined {
|
|
||||||
return this.accessToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if currently authenticated
|
|
||||||
*/
|
|
||||||
isAuthenticated(): boolean {
|
|
||||||
return !!this.accessToken && (!this.tokenExpiry || new Date() < this.tokenExpiry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if currently authenticated
|
||||||
|
*/
|
||||||
|
isAuthenticated(): boolean {
|
||||||
|
return !!this.accessToken && (!this.tokenExpiry || new Date() < this.tokenExpiry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue