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 {
|
||||||
if (customMessages && customMessages[statusCode]) {
|
const statusCode: number = parseInt(error.httpCode ?? '0');
|
||||||
throw new Error(`${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`);
|
const errorResponse = error.errorResponse as unknown as IErrorResponse;
|
||||||
}
|
|
||||||
|
|
||||||
// Default error handling
|
|
||||||
switch (statusCode) {
|
|
||||||
case 400:
|
|
||||||
throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
|
|
||||||
case 401:
|
|
||||||
throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
|
|
||||||
case 413:
|
|
||||||
throw new Error(`Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`);
|
|
||||||
case 500:
|
|
||||||
throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
|
|
||||||
default:
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (customMessages && customMessages[statusCode]) {
|
||||||
* Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
|
throw new Error(
|
||||||
* Upon successful login, all the existing previous tokens for that tenant will be invalidated.
|
`${customMessages[statusCode]}: ${errorResponse?.errorMessage || 'Unknown error'}`,
|
||||||
*
|
);
|
||||||
* @param tenantId - The tenant ID
|
}
|
||||||
* @param tenantSecret - The tenant secret
|
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
|
||||||
* @returns Promise<ILoginResponse> - Access token and refresh token
|
|
||||||
* @throws Error on authentication failure or server error
|
|
||||||
*/
|
|
||||||
async login(tenantId: string, tenantSecret: string, correlationId?: string): Promise<ILoginResponse> {
|
|
||||||
const requestBody: ILoginRequest = {
|
|
||||||
tenantId,
|
|
||||||
tenantSecret,
|
|
||||||
};
|
|
||||||
|
|
||||||
const headers: { [key: string]: string } = {
|
// Default error handling
|
||||||
'Content-Type': 'application/json',
|
switch (statusCode) {
|
||||||
};
|
case 400:
|
||||||
|
throw new Error(`Bad Request: ${errorResponse?.errorMessage || 'Invalid request data'}`);
|
||||||
|
case 401:
|
||||||
|
throw new Error(`Unauthorized: ${errorResponse?.errorMessage || 'Invalid credentials'}`);
|
||||||
|
case 413:
|
||||||
|
throw new Error(
|
||||||
|
`Payload Too Large: ${errorResponse?.errorMessage || 'File size exceeds limit'}`,
|
||||||
|
);
|
||||||
|
case 500:
|
||||||
|
throw new Error(`Server Error: ${errorResponse?.errorMessage || 'Internal server error'}`);
|
||||||
|
default:
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add correlation ID if provided
|
/**
|
||||||
if (correlationId) {
|
* Login Endpoint to generate Access Token and Refresh Token for JWT Authorization.
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
* Upon successful login, all the existing previous tokens for that tenant will be invalidated.
|
||||||
}
|
*
|
||||||
|
* @param tenantId - The tenant ID
|
||||||
|
* @param tenantSecret - The tenant secret
|
||||||
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
|
* @returns Promise<ILoginResponse> - Access token and refresh token
|
||||||
|
* @throws Error on authentication failure or server error
|
||||||
|
*/
|
||||||
|
async login(
|
||||||
|
tenantId: string,
|
||||||
|
tenantSecret: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<ILoginResponse> {
|
||||||
|
const requestBody: ILoginRequest = {
|
||||||
|
tenantId,
|
||||||
|
tenantSecret,
|
||||||
|
};
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const headers: { [key: string]: string } = {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
|
};
|
||||||
headers,
|
|
||||||
body: requestBody,
|
|
||||||
json: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as ILoginResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
|
method: 'POST',
|
||||||
* Upon successful response, all the previous existing Access Tokens and Refresh Tokens of that tenant will be invalidated.
|
url: `${this.baseUrl}/seclore/drm/1.0/auth/login`,
|
||||||
*
|
headers,
|
||||||
* @param refreshToken - The existing valid refresh token
|
body: requestBody,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
json: true,
|
||||||
* @returns Promise<ILoginResponse> - New access token and refresh token
|
};
|
||||||
* @throws Error on authentication failure or server error
|
|
||||||
*/
|
|
||||||
async refreshToken(refreshToken: string, correlationId?: string): Promise<ILoginResponse> {
|
|
||||||
const requestBody: IRefreshTokenRequest = {
|
|
||||||
refreshToken,
|
|
||||||
};
|
|
||||||
|
|
||||||
const headers: { [key: string]: string } = {
|
try {
|
||||||
'Content-Type': 'application/json',
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
};
|
return response as ILoginResponse;
|
||||||
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add correlation ID if provided
|
/**
|
||||||
if (correlationId) {
|
* Endpoint for generating new Access Token and Refresh Token using an existing valid Refresh Token.
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
* 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 correlationId - Optional request ID for logging purpose
|
||||||
|
* @returns Promise<ILoginResponse> - New access token and refresh token
|
||||||
|
* @throws Error on authentication failure or server error
|
||||||
|
*/
|
||||||
|
async refreshToken(refreshToken: string, correlationId?: string): Promise<ILoginResponse> {
|
||||||
|
const requestBody: IRefreshTokenRequest = {
|
||||||
|
refreshToken,
|
||||||
|
};
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
const headers: { [key: string]: string } = {
|
||||||
method: 'POST',
|
'Content-Type': 'application/json',
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
|
};
|
||||||
headers,
|
|
||||||
body: requestBody,
|
|
||||||
json: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as ILoginResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error, { 401: 'Unauthorized' });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
|
method: 'POST',
|
||||||
*
|
url: `${this.baseUrl}/seclore/drm/1.0/auth/refresh`,
|
||||||
* @param protectRequest - The protection request details
|
headers,
|
||||||
* @param accessToken - JWT access token for authorization
|
body: requestBody,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
json: true,
|
||||||
* @returns Promise<IProtectWithExternalRefIdResponse> - File storage ID and Seclore file ID
|
};
|
||||||
* @throws Error on bad request, authentication failure or server error
|
|
||||||
*/
|
|
||||||
async protectWithExternalRefId(
|
|
||||||
protectRequest: IProtectWithExternalRefIdRequest,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<IProtectWithExternalRefIdResponse> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as ILoginResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError, { 401: 'Unauthorized' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
/**
|
||||||
method: 'POST',
|
* Protect file using external identifier of protected File and HotFolder with PS configured against the logged in Tenant in application.
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
|
*
|
||||||
headers,
|
* @param protectRequest - The protection request details
|
||||||
body: protectRequest,
|
* @param accessToken - JWT access token for authorization
|
||||||
json: true,
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
};
|
* @returns Promise<IProtectWithExternalRefIdResponse> - File storage ID and Seclore file ID
|
||||||
|
* @throws Error on bad request, authentication failure or server error
|
||||||
|
*/
|
||||||
|
async protectWithExternalRefId(
|
||||||
|
protectRequest: IProtectWithExternalRefIdRequest,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<IProtectWithExternalRefIdResponse> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as IProtectWithExternalRefIdResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
|
method: 'POST',
|
||||||
*
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/externalref`,
|
||||||
* @param protectRequest - The protection request details with existing protected file ID
|
headers,
|
||||||
* @param accessToken - JWT access token for authorization
|
body: protectRequest,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
json: true,
|
||||||
* @returns Promise<IProtectWithFileIdResponse> - File storage ID and Seclore file ID
|
};
|
||||||
* @throws Error on bad request, authentication failure or server error
|
|
||||||
*/
|
|
||||||
async protectWithFileId(
|
|
||||||
protectRequest: IProtectWithFileIdRequest,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<IProtectWithFileIdResponse> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as IProtectWithExternalRefIdResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
/**
|
||||||
method: 'POST',
|
* Protects file using File ID of already protected file with PS configured against the logged in Tenant in application.
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
|
*
|
||||||
headers,
|
* @param protectRequest - The protection request details with existing protected file ID
|
||||||
body: protectRequest,
|
* @param accessToken - JWT access token for authorization
|
||||||
json: true,
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
};
|
* @returns Promise<IProtectWithFileIdResponse> - File storage ID and Seclore file ID
|
||||||
|
* @throws Error on bad request, authentication failure or server error
|
||||||
|
*/
|
||||||
|
async protectWithFileId(
|
||||||
|
protectRequest: IProtectWithFileIdRequest,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<IProtectWithFileIdResponse> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as IProtectWithFileIdResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
|
method: 'POST',
|
||||||
*
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/fileid`,
|
||||||
* @param protectRequest - The protection request details with hotfolder ID
|
headers,
|
||||||
* @param accessToken - JWT access token for authorization
|
body: protectRequest,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
json: true,
|
||||||
* @returns Promise<IProtectWithHotFolderResponse> - File storage ID and Seclore file ID
|
};
|
||||||
* @throws Error on bad request, authentication failure or server error
|
|
||||||
*/
|
|
||||||
async protectWithHotFolder(
|
|
||||||
protectRequest: IProtectWithHotFolderRequest,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<IProtectWithHotFolderResponse> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as IProtectWithFileIdResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
/**
|
||||||
method: 'POST',
|
* Protects file using HotFolder ID with PS configured against the logged in Tenant in application.
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
|
*
|
||||||
headers,
|
* @param protectRequest - The protection request details with hotfolder ID
|
||||||
body: protectRequest,
|
* @param accessToken - JWT access token for authorization
|
||||||
json: true,
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
};
|
* @returns Promise<IProtectWithHotFolderResponse> - File storage ID and Seclore file ID
|
||||||
|
* @throws Error on bad request, authentication failure or server error
|
||||||
|
*/
|
||||||
|
async protectWithHotFolder(
|
||||||
|
protectRequest: IProtectWithHotFolderRequest,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<IProtectWithHotFolderResponse> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as IProtectWithHotFolderResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Unprotects file with PS configured against the logged in Tenant in application.
|
method: 'POST',
|
||||||
*
|
url: `${this.baseUrl}/seclore/drm/1.0/protect/hf`,
|
||||||
* @param unprotectRequest - The unprotect request details with file storage ID
|
headers,
|
||||||
* @param accessToken - JWT access token for authorization
|
body: protectRequest,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
json: true,
|
||||||
* @returns Promise<IUnprotectResponse> - File storage ID of unprotected file
|
};
|
||||||
* @throws Error on bad request, authentication failure or server error
|
|
||||||
*/
|
|
||||||
async unprotect(
|
|
||||||
unprotectRequest: IUnprotectRequest,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<IUnprotectResponse> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as IProtectWithHotFolderResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
/**
|
||||||
method: 'POST',
|
* Unprotects file with PS configured against the logged in Tenant in application.
|
||||||
url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
|
*
|
||||||
headers,
|
* @param unprotectRequest - The unprotect request details with file storage ID
|
||||||
body: unprotectRequest,
|
* @param accessToken - JWT access token for authorization
|
||||||
json: true,
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
};
|
* @returns Promise<IUnprotectResponse> - File storage ID of unprotected file
|
||||||
|
* @throws Error on bad request, authentication failure or server error
|
||||||
|
*/
|
||||||
|
async unprotect(
|
||||||
|
unprotectRequest: IUnprotectRequest,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<IUnprotectResponse> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return response as IUnprotectResponse;
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Adds a new file to the file storage for currently logged in Tenant.
|
method: 'POST',
|
||||||
*
|
url: `${this.baseUrl}/seclore/drm/1.0/unprotect`,
|
||||||
* @param fileBuffer - The file buffer data
|
headers,
|
||||||
* @param fileName - The name of the file
|
body: unprotectRequest,
|
||||||
* @param accessToken - JWT access token for authorization
|
json: true,
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
};
|
||||||
* @returns Promise<IFileUploadResponse> - File storage details including file ID and metadata
|
|
||||||
* @throws Error on authentication failure, payload too large, or server error
|
|
||||||
*/
|
|
||||||
async uploadFile(
|
|
||||||
fileBuffer: Uint8Array,
|
|
||||||
fileName: string,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<IFileUploadResponse> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as IUnprotectResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create FormData for multipart/form-data upload
|
/**
|
||||||
const formData = new FormData();
|
* Adds a new file to the file storage for currently logged in Tenant.
|
||||||
formData.append('file', fileBuffer, fileName);
|
*
|
||||||
|
* @param fileBuffer - The file buffer data
|
||||||
|
* @param fileName - The name of the file
|
||||||
|
* @param accessToken - JWT access token for authorization
|
||||||
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
|
* @returns Promise<IFileUploadResponse> - File storage details including file ID and metadata
|
||||||
|
* @throws Error on authentication failure, payload too large, or server error
|
||||||
|
*/
|
||||||
|
async uploadFile(
|
||||||
|
fileBuffer: Uint8Array,
|
||||||
|
fileName: string,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<IFileUploadResponse> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
// Add correlation ID if provided
|
||||||
method: 'POST',
|
if (correlationId) {
|
||||||
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
headers,
|
}
|
||||||
body: formData,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
// Create FormData for multipart/form-data upload
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
const formData = new FormData();
|
||||||
return response as IFileUploadResponse;
|
const file = new Blob([fileBuffer], { type: 'application/octet-stream' });
|
||||||
} catch (error: any) {
|
formData.append('file', file, fileName);
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
const options: IHttpRequestOptions = {
|
||||||
* Downloads file with fileStorageId from file storage of currently logged in Tenant.
|
method: 'POST',
|
||||||
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/upload`,
|
||||||
*
|
headers,
|
||||||
* @param fileStorageId - Storage ID of the file to be retrieved
|
body: formData,
|
||||||
* @param accessToken - JWT access token for authorization
|
};
|
||||||
* @param correlationId - Optional request ID for logging purpose
|
|
||||||
* @returns Promise<Uint8Array> - The downloaded file data
|
|
||||||
* @throws Error on authentication failure or server error
|
|
||||||
*/
|
|
||||||
async downloadFile(
|
|
||||||
fileStorageId: string,
|
|
||||||
accessToken: string,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
const headers: { [key: string]: string } = {
|
|
||||||
'Authorization': `Bearer ${accessToken}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add correlation ID if provided
|
try {
|
||||||
if (correlationId) {
|
const response = await this.context.helpers.httpRequest(options);
|
||||||
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
return response as IFileUploadResponse;
|
||||||
}
|
} catch (error: unknown) {
|
||||||
|
this.handleHttpError(error as NodeApiError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const options: IHttpRequestOptions = {
|
/**
|
||||||
method: 'GET',
|
* Downloads file with fileStorageId from file storage of currently logged in Tenant.
|
||||||
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
|
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
||||||
headers,
|
*
|
||||||
encoding: 'arraybuffer',
|
* @param fileStorageId - Storage ID of the file to be retrieved
|
||||||
};
|
* @param accessToken - JWT access token for authorization
|
||||||
|
* @param correlationId - Optional request ID for logging purpose
|
||||||
|
* @returns Promise<Uint8Array> - The downloaded file data
|
||||||
|
* @throws Error on authentication failure or server error
|
||||||
|
*/
|
||||||
|
async downloadFile(
|
||||||
|
fileStorageId: string,
|
||||||
|
accessToken: string,
|
||||||
|
correlationId?: string,
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
const headers: { [key: string]: string } = {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
// Add correlation ID if provided
|
||||||
const response = await this.context.helpers.httpRequest(options);
|
if (correlationId) {
|
||||||
return new Uint8Array(response as ArrayBuffer);
|
headers['X-SECLORE-CORRELATION-ID'] = correlationId;
|
||||||
} catch (error: any) {
|
}
|
||||||
this.handleHttpError(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const options: IHttpRequestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
url: `${this.baseUrl}/seclore/drm/filestorage/1.0/download/${fileStorageId}`,
|
||||||
|
headers,
|
||||||
|
encoding: 'arraybuffer',
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
|
||||||
this.loginPromise = this.login(correlationId);
|
|
||||||
try {
|
|
||||||
await this.loginPromise;
|
|
||||||
} finally {
|
|
||||||
this.loginPromise = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Start login and store the promise
|
||||||
* Performs login and stores tokens
|
this.loginPromise = this.login(correlationId);
|
||||||
* @param correlationId - Optional correlation ID for logging
|
try {
|
||||||
*/
|
await this.loginPromise;
|
||||||
private async login(correlationId?: string): Promise<void> {
|
} finally {
|
||||||
try {
|
this.loginPromise = undefined;
|
||||||
const loginResponse = await this.apiService.login(
|
}
|
||||||
this.tenantId,
|
}
|
||||||
this.tenantSecret,
|
}
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
|
|
||||||
this.accessToken = loginResponse.accessToken;
|
|
||||||
this.refreshToken = loginResponse.refreshToken;
|
|
||||||
|
|
||||||
// Set token expiry to 50 minutes from now (assuming 1 hour token life)
|
|
||||||
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
|
||||||
} catch (error) {
|
|
||||||
this.clearTokens();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to refresh the access token with concurrency protection
|
* Performs login and stores tokens
|
||||||
* @param correlationId - Optional correlation ID for logging
|
* @param correlationId - Optional correlation ID for logging
|
||||||
*/
|
*/
|
||||||
private async refreshAccessToken(correlationId?: string): Promise<void> {
|
private async login(correlationId?: string): Promise<void> {
|
||||||
// If there's already a refresh in progress, wait for it
|
try {
|
||||||
if (this.refreshPromise) {
|
const loginResponse = await this.apiService.login(
|
||||||
await this.refreshPromise;
|
this.tenantId,
|
||||||
return;
|
this.tenantSecret,
|
||||||
}
|
correlationId,
|
||||||
|
);
|
||||||
|
|
||||||
if (!this.refreshToken) {
|
this.accessToken = loginResponse.accessToken;
|
||||||
throw new Error('No refresh token available');
|
this.refreshToken = loginResponse.refreshToken;
|
||||||
}
|
|
||||||
|
|
||||||
// Start refresh and store the promise
|
// Set token expiry to 50 minutes from now (assuming 1 hour token life)
|
||||||
this.refreshPromise = this.performTokenRefresh(correlationId);
|
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
||||||
try {
|
} catch (error) {
|
||||||
await this.refreshPromise;
|
this.clearTokens();
|
||||||
} finally {
|
throw error;
|
||||||
this.refreshPromise = undefined;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs the actual token refresh
|
* 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 performTokenRefresh(correlationId?: string): Promise<void> {
|
private async refreshAccessToken(correlationId?: string): Promise<void> {
|
||||||
try {
|
// If there's already a refresh in progress, wait for it
|
||||||
const refreshResponse = await this.apiService.refreshToken(
|
if (this.refreshPromise) {
|
||||||
this.refreshToken!,
|
await this.refreshPromise;
|
||||||
correlationId
|
return;
|
||||||
);
|
}
|
||||||
|
|
||||||
this.accessToken = refreshResponse.accessToken;
|
|
||||||
this.refreshToken = refreshResponse.refreshToken;
|
|
||||||
|
|
||||||
// Set token expiry to 50 minutes from now
|
|
||||||
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
|
||||||
} catch (error) {
|
|
||||||
this.clearTokens();
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!this.refreshToken) {
|
||||||
* Clears stored tokens and pending promises
|
throw new Error('No refresh token available');
|
||||||
*/
|
}
|
||||||
private clearTokens(): void {
|
|
||||||
this.accessToken = undefined;
|
|
||||||
this.refreshToken = undefined;
|
|
||||||
this.tokenExpiry = undefined;
|
|
||||||
this.refreshPromise = undefined;
|
|
||||||
this.loginPromise = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Start refresh and store the promise
|
||||||
* Executes an API call with automatic authentication and retry logic
|
this.refreshPromise = this.performTokenRefresh(correlationId);
|
||||||
* @param apiCall - The API call function to execute
|
try {
|
||||||
* @param retryCount - Number of retries (defaults to class default)
|
await this.refreshPromise;
|
||||||
* @param correlationId - Optional correlation ID for logging
|
} finally {
|
||||||
*/
|
this.refreshPromise = undefined;
|
||||||
private async executeWithRetry<T>(
|
}
|
||||||
apiCall: (accessToken: string) => Promise<T>,
|
}
|
||||||
retryCount: number = this.defaultRetryCount,
|
|
||||||
correlationId?: string
|
|
||||||
): Promise<T> {
|
|
||||||
let lastError: Error;
|
|
||||||
|
|
||||||
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
/**
|
||||||
try {
|
* Performs the actual token refresh
|
||||||
// Ensure we have a valid token
|
* @param correlationId - Optional correlation ID for logging
|
||||||
await this.ensureAuthenticated(correlationId);
|
*/
|
||||||
|
private async performTokenRefresh(correlationId?: string): Promise<void> {
|
||||||
if (!this.accessToken) {
|
try {
|
||||||
throw new Error('Failed to obtain access token');
|
const refreshResponse = await this.apiService.refreshToken(this.refreshToken!, correlationId);
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the API call
|
this.accessToken = refreshResponse.accessToken;
|
||||||
return await apiCall(this.accessToken);
|
this.refreshToken = refreshResponse.refreshToken;
|
||||||
|
|
||||||
} catch (error: any) {
|
|
||||||
lastError = error;
|
|
||||||
|
|
||||||
// If it's an authentication error and we have retries left, try to refresh token
|
|
||||||
if (error.message.includes('Unauthorized') && attempt < retryCount) {
|
|
||||||
try {
|
|
||||||
await this.refreshAccessToken(correlationId);
|
|
||||||
continue; // Retry with new token
|
|
||||||
} catch (refreshError) {
|
|
||||||
// If refresh fails, clear tokens and try full login on next attempt
|
|
||||||
this.clearTokens();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's the last attempt or not an auth error, throw
|
|
||||||
if (attempt === retryCount) {
|
|
||||||
throw lastError;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait before retry (exponential backoff)
|
|
||||||
await new Promise(resolve => {
|
|
||||||
const delay = Math.pow(2, attempt) * 1000;
|
|
||||||
// Use a simple delay implementation
|
|
||||||
const start = Date.now();
|
|
||||||
while (Date.now() - start < delay) {
|
|
||||||
// Busy wait for delay
|
|
||||||
}
|
|
||||||
resolve(undefined);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw lastError!;
|
// Set token expiry to 50 minutes from now
|
||||||
}
|
this.tokenExpiry = new Date(Date.now() + 50 * 60 * 1000);
|
||||||
|
} catch (error) {
|
||||||
|
this.clearTokens();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using external identifier with automatic authentication and retry
|
* Clears stored tokens and pending promises
|
||||||
*/
|
*/
|
||||||
async protectWithExternalRefId(
|
private clearTokens(): void {
|
||||||
protectRequest: IProtectWithExternalRefIdRequest,
|
this.accessToken = undefined;
|
||||||
correlationId?: string,
|
this.refreshToken = undefined;
|
||||||
retryCount?: number
|
this.tokenExpiry = undefined;
|
||||||
): Promise<IProtectWithExternalRefIdResponse> {
|
this.refreshPromise = undefined;
|
||||||
return this.executeWithRetry(
|
this.loginPromise = undefined;
|
||||||
(accessToken) => this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
|
}
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protect file using existing protected file ID with automatic authentication and retry
|
* Executes an API call with automatic authentication and retry logic
|
||||||
*/
|
* @param apiCall - The API call function to execute
|
||||||
async protectWithFileId(
|
* @param retryCount - Number of retries (defaults to class default)
|
||||||
protectRequest: IProtectWithFileIdRequest,
|
* @param correlationId - Optional correlation ID for logging
|
||||||
correlationId?: string,
|
*/
|
||||||
retryCount?: number
|
private async executeWithRetry<T>(
|
||||||
): Promise<IProtectWithFileIdResponse> {
|
apiCall: (accessToken: string) => Promise<T>,
|
||||||
return this.executeWithRetry(
|
retryCount: number = this.defaultRetryCount,
|
||||||
(accessToken) => this.apiService.protectWithFileId(protectRequest, accessToken, correlationId),
|
correlationId?: string,
|
||||||
retryCount,
|
): Promise<T> {
|
||||||
correlationId
|
let lastError: Error;
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
||||||
* Protect file using HotFolder ID with automatic authentication and retry
|
try {
|
||||||
*/
|
// Ensure we have a valid token
|
||||||
async protectWithHotFolder(
|
await this.ensureAuthenticated(correlationId);
|
||||||
protectRequest: IProtectWithHotFolderRequest,
|
|
||||||
correlationId?: string,
|
|
||||||
retryCount?: number
|
|
||||||
): Promise<IProtectWithHotFolderResponse> {
|
|
||||||
return this.executeWithRetry(
|
|
||||||
(accessToken) => this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
|
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
if (!this.accessToken) {
|
||||||
* Unprotect file with automatic authentication and retry
|
throw new Error('Failed to obtain access token');
|
||||||
*/
|
}
|
||||||
async unprotect(
|
|
||||||
unprotectRequest: IUnprotectRequest,
|
|
||||||
correlationId?: string,
|
|
||||||
retryCount?: number
|
|
||||||
): Promise<IUnprotectResponse> {
|
|
||||||
return this.executeWithRetry(
|
|
||||||
(accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
|
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Execute the API call
|
||||||
* Upload file with automatic authentication and retry
|
return await apiCall(this.accessToken);
|
||||||
*/
|
} catch (error: unknown) {
|
||||||
async uploadFile(
|
lastError = error as Error;
|
||||||
fileBuffer: Uint8Array,
|
// If it's an authentication error and we have retries left, try to refresh token
|
||||||
fileName: string,
|
if ((error as Error).message.includes('Unauthorized') && attempt < retryCount) {
|
||||||
correlationId?: string,
|
try {
|
||||||
retryCount?: number
|
await this.refreshAccessToken(correlationId);
|
||||||
): Promise<IFileUploadResponse> {
|
continue; // Retry with new token
|
||||||
return this.executeWithRetry(
|
} catch (error: unknown) {
|
||||||
(accessToken) => this.apiService.uploadFile(fileBuffer, fileName, accessToken, correlationId),
|
console.error(error);
|
||||||
retryCount,
|
// If refresh fails, clear tokens and try full login on next attempt
|
||||||
correlationId
|
this.clearTokens();
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// If it's the last attempt or not an auth error, throw
|
||||||
* Download file with automatic authentication and retry
|
if (attempt === retryCount) {
|
||||||
* NOTE: Files whose fileStorageId has 'DL_' prefix will be deleted from the file storage after download.
|
throw lastError;
|
||||||
*/
|
}
|
||||||
async downloadFile(
|
|
||||||
fileStorageId: string,
|
|
||||||
correlationId?: string,
|
|
||||||
retryCount?: number
|
|
||||||
): Promise<Uint8Array> {
|
|
||||||
return this.executeWithRetry(
|
|
||||||
(accessToken) => this.apiService.downloadFile(fileStorageId, accessToken, correlationId),
|
|
||||||
retryCount,
|
|
||||||
correlationId
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
// Wait before retry (exponential backoff)
|
||||||
* Get current access token (for debugging/monitoring)
|
await new Promise((resolve) => {
|
||||||
*/
|
const delay = Math.pow(2, attempt) * 1000;
|
||||||
getAccessToken(): string | undefined {
|
// Use a simple delay implementation
|
||||||
return this.accessToken;
|
const start = Date.now();
|
||||||
}
|
while (Date.now() - start < delay) {
|
||||||
|
// Busy wait for delay
|
||||||
|
}
|
||||||
|
resolve(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
throw lastError!;
|
||||||
* Check if currently authenticated
|
}
|
||||||
*/
|
|
||||||
isAuthenticated(): boolean {
|
|
||||||
return !!this.accessToken && (!this.tokenExpiry || new Date() < this.tokenExpiry);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protect file using external identifier with automatic authentication and retry
|
||||||
|
*/
|
||||||
|
async protectWithExternalRefId(
|
||||||
|
protectRequest: IProtectWithExternalRefIdRequest,
|
||||||
|
correlationId?: string,
|
||||||
|
retryCount?: number,
|
||||||
|
): Promise<IProtectWithExternalRefIdResponse> {
|
||||||
|
return this.executeWithRetry(
|
||||||
|
(accessToken) =>
|
||||||
|
this.apiService.protectWithExternalRefId(protectRequest, accessToken, correlationId),
|
||||||
|
retryCount,
|
||||||
|
correlationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 HotFolder ID with automatic authentication and retry
|
||||||
|
*/
|
||||||
|
async protectWithHotFolder(
|
||||||
|
protectRequest: IProtectWithHotFolderRequest,
|
||||||
|
correlationId?: string,
|
||||||
|
retryCount?: number,
|
||||||
|
): Promise<IProtectWithHotFolderResponse> {
|
||||||
|
return this.executeWithRetry(
|
||||||
|
(accessToken) =>
|
||||||
|
this.apiService.protectWithHotFolder(protectRequest, accessToken, correlationId),
|
||||||
|
retryCount,
|
||||||
|
correlationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unprotect file with automatic authentication and retry
|
||||||
|
*/
|
||||||
|
async unprotect(
|
||||||
|
unprotectRequest: IUnprotectRequest,
|
||||||
|
correlationId?: string,
|
||||||
|
retryCount?: number,
|
||||||
|
): Promise<IUnprotectResponse> {
|
||||||
|
return this.executeWithRetry(
|
||||||
|
(accessToken) => this.apiService.unprotect(unprotectRequest, accessToken, correlationId),
|
||||||
|
retryCount,
|
||||||
|
correlationId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload file with automatic authentication and retry
|
||||||
|
*/
|
||||||
|
async uploadFile(
|
||||||
|
fileBuffer: Uint8Array,
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue