fix: allow vault info under path scope and clarify API errors
Build and Push Docker Container / build-and-push (push) Successful in 4m29s

- Allow vault and vaults commands when a token has path restrictions

- Treat vault/vaults as pathless metadata commands in policy checks

- Add allowed commands, vaults and paths to 403 permission errors

- Surface real JWT failure reason in 401 instead of generic Unauthorized

- Report missing, malformed and empty Bearer tokens distinctly

- Catch httpRequest failures in the n8n node to avoid circular JSON

- Map API errors to clean NodeApiError with status code and detail

- Support continueOnFail with per-item error output in the n8n node

- Push a versioned image tag from pyproject in the CI workflow

- Bump server to 1.0.2 and n8n node package to 0.2.1

- Add tests for vault info access and richer auth error messages
This commit is contained in:
2026-06-24 11:41:18 +02:00
parent be78e3aa00
commit 3bda4002fe
9 changed files with 184 additions and 53 deletions
@@ -225,41 +225,50 @@ export class ObsidianApi implements INodeType {
const returnData: INodeExecutionData[] = [];
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
const resource = this.getNodeParameter('resource', itemIndex) as string;
const operation = this.getNodeParameter('operation', itemIndex) as string;
const additionalFields = this.getNodeParameter('additionalFields', itemIndex, {}) as Record<string, unknown>;
const vault = String(additionalFields.vault || '');
const timeout = Number(additionalFields.timeout ?? 60);
try {
const resource = this.getNodeParameter('resource', itemIndex) as string;
const operation = this.getNodeParameter('operation', itemIndex) as string;
const additionalFields = this.getNodeParameter('additionalFields', itemIndex, {}) as Record<string, unknown>;
const vault = String(additionalFields.vault || '');
const timeout = Number(additionalFields.timeout ?? 60);
let args: string[];
let args: string[];
switch (resource) {
case 'note': {
args = buildNoteArgs(this, operation, itemIndex);
break;
switch (resource) {
case 'note': {
args = buildNoteArgs(this, operation, itemIndex);
break;
}
case 'daily': {
args = buildDailyArgs(this, operation, itemIndex);
break;
}
case 'search': {
args = buildSearchArgs(this, operation, itemIndex);
break;
}
case 'vault': {
args = buildVaultArgs(this, operation, itemIndex, additionalFields);
break;
}
case 'raw': {
args = parseRawArgs(this, itemIndex);
break;
}
default:
throw new NodeOperationError(this.getNode(), `Unsupported resource: ${resource}`, { itemIndex });
}
case 'daily': {
args = buildDailyArgs(this, operation, itemIndex);
break;
const result = await executeObsidianCommand(this, itemIndex, args, { vault, timeout });
returnData.push({ json: result, pairedItem: { item: itemIndex } });
} catch (error) {
if (this.continueOnFail()) {
const message = error instanceof Error ? error.message : String(error);
returnData.push({ json: { error: message }, pairedItem: { item: itemIndex } });
continue;
}
case 'search': {
args = buildSearchArgs(this, operation, itemIndex);
break;
}
case 'vault': {
args = buildVaultArgs(this, operation, itemIndex, additionalFields);
break;
}
case 'raw': {
args = parseRawArgs(this, itemIndex);
break;
}
default:
throw new NodeOperationError(this.getNode(), `Unsupported resource: ${resource}`, { itemIndex });
throw error;
}
const result = await executeObsidianCommand(this, itemIndex, args, { vault, timeout });
returnData.push({ json: result, pairedItem: { item: itemIndex } });
}
return [returnData];
@@ -285,7 +294,7 @@ function buildNoteArgs(context: IExecuteFunctions, operation: string, itemIndex:
return compactArgs(['files', kv('folder', context.getNodeParameter('folder', itemIndex, '') as string)]);
default:
throw new NodeOperationError(context.getNode(), `Unsupported note operation: ${operation}`, { itemIndex });
}
}
}
function buildDailyArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
@@ -300,7 +309,7 @@ function buildDailyArgs(context: IExecuteFunctions, operation: string, itemIndex
return ['daily:path'];
default:
throw new NodeOperationError(context.getNode(), `Unsupported daily operation: ${operation}`, { itemIndex });
}
}
}
function buildSearchArgs(context: IExecuteFunctions, operation: string, itemIndex: number): string[] {
@@ -322,7 +331,7 @@ function buildVaultArgs(context: IExecuteFunctions, operation: string, itemIndex
}
default:
throw new NodeOperationError(context.getNode(), `Unsupported vault operation: ${operation}`, { itemIndex });
}
}
}
function parseRawArgs(context: IExecuteFunctions, itemIndex: number): string[] {
@@ -330,7 +339,7 @@ function parseRawArgs(context: IExecuteFunctions, itemIndex: number): string[] {
const parsed = Array.isArray(raw) ? raw : JSON.parse(raw);
if (!Array.isArray(parsed) || !parsed.every((item) => typeof item === 'string')) {
throw new NodeOperationError(context.getNode(), 'Args JSON must be an array of strings', { itemIndex });
}
}
return parsed;
}