Skip to content

Commit 2f80bbf

Browse files
committed
update 1password to support cloud & locally hosted
1 parent 9c2e61b commit 2f80bbf

File tree

33 files changed

+1558
-821
lines changed

33 files changed

+1558
-821
lines changed

apps/docs/content/docs/en/tools/jira.mdx

Lines changed: 3 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -313,45 +313,7 @@ Retrieve multiple Jira issues from a project in bulk
313313
| Parameter | Type | Description |
314314
| --------- | ---- | ----------- |
315315
| `ts` | string | ISO 8601 timestamp of the operation |
316-
| `total` | number | Total number of issues in the project |
317-
| `issues` | array | Array of Jira issues |
318-
|`id` | string | Issue ID |
319-
|`key` | string | Issue key \(e.g., PROJ-123\) |
320-
|`self` | string | REST API URL for this issue |
321-
|`summary` | string | Issue summary |
322-
|`description` | string | Issue description text |
323-
|`status` | object | Issue status |
324-
|`id` | string | Status ID |
325-
|`name` | string | Status name |
326-
|`issuetype` | object | Issue type |
327-
|`id` | string | Issue type ID |
328-
|`name` | string | Issue type name |
329-
|`priority` | object | Issue priority |
330-
|`id` | string | Priority ID |
331-
|`name` | string | Priority name |
332-
|`assignee` | object | Assigned user |
333-
|`accountId` | string | Atlassian account ID |
334-
|`displayName` | string | Display name |
335-
|`created` | string | ISO 8601 creation timestamp |
336-
|`updated` | string | ISO 8601 last updated timestamp |
337-
338-
### `jira_bulk_read`
339-
340-
Retrieve multiple Jira issues from a project in bulk with cursor-based pagination (V2 - uses nextPageToken)
341-
342-
#### Input
343-
344-
| Parameter | Type | Required | Description |
345-
| --------- | ---- | -------- | ----------- |
346-
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
347-
| `projectId` | string | Yes | Jira project key \(e.g., PROJ\) |
348-
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
349-
350-
#### Output
351-
352-
| Parameter | Type | Description |
353-
| --------- | ---- | ----------- |
354-
| `ts` | string | ISO 8601 timestamp of the operation |
316+
| `total` | number | Total number of issues in the project \(may not always be available\) |
355317
| `issues` | array | Array of Jira issues |
356318
|`id` | string | Issue ID |
357319
|`key` | string | Issue key \(e.g., PROJ-123\) |
@@ -374,7 +336,6 @@ Retrieve multiple Jira issues from a project in bulk with cursor-based paginatio
374336
|`updated` | string | ISO 8601 last updated timestamp |
375337
| `nextPageToken` | string | Cursor token for the next page. Null when no more results. |
376338
| `isLast` | boolean | Whether this is the last page of results |
377-
| `total` | number | Total number of issues in the project \(may not always be available\) |
378339

379340
### `jira_delete_issue`
380341

