Official 1Password Template Function (#305)

This commit is contained in:
Gregory Schier
2025-11-22 06:08:13 -08:00
committed by GitHub
parent 43a7132014
commit 2bac610efe
20 changed files with 1440 additions and 142 deletions

View File

@@ -0,0 +1,20 @@
{
"name": "@yaak/template-function-1password",
"displayName": "1Password Template Functions",
"description": "Template function for accessing 1Password secrets",
"private": true,
"version": "0.1.0",
"scripts": {
"build": "run-p build:*",
"build:1-build": "yaakcli build",
"build:2-cpywasm": "cpx '../../node_modules/@1password/sdk-core/nodejs/core_bg.*' build/",
"dev": "yaakcli dev",
"lint": "tsc --noEmit && eslint . --ext .ts,.tsx"
},
"dependencies": {
"@1password/sdk": "^0.4.0-beta.2"
},
"devDependencies": {
"cpx": "^1.5.0"
}
}

View File

@@ -0,0 +1,123 @@
import type { Client } from '@1password/sdk';
import { createClient } from '@1password/sdk';
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
import type { PluginDefinition } from '@yaakapp/api';
import crypto from 'crypto';
const _clients: Record<string, Client> = {};
async function op(args: CallTemplateFunctionArgs): Promise<Client | null> {
const token = args.values.token;
if (typeof token !== 'string') return null;
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
try {
_clients[tokenHash] ??= await createClient({
auth: token,
integrationName: 'Yaak 1Password Plugin',
integrationVersion: 'v1.0.0',
});
} catch {
return null;
}
return _clients[tokenHash];
}
export const plugin: PluginDefinition = {
templateFunctions: [
{
name: '1password.item',
description: 'Get a secret',
args: [
{
name: 'token',
type: 'text',
label: '1Password Service Account Token',
description: '',
defaultValue: '${[ONEPASSWORD_TOKEN]}',
password: true,
},
{
name: 'vault',
label: 'Vault',
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
if (client == null) return { hidden: true };
// Fetches a secret.
const vaults = await client.vaults.list({ decryptDetails: true });
return {
options: vaults.map((vault) => ({
label: `${vault.title} (${vault.activeItemCount} Items)`,
value: vault.id,
})),
};
},
},
{
name: 'item',
label: 'Item',
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
if (client == null) return { hidden: true };
const vaultId = args.values.vault;
if (typeof vaultId !== 'string') return { hidden: true };
const items = await client.items.list(vaultId);
return {
options: items.map((item) => ({
label: item.title + ' ' + item.category,
value: item.id,
})),
};
},
},
{
name: 'field',
label: 'Field',
type: 'select',
options: [],
async dynamic(_ctx, args) {
const client = await op(args);
if (client == null) return { hidden: true };
const vaultId = args.values.vault;
const itemId = args.values.item;
if (typeof vaultId !== 'string' || typeof itemId !== 'string') {
return { hidden: true };
}
const item = await client.items.get(vaultId, itemId);
return {
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
};
},
},
],
async onRender(_ctx, args) {
const client = await op(args);
if (client == null) throw new Error('Invalid token');
const vaultId = args.values.vault;
const itemId = args.values.item;
const fieldId = args.values.field;
if (
typeof vaultId !== 'string' ||
typeof itemId !== 'string' ||
typeof fieldId !== 'string'
) {
return null;
}
const item = await client.items.get(vaultId, itemId);
const field = item.fields.find((f) => f.id === fieldId);
if (field == null) {
throw new Error('Field not found: ' + fieldId);
}
return field.value ?? '';
},
},
],
};

View File

@@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@@ -1,4 +1,5 @@
import type { AnyModel, HttpUrlParameter } from '@yaakapp-internal/models';
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api';
export const plugin: PluginDefinition = {
@@ -36,6 +37,21 @@ export const plugin: PluginDefinition = {
name: 'header',
label: 'Header Name',
type: 'text',
async dynamic(ctx, args) {
if (typeof args.values.requestId !== 'string') return null;
const request = await ctx.httpRequest.getById({ id: args.values.requestId });
if (request == null) return null;
const validHeaders = request.headers.filter(h => h.enabled !== false && h.name);
return {
placeholder: validHeaders[0]?.name,
completionOptions: validHeaders.map<GenericCompletionOption>((h) => ({
label: h.name,
type: 'constant',
})),
};
},
},
],
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {

View File

@@ -1,3 +1,4 @@
import type { GenericCompletionOption } from '@yaakapp-internal/plugins';
import type { JSONPathResult } from '../../template-function-json';
import { filterJSONPath } from '../../template-function-json';
import type { XPathResult } from '../../template-function-xml';
@@ -68,7 +69,22 @@ export const plugin: PluginDefinition = {
type: 'text',
name: 'header',
label: 'Header Name',
placeholder: 'Content-Type',
async dynamic(ctx, args) {
const response = await getResponse(ctx, {
requestId: String(args.values.request || ''),
purpose: args.purpose,
behavior: args.values.behavior ? String(args.values.behavior) : null,
ttl: String(args.values.ttl || ''),
});
return {
placeholder: response?.headers[0]?.name,
completionOptions: response?.headers.map<GenericCompletionOption>((h) => ({
label: h.name,
type: 'constant',
})),
};
},
},
],
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {