mirror of
https://github.com/Infisical/infisical.git
synced 2025-07-29 22:37:44 +00:00
Compare commits
6 Commits
change-req
...
misc/added
Author | SHA1 | Date | |
---|---|---|---|
|
4fef5c305d | ||
|
30f3543850 | ||
|
114915f913 | ||
|
d79a6b8f25 | ||
|
217a09c97b | ||
|
9af5a66bab |
@@ -2427,7 +2427,8 @@ export const SecretSyncs = {
|
||||
keyOcid: "The OCID (Oracle Cloud Identifier) of the encryption key to use when creating secrets in the vault."
|
||||
},
|
||||
ONEPASS: {
|
||||
vaultId: "The ID of the 1Password vault to sync secrets to."
|
||||
vaultId: "The ID of the 1Password vault to sync secrets to.",
|
||||
valueLabel: "The label of the entry that holds the secret value."
|
||||
},
|
||||
HEROKU: {
|
||||
app: "The ID of the Heroku app to sync secrets to.",
|
||||
|
@@ -457,6 +457,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiAlerting],
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
@@ -487,6 +489,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateCollections],
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
@@ -549,6 +553,8 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
schema: {
|
||||
hide: false,
|
||||
tags: [ApiDocsTags.PkiCertificateTemplates],
|
||||
params: z.object({
|
||||
projectId: z.string().trim()
|
||||
}),
|
||||
|
@@ -6,7 +6,6 @@ import {
|
||||
TOnePassListVariablesResponse,
|
||||
TOnePassSyncWithCredentials,
|
||||
TOnePassVariable,
|
||||
TOnePassVariableDetails,
|
||||
TPostOnePassVariable,
|
||||
TPutOnePassVariable
|
||||
} from "@app/services/secret-sync/1password/1password-sync-types";
|
||||
@@ -14,7 +13,10 @@ import { SecretSyncError } from "@app/services/secret-sync/secret-sync-errors";
|
||||
import { matchesSchema } from "@app/services/secret-sync/secret-sync-fns";
|
||||
import { TSecretMap } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const listOnePassItems = async ({ instanceUrl, apiToken, vaultId }: TOnePassListVariables) => {
|
||||
// This should not be changed or it may break existing logic
|
||||
const VALUE_LABEL_DEFAULT = "value";
|
||||
|
||||
const listOnePassItems = async ({ instanceUrl, apiToken, vaultId, valueLabel }: TOnePassListVariables) => {
|
||||
const { data } = await request.get<TOnePassListVariablesResponse>(`${instanceUrl}/v1/vaults/${vaultId}/items`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
@@ -22,36 +24,49 @@ const listOnePassItems = async ({ instanceUrl, apiToken, vaultId }: TOnePassList
|
||||
}
|
||||
});
|
||||
|
||||
const result: Record<string, TOnePassVariable & { value: string; fieldId: string }> = {};
|
||||
const items: Record<string, TOnePassVariable & { value: string; fieldId: string }> = {};
|
||||
const duplicates: Record<string, string> = {};
|
||||
|
||||
for await (const s of data) {
|
||||
const { data: secret } = await request.get<TOnePassVariableDetails>(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
}
|
||||
);
|
||||
// eslint-disable-next-line no-continue
|
||||
if (s.category !== "API_CREDENTIAL") continue;
|
||||
|
||||
const value = secret.fields.find((f) => f.label === "value")?.value;
|
||||
const fieldId = secret.fields.find((f) => f.label === "value")?.id;
|
||||
if (items[s.title]) {
|
||||
duplicates[s.id] = s.title;
|
||||
// eslint-disable-next-line no-continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const { data: secret } = await request.get<TOnePassVariable>(`${instanceUrl}/v1/vaults/${vaultId}/items/${s.id}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
Accept: "application/json"
|
||||
}
|
||||
});
|
||||
|
||||
const valueField = secret.fields.find((f) => f.label === valueLabel);
|
||||
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!value || !fieldId) continue;
|
||||
if (!valueField || !valueField.value || !valueField.id) continue;
|
||||
|
||||
result[s.title] = {
|
||||
items[s.title] = {
|
||||
...secret,
|
||||
value,
|
||||
fieldId
|
||||
value: valueField.value,
|
||||
fieldId: valueField.id
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
return { items, duplicates };
|
||||
};
|
||||
|
||||
const createOnePassItem = async ({ instanceUrl, apiToken, vaultId, itemTitle, itemValue }: TPostOnePassVariable) => {
|
||||
const createOnePassItem = async ({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemTitle,
|
||||
itemValue,
|
||||
valueLabel
|
||||
}: TPostOnePassVariable) => {
|
||||
return request.post(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items`,
|
||||
{
|
||||
@@ -63,7 +78,7 @@ const createOnePassItem = async ({ instanceUrl, apiToken, vaultId, itemTitle, it
|
||||
tags: ["synced-from-infisical"],
|
||||
fields: [
|
||||
{
|
||||
label: "value",
|
||||
label: valueLabel,
|
||||
value: itemValue,
|
||||
type: "CONCEALED"
|
||||
}
|
||||
@@ -85,7 +100,9 @@ const updateOnePassItem = async ({
|
||||
itemId,
|
||||
fieldId,
|
||||
itemTitle,
|
||||
itemValue
|
||||
itemValue,
|
||||
valueLabel,
|
||||
otherFields
|
||||
}: TPutOnePassVariable) => {
|
||||
return request.put(
|
||||
`${instanceUrl}/v1/vaults/${vaultId}/items/${itemId}`,
|
||||
@@ -98,9 +115,10 @@ const updateOnePassItem = async ({
|
||||
},
|
||||
tags: ["synced-from-infisical"],
|
||||
fields: [
|
||||
...otherFields,
|
||||
{
|
||||
id: fieldId,
|
||||
label: "value",
|
||||
label: valueLabel,
|
||||
value: itemValue,
|
||||
type: "CONCEALED"
|
||||
}
|
||||
@@ -128,13 +146,18 @@ export const OnePassSyncFns = {
|
||||
const {
|
||||
connection,
|
||||
environment,
|
||||
destinationConfig: { vaultId }
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const items = await listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
const { items, duplicates } = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
|
||||
for await (const entry of Object.entries(secretMap)) {
|
||||
const [key, { value }] = entry;
|
||||
@@ -148,10 +171,19 @@ export const OnePassSyncFns = {
|
||||
itemTitle: key,
|
||||
itemValue: value,
|
||||
itemId: items[key].id,
|
||||
fieldId: items[key].fieldId
|
||||
fieldId: items[key].fieldId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT,
|
||||
otherFields: items[key].fields.filter((field) => field.label !== (valueLabel || VALUE_LABEL_DEFAULT))
|
||||
});
|
||||
} else {
|
||||
await createOnePassItem({ instanceUrl, apiToken, vaultId, itemTitle: key, itemValue: value });
|
||||
await createOnePassItem({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemTitle: key,
|
||||
itemValue: value,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
@@ -163,7 +195,28 @@ export const OnePassSyncFns = {
|
||||
|
||||
if (secretSync.syncOptions.disableSecretDeletion) return;
|
||||
|
||||
for await (const [key, variable] of Object.entries(items)) {
|
||||
// Delete duplicate item entries
|
||||
for await (const [itemId, key] of Object.entries(duplicates)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
|
||||
|
||||
try {
|
||||
await deleteOnePassItem({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemId
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
error,
|
||||
secretKey: key
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Delete item entries that are not in secretMap
|
||||
for await (const [key, item] of Object.entries(items)) {
|
||||
// eslint-disable-next-line no-continue
|
||||
if (!matchesSchema(key, environment?.slug || "", secretSync.syncOptions.keySchema)) continue;
|
||||
|
||||
@@ -173,7 +226,7 @@ export const OnePassSyncFns = {
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
itemId: variable.id
|
||||
itemId: item.id
|
||||
});
|
||||
} catch (error) {
|
||||
throw new SecretSyncError({
|
||||
@@ -187,13 +240,18 @@ export const OnePassSyncFns = {
|
||||
removeSecrets: async (secretSync: TOnePassSyncWithCredentials, secretMap: TSecretMap) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { vaultId }
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
const items = await listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
const { items } = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
|
||||
for await (const [key, item] of Object.entries(items)) {
|
||||
if (key in secretMap) {
|
||||
@@ -216,12 +274,19 @@ export const OnePassSyncFns = {
|
||||
getSecrets: async (secretSync: TOnePassSyncWithCredentials) => {
|
||||
const {
|
||||
connection,
|
||||
destinationConfig: { vaultId }
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
} = secretSync;
|
||||
|
||||
const instanceUrl = await getOnePassInstanceUrl(connection);
|
||||
const { apiToken } = connection.credentials;
|
||||
|
||||
return listOnePassItems({ instanceUrl, apiToken, vaultId });
|
||||
const res = await listOnePassItems({
|
||||
instanceUrl,
|
||||
apiToken,
|
||||
vaultId,
|
||||
valueLabel: valueLabel || VALUE_LABEL_DEFAULT
|
||||
});
|
||||
|
||||
return Object.fromEntries(Object.entries(res.items).map(([key, item]) => [key, { value: item.value }]));
|
||||
}
|
||||
};
|
||||
|
@@ -11,7 +11,8 @@ import {
|
||||
import { TSyncOptionsConfig } from "@app/services/secret-sync/secret-sync-types";
|
||||
|
||||
const OnePassSyncDestinationConfigSchema = z.object({
|
||||
vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId)
|
||||
vaultId: z.string().trim().min(1, "Vault required").describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.vaultId),
|
||||
valueLabel: z.string().trim().optional().describe(SecretSyncs.DESTINATION_CONFIG.ONEPASS.valueLabel)
|
||||
});
|
||||
|
||||
const OnePassSyncOptionsConfig: TSyncOptionsConfig = { canImportSecrets: true };
|
||||
|
@@ -14,29 +14,32 @@ export type TOnePassSyncWithCredentials = TOnePassSync & {
|
||||
connection: TOnePassConnection;
|
||||
};
|
||||
|
||||
type Field = {
|
||||
id: string;
|
||||
type: string; // CONCEALED, STRING
|
||||
label: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type TOnePassVariable = {
|
||||
id: string;
|
||||
title: string;
|
||||
category: string; // API_CREDENTIAL, SECURE_NOTE, LOGIN, etc
|
||||
};
|
||||
|
||||
export type TOnePassVariableDetails = TOnePassVariable & {
|
||||
fields: {
|
||||
id: string;
|
||||
type: string; // CONCEALED, STRING
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
fields: Field[];
|
||||
};
|
||||
|
||||
export type TOnePassListVariablesResponse = TOnePassVariable[];
|
||||
|
||||
export type TOnePassListVariables = {
|
||||
type TOnePassBase = {
|
||||
apiToken: string;
|
||||
instanceUrl: string;
|
||||
vaultId: string;
|
||||
};
|
||||
|
||||
export type TOnePassListVariables = TOnePassBase & {
|
||||
valueLabel: string;
|
||||
};
|
||||
|
||||
export type TPostOnePassVariable = TOnePassListVariables & {
|
||||
itemTitle: string;
|
||||
itemValue: string;
|
||||
@@ -47,8 +50,9 @@ export type TPutOnePassVariable = TOnePassListVariables & {
|
||||
fieldId: string;
|
||||
itemTitle: string;
|
||||
itemValue: string;
|
||||
otherFields: Field[];
|
||||
};
|
||||
|
||||
export type TDeleteOnePassVariable = TOnePassListVariables & {
|
||||
export type TDeleteOnePassVariable = TOnePassBase & {
|
||||
itemId: string;
|
||||
};
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 715 KiB After Width: | Height: | Size: 641 KiB |
@@ -36,6 +36,7 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
|
||||
- **1Password Connection**: The 1Password Connection to authenticate with.
|
||||
- **Vault**: The 1Password vault to sync secrets to.
|
||||
- **Value Label**: The label of the 1Password item field that will hold your secret value.
|
||||
</Step>
|
||||
<Step title="Configure sync options">
|
||||
Configure the **Sync Options** to specify how secrets should be synced, then click **Next**.
|
||||
@@ -94,7 +95,8 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
"initialSyncBehavior": "overwrite-destination"
|
||||
},
|
||||
"destinationConfig": {
|
||||
"vaultId": "..."
|
||||
"vaultId": "...",
|
||||
"valueLabel": "value"
|
||||
}
|
||||
}'
|
||||
```
|
||||
@@ -145,7 +147,8 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
},
|
||||
"destination": "1password",
|
||||
"destinationConfig": {
|
||||
"vaultId": "..."
|
||||
"vaultId": "...",
|
||||
"valueLabel": "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,4 +163,7 @@ description: "Learn how to configure a 1Password Sync for Infisical."
|
||||
Infisical can only perform CRUD operations on the following item types:
|
||||
- API Credentials
|
||||
</Accordion>
|
||||
<Accordion title="What is a 'Value Label'?">
|
||||
It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
@@ -4,7 +4,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { SecretSyncConnectionField } from "@app/components/secret-syncs/forms/SecretSyncConnectionField";
|
||||
import { FilterableSelect, FormControl, Tooltip } from "@app/components/v2";
|
||||
import { FilterableSelect, FormControl, Input, Tooltip } from "@app/components/v2";
|
||||
import {
|
||||
TOnePassVault,
|
||||
useOnePassConnectionListVaults
|
||||
@@ -32,6 +32,7 @@ export const OnePassSyncFields = () => {
|
||||
<SecretSyncConnectionField
|
||||
onChange={() => {
|
||||
setValue("destinationConfig.vaultId", "");
|
||||
setValue("destinationConfig.valueLabel", "");
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -69,6 +70,22 @@ export const OnePassSyncFields = () => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
isOptional
|
||||
label="Value Label"
|
||||
tooltipText="It's the label of the 1Password item field which will hold your secret value. For example, if you were to sync Infisical secret 'foo: bar', the 1Password item equivalent would have an item title of 'foo', and a field on that item 'value: bar'. The field label 'value' is what gets changed by this option."
|
||||
>
|
||||
<Input value={value} onChange={onChange} placeholder="value" />
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="destinationConfig.valueLabel"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -6,7 +6,15 @@ import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export const OnePassSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.OnePass }>();
|
||||
const vaultId = watch("destinationConfig.vaultId");
|
||||
const [vaultId, valueLabel] = watch([
|
||||
"destinationConfig.vaultId",
|
||||
"destinationConfig.valueLabel"
|
||||
]);
|
||||
|
||||
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@@ -7,7 +7,8 @@ export const OnePassSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.OnePass),
|
||||
destinationConfig: z.object({
|
||||
vaultId: z.string().trim().min(1, "Vault ID required")
|
||||
vaultId: z.string().trim().min(1, "Vault ID required"),
|
||||
valueLabel: z.string().trim().optional()
|
||||
})
|
||||
})
|
||||
);
|
||||
|
@@ -6,6 +6,7 @@ export type TOnePassSync = TRootSecretSync & {
|
||||
destination: SecretSync.OnePass;
|
||||
destinationConfig: {
|
||||
vaultId: string;
|
||||
valueLabel?: string;
|
||||
};
|
||||
connection: {
|
||||
app: AppConnection.OnePass;
|
||||
|
@@ -164,7 +164,10 @@ export const MinimizedOrgSidebar = () => {
|
||||
const handleCopyToken = async () => {
|
||||
try {
|
||||
await window.navigator.clipboard.writeText(getAuthToken());
|
||||
createNotification({ type: "success", text: "Copied current login session token to clipboard" });
|
||||
createNotification({
|
||||
type: "success",
|
||||
text: "Copied current login session token to clipboard"
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
createNotification({ type: "error", text: "Failed to copy user token to clipboard" });
|
||||
|
@@ -7,8 +7,13 @@ type Props = {
|
||||
|
||||
export const OnePassSyncDestinationSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
destinationConfig: { vaultId }
|
||||
destinationConfig: { vaultId, valueLabel }
|
||||
} = secretSync;
|
||||
|
||||
return <GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>;
|
||||
return (
|
||||
<>
|
||||
<GenericFieldLabel label="Vault ID">{vaultId}</GenericFieldLabel>
|
||||
<GenericFieldLabel label="Value Key">{valueLabel || "value"}</GenericFieldLabel>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user