@@ -454,90 +415,8 @@ Search for Jira issues using JQL (Jira Query Language)
454415
| --------- | ---- | -------- | ----------- |
455416
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
456417
| `jql` | string | Yes | JQL query string to search for issues \(e.g., "project = PROJ AND status = Open"\) |
457-
| `startAt` | number | No | The index of the first result to return \(for pagination\) |
458-
| `maxResults` | number | No | Maximum number of results to return \(default: 50\) |
459-
| `fields` | array | No | Array of field names to return \(default: all navigable\). Use "*all" for every field. |
460-
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
461-
462-
#### Output
463-
464-
| Parameter | Type | Description |
465-
| --------- | ---- | ----------- |
466-
| `ts` | string | ISO 8601 timestamp of the operation |
467-
| `total` | number | Total number of matching issues |
468-
| `startAt` | number | Pagination start index |
469-
| `maxResults` | number | Maximum results per page |
470-
| `issues` | array | Array of matching issues |
471-
|`id` | string | Issue ID |
472-
|`key` | string | Issue key \(e.g., PROJ-123\) |
473-
|`self` | string | REST API URL for this issue |
474-
|`summary` | string | Issue summary |
475-
|`description` | string | Issue description text \(extracted from ADF\) |
476-
|`status` | object | Issue status |
477-
|`id` | string | Status ID |
478-
|`name` | string | Status name \(e.g., Open, In Progress, Done\) |
479-
|`description` | string | Status description |
480-
|`statusCategory` | object | Status category grouping |
481-
|`id` | number | Status category ID |
482-
|`key` | string | Status category key \(e.g., new, indeterminate, done\) |
483-
|`name` | string | Status category name \(e.g., To Do, In Progress, Done\) |
484-
|`colorName` | string | Status category color \(e.g., blue-gray, yellow, green\) |
485-
|`issuetype` | object | Issue type |
486-
|`id` | string | Issue type ID |
487-
|`name` | string | Issue type name \(e.g., Task, Bug, Story, Epic\) |
488-
|`description` | string | Issue type description |
489-
|`subtask` | boolean | Whether this is a subtask type |
490-
|`iconUrl` | string | URL to the issue type icon |
491-
|`project` | object | Project the issue belongs to |
492-
|`id` | string | Project ID |
493-
|`key` | string | Project key \(e.g., PROJ\) |
494-
|`name` | string | Project name |
495-
|`projectTypeKey` | string | Project type key \(e.g., software, business\) |
496-
|`priority` | object | Issue priority |
497-
|`id` | string | Priority ID |
498-
|`name` | string | Priority name \(e.g., Highest, High, Medium, Low, Lowest\) |
499-
|`iconUrl` | string | URL to the priority icon |
500-
|`assignee` | object | Assigned user |
501-
|`accountId` | string | Atlassian account ID of the user |
502-
|`displayName` | string | Display name of the user |
503-
|`active` | boolean | Whether the user account is active |
504-
|`emailAddress` | string | Email address of the user |
505-
|`accountType` | string | Type of account \(e.g., atlassian, app, customer\) |
506-
|`avatarUrl` | string | URL to the user avatar \(48x48\) |
507-
|`timeZone` | string | User timezone |
508-
|`reporter` | object | Reporter user |
509-
|`accountId` | string | Atlassian account ID of the user |
510-
|`displayName` | string | Display name of the user |
511-
|`active` | boolean | Whether the user account is active |
512-
|`emailAddress` | string | Email address of the user |
513-
|`accountType` | string | Type of account \(e.g., atlassian, app, customer\) |
514-
|`avatarUrl` | string | URL to the user avatar \(48x48\) |
515-
|`timeZone` | string | User timezone |
516-
|`labels` | array | Issue labels |
517-
|`components` | array | Issue components |
518-
|`id` | string | Component ID |
519-
|`name` | string | Component name |
520-
|`description` | string | Component description |
521-
|`resolution` | object | Issue resolution |
522-
|`id` | string | Resolution ID |
523-
|`name` | string | Resolution name \(e.g., Fixed, Duplicate, Won't Fix\) |
524-
|`description` | string | Resolution description |
525-
|`duedate` | string | Due date \(YYYY-MM-DD\) |
526-
|`created` | string | ISO 8601 timestamp when the issue was created |
527-
|`updated` | string | ISO 8601 timestamp when the issue was last updated |
528-
529-
### `jira_search_issues`
530-
531-
Search for Jira issues using JQL with cursor-based pagination (V2 - uses nextPageToken)
532-
533-
#### Input
534-
535-
| Parameter | Type | Required | Description |
536-
| --------- | ---- | -------- | ----------- |
537-
| `domain` | string | Yes | Your Jira domain \(e.g., yourcompany.atlassian.net\) |
538-
| `jql` | string | Yes | JQL query string to search for issues \(e.g., "project = PROJ AND status = Open"\) |
539-
| `startAt` | number | No | The index of the first result to return \(for pagination\) |
540-
| `maxResults` | number | No | Maximum number of results to return \(default: 50\) |
418+
| `nextPageToken` | string | No | Cursor token for the next page of results. Omit for the first page. |
419+
| `maxResults` | number | No | Maximum number of results to return per page \(default: 50\) |
541420
| `fields` | array | No | Array of field names to return \(default: all navigable\). Use "*all" for every field. |
542421
| `cloudId` | string | No | Jira Cloud ID for the instance. If not provided, it will be fetched using the domain. |
543422

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { randomUUID } from 'crypto'
2+
import { createLogger } from '@sim/logger'
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { z } from 'zod'
5+
import { checkInternalAuth } from '@/lib/auth/hybrid'
6+
import {
7+
connectRequest,
8+
createOnePasswordClient,
9+
normalizeSdkItem,
10+
resolveCredentials,
11+
toSdkCategory,
12+
toSdkFieldType,
13+
} from '../utils'
14+
15+
const logger = createLogger('OnePasswordCreateItemAPI')
16+
17+
const CreateItemSchema = z.object({
18+
connectionMode: z.enum(['service_account', 'connect']).nullish(),
19+
serviceAccountToken: z.string().nullish(),
20+
serverUrl: z.string().nullish(),
21+
apiKey: z.string().nullish(),
22+
vaultId: z.string().min(1, 'Vault ID is required'),
23+
category: z.string().min(1, 'Category is required'),
24+
title: z.string().nullish(),
25+
tags: z.string().nullish(),
26+
fields: z.string().nullish(),
27+
})
28+
29+
export async function POST(request: NextRequest) {
30+
const requestId = randomUUID().slice(0, 8)
31+
32+
const auth = await checkInternalAuth(request)
33+
if (!auth.success || !auth.userId) {
34+
logger.warn(`[${requestId}] Unauthorized 1Password create-item attempt`)
35+
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
36+
}
37+
38+
try {
39+
const body = await request.json()
40+
const params = CreateItemSchema.parse(body)
41+
const creds = resolveCredentials(params)
42+
43+
logger.info(`[${requestId}] Creating item in vault ${params.vaultId} (${creds.mode} mode)`)
44+
45+
if (creds.mode === 'service_account') {
46+
const client = await createOnePasswordClient(creds.serviceAccountToken!)
47+
48+
const parsedTags = params.tags
49+
? params.tags
50+
.split(',')
51+
.map((t) => t.trim())
52+
.filter(Boolean)
53+
: undefined
54+
55+
const parsedFields = params.fields
56+
? (JSON.parse(params.fields) as Array<Record<string, any>>).map((f) => ({
57+
id: f.id || '',
58+
title: f.label || f.title || '',
59+
fieldType: toSdkFieldType(f.type || 'STRING'),
60+
value: f.value || '',
61+
sectionId: f.section?.id ?? f.sectionId,
62+
}))
63+
: undefined
64+
65+
// Cast to any because toSdkCategory/toSdkFieldType return string literals
66+
// that match SDK enum values but TypeScript can't verify this at compile time
67+
const item = await client.items.create({
68+
vaultId: params.vaultId,
69+
category: toSdkCategory(params.category) as any,
70+
title: params.title || '',
71+
tags: parsedTags,
72+
fields: parsedFields as any,
73+
})
74+
75+
return NextResponse.json(normalizeSdkItem(item))
76+
}
77+
78+
const connectBody: Record<string, unknown> = {
79+
vault: { id: params.vaultId },
80+
category: params.category,
81+
}
82+
if (params.title) connectBody.title = params.title
83+
if (params.tags) connectBody.tags = params.tags.split(',').map((t) => t.trim())
84+
if (params.fields) connectBody.fields = JSON.parse(params.fields)
85+
86+
const response = await connectRequest({
87+
serverUrl: creds.serverUrl!,
88+
apiKey: creds.apiKey!,
89+
path: `/v1/vaults/${params.vaultId}/items`,
90+
method: 'POST',
91+
body: connectBody,
92+
})
93+
94+
const data = await response.json()
95+
if (!response.ok) {
96+
return NextResponse.json(
97+
{ error: data.message || 'Failed to create item' },
98+
{ status: response.status }
99+
)
100+
}
101+
102+
return NextResponse.json(data)
103+
} catch (error) {
104+
if (error instanceof z.ZodError) {
105+
return NextResponse.json(
106+
{ error: 'Invalid request data', details: error.errors },
107+
{ status: 400 }
108+
)
109+
}
110+
const message = error instanceof Error ? error.message : 'Unknown error'
111+
logger.error(`[${requestId}] Create item failed:`, error)
112+
return NextResponse.json({ error: `Failed to create item: ${message}` }, { status: 500 })
113+
}
114+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { randomUUID } from 'crypto'
2+
import { createLogger } from '@sim/logger'
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { z } from 'zod'
5+
import { checkInternalAuth } from '@/lib/auth/hybrid'
6+
import { connectRequest, createOnePasswordClient, resolveCredentials } from '../utils'
7+
8+
const logger = createLogger('OnePasswordDeleteItemAPI')
9+
10+
const DeleteItemSchema = z.object({
11+
connectionMode: z.enum(['service_account', 'connect']).nullish(),
12+
serviceAccountToken: z.string().nullish(),
13+
serverUrl: z.string().nullish(),
14+
apiKey: z.string().nullish(),
15+
vaultId: z.string().min(1, 'Vault ID is required'),
16+
itemId: z.string().min(1, 'Item ID is required'),
17+
})
18+
19+
export async function POST(request: NextRequest) {
20+
const requestId = randomUUID().slice(0, 8)
21+
22+
const auth = await checkInternalAuth(request)
23+
if (!auth.success || !auth.userId) {
24+
logger.warn(`[${requestId}] Unauthorized 1Password delete-item attempt`)
25+
return NextResponse.json({ error: auth.error || 'Unauthorized' }, { status: 401 })
26+
}
27+
28+
try {
29+
const body = await request.json()
30+
const params = DeleteItemSchema.parse(body)
31+
const creds = resolveCredentials(params)
32+
33+
logger.info(
34+
`[${requestId}] Deleting item ${params.itemId} from vault ${params.vaultId} (${creds.mode} mode)`
35+
)
36+
37+
if (creds.mode === 'service_account') {
38+
const client = await createOnePasswordClient(creds.serviceAccountToken!)
39+
await client.items.delete(params.vaultId, params.itemId)
40+
return NextResponse.json({ success: true })
41+
}
42+
43+
const response = await connectRequest({
44+
serverUrl: creds.serverUrl!,
45+
apiKey: creds.apiKey!,
46+
path: `/v1/vaults/${params.vaultId}/items/${params.itemId}`,
47+
method: 'DELETE',
48+
})
49+
50+
if (!response.ok) {
51+
const data = await response.json().catch(() => ({}))
52+
return NextResponse.json(
53+
{ error: (data as Record<string, string>).message || 'Failed to delete item' },
54+
{ status: response.status }
55+
)
56+
}
57+
58+
return NextResponse.json({ success: true })
59+
} catch (error) {
60+
if (error instanceof z.ZodError) {
61+
return NextResponse.json(
62+
{ error: 'Invalid request data', details: error.errors },
63+
{ status: 400 }
64+
)
65+
}
66+
const message = error instanceof Error ? error.message : 'Unknown error'
67+
logger.error(`[${requestId}] Delete item failed:`, error)
68+
return NextResponse.json({ error: `Failed to delete item: ${message}` }, { status: 500 })
69+
}
70+
}

0 commit comments

Comments
 (0)