mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-09 18:53:38 +02:00
Update 1Password template to support the new Desktop authentication method (#316)
This commit is contained in:
@@ -1,26 +1,86 @@
|
|||||||
import crypto from 'node:crypto';
|
import crypto from 'node:crypto';
|
||||||
import type { Client } from '@1password/sdk';
|
import type { Client } from '@1password/sdk';
|
||||||
import { createClient } from '@1password/sdk';
|
import { createClient, DesktopAuth } from '@1password/sdk';
|
||||||
import type { PluginDefinition } from '@yaakapp/api';
|
import type { JsonPrimitive, PluginDefinition } from '@yaakapp/api';
|
||||||
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
import type { CallTemplateFunctionArgs } from '@yaakapp-internal/plugins';
|
||||||
|
|
||||||
const _clients: Record<string, Client> = {};
|
const _clients: Record<string, Client> = {};
|
||||||
|
|
||||||
async function op(args: CallTemplateFunctionArgs): Promise<Client | null> {
|
async function op(args: CallTemplateFunctionArgs): Promise<{ client?: Client; error?: unknown }> {
|
||||||
const token = args.values.token;
|
let authMethod: string | DesktopAuth | null = null;
|
||||||
if (typeof token !== 'string') return null;
|
let hash: string | null = null;
|
||||||
|
switch (args.values.authMethod) {
|
||||||
|
case 'desktop': {
|
||||||
|
const account = args.values.account;
|
||||||
|
if (typeof account !== 'string' || !account) return { error: 'Missing account name' };
|
||||||
|
|
||||||
|
hash = crypto.createHash('sha256').update(`desktop:${account}`).digest('hex');
|
||||||
|
authMethod = new DesktopAuth(account);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'token': {
|
||||||
|
const token = args.values.token;
|
||||||
|
if (typeof token !== 'string' || !token) return { error: 'Missing service token' };
|
||||||
|
|
||||||
|
hash = crypto.createHash('sha256').update(`token:${token}`).digest('hex');
|
||||||
|
authMethod = token;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hash == null || authMethod == null) return { error: 'Invalid authentication method' };
|
||||||
|
|
||||||
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
|
|
||||||
try {
|
try {
|
||||||
_clients[tokenHash] ??= await createClient({
|
_clients[hash] ??= await createClient({
|
||||||
auth: token,
|
auth: authMethod,
|
||||||
integrationName: 'Yaak 1Password Plugin',
|
integrationName: 'Yaak 1Password Plugin',
|
||||||
integrationVersion: 'v1.0.0',
|
integrationVersion: 'v1.0.0',
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (e) {
|
||||||
return null;
|
return { error: e };
|
||||||
}
|
}
|
||||||
return _clients[tokenHash];
|
|
||||||
|
return { client: _clients[hash] };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getValue(
|
||||||
|
args: CallTemplateFunctionArgs,
|
||||||
|
vaultId?: JsonPrimitive,
|
||||||
|
itemId?: JsonPrimitive,
|
||||||
|
fieldId?: JsonPrimitive,
|
||||||
|
): Promise<{ value?: string; error?: unknown }> {
|
||||||
|
const { client, error } = await op(args);
|
||||||
|
if (!client) return { error };
|
||||||
|
|
||||||
|
if (vaultId && typeof vaultId === 'string') {
|
||||||
|
try {
|
||||||
|
await client.vaults.getOverview(vaultId);
|
||||||
|
} catch {
|
||||||
|
return { error: `Vault ${vaultId} not found` };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { error: 'No vault specified' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemId && typeof itemId === 'string') {
|
||||||
|
try {
|
||||||
|
const item = await client.items.get(vaultId, itemId);
|
||||||
|
if (fieldId && typeof fieldId === 'string') {
|
||||||
|
const field = item.fields.find((f) => f.id === fieldId);
|
||||||
|
if (field) {
|
||||||
|
return { value: field.value };
|
||||||
|
} else {
|
||||||
|
return { error: `Field ${fieldId} not found in item ${itemId} in vault ${vaultId}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return { error: `Item ${itemId} not found in vault ${vaultId}` };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { error: 'No item specified' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const plugin: PluginDefinition = {
|
export const plugin: PluginDefinition = {
|
||||||
@@ -31,14 +91,54 @@ export const plugin: PluginDefinition = {
|
|||||||
previewArgs: ['field'],
|
previewArgs: ['field'],
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: 'token',
|
type: 'h_stack',
|
||||||
type: 'text',
|
inputs: [
|
||||||
label: '1Password Service Account Token',
|
{
|
||||||
description:
|
name: 'authMethod',
|
||||||
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
|
type: 'select',
|
||||||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
label: 'Authentication Method',
|
||||||
defaultValue: '${[1PASSWORD_TOKEN]}',
|
description: '',
|
||||||
password: true,
|
options: [
|
||||||
|
{
|
||||||
|
label: 'Service Account',
|
||||||
|
value: 'token',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Desktop App',
|
||||||
|
value: 'desktop',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultValue: 'token',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'account',
|
||||||
|
type: 'text',
|
||||||
|
description: '',
|
||||||
|
dynamic(_ctx, args) {
|
||||||
|
switch (args.values.authMethod) {
|
||||||
|
case 'desktop':
|
||||||
|
return {
|
||||||
|
name: 'account',
|
||||||
|
label: 'Account Name',
|
||||||
|
description:
|
||||||
|
'Account name can be taken from the sidebar of the 1Password App. Make sure you\'re on the BETA version of the 1Password app and have "Integrate with other apps" enabled in Settings > Developer.',
|
||||||
|
};
|
||||||
|
case 'token':
|
||||||
|
return {
|
||||||
|
name: 'token',
|
||||||
|
label: 'Token',
|
||||||
|
description:
|
||||||
|
'Token can be generated from the 1Password website by visiting Developer > Service Accounts',
|
||||||
|
// biome-ignore lint/suspicious/noTemplateCurlyInString: Yaak template syntax
|
||||||
|
defaultValue: '${[1PASSWORD_TOKEN]}',
|
||||||
|
password: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { hidden: true };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'vault',
|
name: 'vault',
|
||||||
@@ -46,7 +146,7 @@ export const plugin: PluginDefinition = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [],
|
options: [],
|
||||||
async dynamic(_ctx, args) {
|
async dynamic(_ctx, args) {
|
||||||
const client = await op(args);
|
const { client } = await op(args);
|
||||||
if (client == null) return { hidden: true };
|
if (client == null) return { hidden: true };
|
||||||
// Fetches a secret.
|
// Fetches a secret.
|
||||||
const vaults = await client.vaults.list({ decryptDetails: true });
|
const vaults = await client.vaults.list({ decryptDetails: true });
|
||||||
@@ -64,18 +164,23 @@ export const plugin: PluginDefinition = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [],
|
options: [],
|
||||||
async dynamic(_ctx, args) {
|
async dynamic(_ctx, args) {
|
||||||
const client = await op(args);
|
const { client } = await op(args);
|
||||||
if (client == null) return { hidden: true };
|
if (client == null) return { hidden: true };
|
||||||
const vaultId = args.values.vault;
|
const vaultId = args.values.vault;
|
||||||
if (typeof vaultId !== 'string') return { hidden: true };
|
if (typeof vaultId !== 'string') return { hidden: true };
|
||||||
|
|
||||||
const items = await client.items.list(vaultId);
|
try {
|
||||||
return {
|
const items = await client.items.list(vaultId);
|
||||||
options: items.map((item) => ({
|
return {
|
||||||
label: `${item.title} ${item.category}`,
|
options: items.map((item) => ({
|
||||||
value: item.id,
|
label: `${item.title} ${item.category}`,
|
||||||
})),
|
value: item.id,
|
||||||
};
|
})),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
// Hide as we can't list the items for this vault
|
||||||
|
return { hidden: true };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -84,7 +189,7 @@ export const plugin: PluginDefinition = {
|
|||||||
type: 'select',
|
type: 'select',
|
||||||
options: [],
|
options: [],
|
||||||
async dynamic(_ctx, args) {
|
async dynamic(_ctx, args) {
|
||||||
const client = await op(args);
|
const { client } = await op(args);
|
||||||
if (client == null) return { hidden: true };
|
if (client == null) return { hidden: true };
|
||||||
const vaultId = args.values.vault;
|
const vaultId = args.values.vault;
|
||||||
const itemId = args.values.item;
|
const itemId = args.values.item;
|
||||||
@@ -92,34 +197,28 @@ export const plugin: PluginDefinition = {
|
|||||||
return { hidden: true };
|
return { hidden: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = await client.items.get(vaultId, itemId);
|
try {
|
||||||
|
const item = await client.items.get(vaultId, itemId);
|
||||||
return {
|
return {
|
||||||
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
|
options: item.fields.map((field) => ({ label: field.title, value: field.id })),
|
||||||
};
|
};
|
||||||
|
} catch {
|
||||||
|
// Hide as we can't find the item within this vault
|
||||||
|
return { hidden: true };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
async onRender(_ctx, args) {
|
async onRender(_ctx, args) {
|
||||||
const client = await op(args);
|
|
||||||
if (client == null) throw new Error('Invalid token');
|
|
||||||
const vaultId = args.values.vault;
|
const vaultId = args.values.vault;
|
||||||
const itemId = args.values.item;
|
const itemId = args.values.item;
|
||||||
const fieldId = args.values.field;
|
const fieldId = args.values.field;
|
||||||
if (
|
const { value, error } = await getValue(args, vaultId, itemId, fieldId);
|
||||||
typeof vaultId !== 'string' ||
|
if (error) {
|
||||||
typeof itemId !== 'string' ||
|
throw error;
|
||||||
typeof fieldId !== 'string'
|
|
||||||
) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const item = await client.items.get(vaultId, itemId);
|
return value ?? '';
|
||||||
const field = item.fields.find((f) => f.id === fieldId);
|
|
||||||
if (field == null) {
|
|
||||||
throw new Error(`Field not found: ${fieldId}`);
|
|
||||||
}
|
|
||||||
return field.value ?? '';
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user