n8n/packages/editor-ui/src/stores/credentials.ts

306 lines
11 KiB
TypeScript
Raw Normal View History

refactor(editor): Finish pinia migration, remove all vuex dependancies (#4533) * ✨ Added pinia support. Migrated community nodes module. * ✨ Added ui pinia store, moved some data from root store to it, updated modals to work with pinia stores * ✨ Added ui pinia store and migrated a part of the root store * ✨ Migrated `settings` store to pinia * ✨ Removing vuex store refs from router * ✨ Migrated `users` module to pinia store * ⚡ Fixing errors after sync with master * ⚡ One more error after merge * ⚡ Created `workflows` pinia store. Moved large part of root store to it. Started updating references. * ✨ Finished migrating workflows store to pinia * ⚡ Renaming some getters and actions to make more sense * ✨ Finished migrating the root store to pinia * ✨ Migrated ndv store to pinia * ⚡ Renaming main panel dimensions getter so it doesn't clash with data prop name * ✔️ Fixing lint errors * ✨ Migrated `templates` store to pinia * ✨ Migrated the `nodeTypes`store * ⚡ Removed unused pieces of code and oold vuex modules * ✨ Adding vuex calls to pinia store, fixing wrong references * 💄 Removing leftover $store refs * ⚡ Added legacy getters and mutations to store to support webhooks * ⚡ Added missing front-end hooks, updated vuex state subscriptions to pinia * ✔️ Fixing linting errors * ⚡ Removing vue composition api plugin * ⚡ Fixing main sidebar state when loading node view * 🐛 Fixing an error when activating workflows * 🐛 Fixing isses with workflow settings and executions auto-refresh * 🐛 Removing duplicate listeners which cause import error * 🐛 Fixing route authentication * ⚡ Updating freshly pulled $store refs * ⚡ Adding deleted const * ⚡ Updating store references in ee features. Reseting NodeView credentials update flag when resetting workspace * ⚡ Adding return type to email submission modal * ⚡ Making NodeView only react to paste event when active * 🐛 Fixing signup view errors * ✨ Started migrating the `credentials` module to pinia * 👌 Addressing PR review comments * ✨ Migrated permissions module to pinia * ✨ Migrated `nodeCreator`, `tags` and `versions` modules to pinia * ✨ Implemented webhooks pinia store * ⚡ Removing all leftover vuex files and references * ✨ Removing final vuex refs * ⚡ Updating expected credentialId type * ⚡ Removing node credentials subscription code, reducing node click debounce timeout * 🐛 Fixing pushing nodes downstream when inserting new node * ✔️ Fixing a lint error in new type guard * ⚡ Updating helper reference * ✔️ Removing unnecessary awaits * ⚡ fix(editor): remove unnecessary imports from NDV * ⚡ Merging mapStores blocks in NodeView * ⚡ fix(editor): make sure JS Plumb not loaded earlier than needed * ⚡ Updating type guard nad credentials subscriptions * ⚡ Updating type guard so it doesn't use `any` type Co-authored-by: Csaba Tuncsik <csaba@n8n.io>
2022-11-09 01:01:50 -08:00
import { createNewCredential, deleteCredential, getAllCredentials, getCredentialData, getCredentialsNewName, getCredentialTypes, getForeignCredentials, oAuth1CredentialAuthorize, oAuth2CredentialAuthorize, testCredential, updateCredential } from "@/api/credentials";
import { setCredentialSharedWith } from "@/api/credentials.ee";
import { getAppNameFromCredType } from "@/components/helpers";
import { EnterpriseEditionFeature, STORES } from "@/constants";
import { ICredentialMap, ICredentialsDecryptedResponse, ICredentialsResponse, ICredentialsState, ICredentialTypeMap } from "@/Interface";
import { i18n } from "@/plugins/i18n";
import { ICredentialsDecrypted, ICredentialType, INodeCredentialTestResult, INodeProperties, INodeTypeDescription, IUser } from "n8n-workflow";
import { defineStore } from "pinia";
import Vue from "vue";
import { useRootStore } from "./n8nRootStore";
import { useNodeTypesStore } from "./nodeTypes";
import { useSettingsStore } from "./settings";
import { useUsersStore } from "./users";
const DEFAULT_CREDENTIAL_NAME = 'Unnamed credential';
const DEFAULT_CREDENTIAL_POSTFIX = 'account';
const TYPES_WITH_DEFAULT_NAME = ['httpBasicAuth', 'oAuth2Api', 'httpDigestAuth', 'oAuth1Api'];
export const useCredentialsStore = defineStore(STORES.CREDENTIALS, {
state: (): ICredentialsState => ({
credentialTypes: {},
credentials: {},
}),
getters: {
credentialTypesById(): Record<ICredentialType['name'], ICredentialType> {
return this.credentialTypes;
},
allCredentialTypes(): ICredentialType[] {
return Object.values(this.credentialTypes)
.sort((a, b) => a.displayName.localeCompare(b.displayName));
},
allCredentials(): ICredentialsResponse[] {
return Object.values(this.credentials)
.sort((a, b) => a.name.localeCompare(b.name));
},
allForeignCredentials(): ICredentialsResponse[] {
return Object.values(this.foreignCredentials || {})
.sort((a, b) => a.name.localeCompare(b.name));
},
allCredentialsByType(): {[type: string]: ICredentialsResponse[]} {
const credentials = this.allCredentials;
const types = this.allCredentialTypes;
return types.reduce((accu: {[type: string]: ICredentialsResponse[]}, type: ICredentialType) => {
accu[type.name] = credentials.filter((cred: ICredentialsResponse) => cred.type === type.name);
return accu;
}, {});
},
getCredentialTypeByName() {
return (type: string): ICredentialType => this.credentialTypes[type];
},
getCredentialById() {
return (id: string): ICredentialsResponse => this.credentials[id];
},
getCredentialByIdAndType() {
return (id: string, type: string): ICredentialsResponse | undefined => {
const credential = this.credentials[id];
return !credential || credential.type !== type ? undefined : credential;
};
},
getCredentialsByType() {
return (credentialType: string): ICredentialsResponse[] => {
return (this.allCredentialsByType[credentialType] || []);
};
},
getNodesWithAccess() {
return (credentialTypeName: string) => {
const nodeTypesStore = useNodeTypesStore();
const allLatestNodeTypes: INodeTypeDescription[] = nodeTypesStore.allLatestNodeTypes;
return allLatestNodeTypes.filter((nodeType: INodeTypeDescription) => {
if (!nodeType.credentials) {
return false;
}
for (const credentialTypeDescription of nodeType.credentials) {
if (credentialTypeDescription.name === credentialTypeName ) {
return true;
}
}
return false;
});
};
},
getScopesByCredentialType() {
return (credentialTypeName: string) => {
const credentialType = this.getCredentialTypeByName(credentialTypeName) as {
properties: INodeProperties[];
};
const scopeProperty = credentialType.properties.find((p) => p.name === 'scope');
if (
!scopeProperty ||
!scopeProperty.default ||
typeof scopeProperty.default !== 'string' ||
scopeProperty.default === ''
) {
return [];
}
let { default: scopeDefault } = scopeProperty;
// disregard expressions for display
scopeDefault = scopeDefault.replace(/^=/, '').replace(/\{\{.*\}\}/, '');
if (/ /.test(scopeDefault)) return scopeDefault.split(' ');
if (/,/.test(scopeDefault)) return scopeDefault.split(',');
return [scopeDefault];
};
},
getCredentialOwnerName() {
return (credentialId: string): string => {
const credential = this.getCredentialById(credentialId);
return credential && credential.ownedBy && credential.ownedBy.firstName
? `${credential.ownedBy.firstName} ${credential.ownedBy.lastName} (${credential.ownedBy.email})`
: i18n.baseText('credentialEdit.credentialSharing.info.sharee.fallback');
};
},
},
actions: {
setCredentialTypes(credentialTypes: ICredentialType[]): void {
this.credentialTypes = credentialTypes.reduce((accu: ICredentialTypeMap, cred: ICredentialType) => {
accu[cred.name] = cred;
return accu;
}, {});
},
setCredentials(credentials: ICredentialsResponse[]): void {
this.credentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
setForeignCredentials(credentials: ICredentialsResponse[]): void {
this.foreignCredentials = credentials.reduce((accu: ICredentialMap, cred: ICredentialsResponse) => {
if (cred.id) {
accu[cred.id] = cred;
}
return accu;
}, {});
},
upsertCredential(credential: ICredentialsResponse): void {
if (credential.id) {
Vue.set(this.credentials, credential.id, { ...this.credentials[credential.id], ...credential });
}
},
enableOAuthCredential(credential: ICredentialsResponse): void {
// enable oauth event to track change between modals
},
async fetchCredentialTypes(forceFetch: boolean): Promise<void> {
if (this.allCredentialTypes.length > 0 && forceFetch !== true) {
return;
}
const rootStore = useRootStore();
const credentialTypes = await getCredentialTypes(rootStore.getRestApiContext);
this.setCredentialTypes(credentialTypes);
},
async fetchAllCredentials(): Promise<ICredentialsResponse[]> {
const rootStore = useRootStore();
const credentials = await getAllCredentials(rootStore.getRestApiContext);
this.setCredentials(credentials);
return credentials;
},
async fetchForeignCredentials(): Promise<ICredentialsResponse[]> {
const rootStore = useRootStore();
const credentials = await getForeignCredentials(rootStore.getRestApiContext);
this.setForeignCredentials(credentials);
return credentials;
},
async getCredentialData({ id }: {id: string}): Promise<ICredentialsResponse | ICredentialsDecryptedResponse | undefined> {
const rootStore = useRootStore();
return await getCredentialData(rootStore.getRestApiContext, id);
},
async createNewCredential(data: ICredentialsDecrypted): Promise<ICredentialsResponse> {
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await createNewCredential(rootStore.getRestApiContext, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
this.upsertCredential(credential);
if (data.ownedBy) {
this.setCredentialOwnedBy({
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
this.setCredentialSharedWith({
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
this.upsertCredential(credential);
}
return credential;
},
async updateCredential(params: {data: ICredentialsDecrypted, id: string}): Promise<ICredentialsResponse> {
const { id, data } = params;
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credential = await updateCredential(rootStore.getRestApiContext, id, data);
if (settingsStore.isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
this.upsertCredential(credential);
if (data.ownedBy) {
this.setCredentialOwnedBy({
credentialId: credential.id,
ownedBy: data.ownedBy,
});
const usersStore = useUsersStore();
if (data.sharedWith && data.ownedBy.id === usersStore.currentUserId) {
this.setCredentialSharedWith({
credentialId: credential.id,
sharedWith: data.sharedWith,
});
}
}
} else {
this.upsertCredential(credential);
}
return credential;
},
async deleteCredential({ id }: {id: string}): void {
const rootStore = useRootStore();
const deleted = await deleteCredential(rootStore.getRestApiContext, id);
if (deleted) {
Vue.delete(this.credentials, id);
}
},
async oAuth2Authorize(data: ICredentialsResponse): Promise<string> {
const rootStore = useRootStore();
return oAuth2CredentialAuthorize(rootStore.getRestApiContext, data);
},
async oAuth1Authorize(data: ICredentialsResponse): Promise<string> {
const rootStore = useRootStore();
return oAuth1CredentialAuthorize(rootStore.getRestApiContext, data);
},
async testCredential(data: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
const rootStore = useRootStore();
return testCredential(rootStore.getRestApiContext, { credentials: data });
},
async getNewCredentialName(params: { credentialTypeName: string }): Promise<string> {
try {
const { credentialTypeName } = params;
let newName = DEFAULT_CREDENTIAL_NAME;
if (!TYPES_WITH_DEFAULT_NAME.includes(credentialTypeName)) {
const { displayName } = this.getCredentialTypeByName(credentialTypeName);
newName = getAppNameFromCredType(displayName);
newName = newName.length > 0 ? `${newName} ${DEFAULT_CREDENTIAL_POSTFIX}` : DEFAULT_CREDENTIAL_NAME;
}
const rootStore = useRootStore();
const res = await getCredentialsNewName(rootStore.getRestApiContext, newName);
return res.name;
} catch (e) {
return DEFAULT_CREDENTIAL_NAME;
}
},
// Enterprise edition actions
setCredentialOwnedBy(payload: { credentialId: string, ownedBy: Partial<IUser> }): void {
Vue.set(this.credentials[payload.credentialId], 'ownedBy', payload.ownedBy);
},
async setCredentialSharedWith(payload: { sharedWith: IUser[]; credentialId: string; }): void {
if(useSettingsStore().isEnterpriseFeatureEnabled(EnterpriseEditionFeature.Sharing)) {
await setCredentialSharedWith(
useRootStore().getRestApiContext,
payload.credentialId,
{
shareWithIds: payload.sharedWith.map((sharee) => sharee.id),
},
);
Vue.set(this.credentials[payload.credentialId], 'sharedWith', payload.sharedWith);
}
},
addCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || []).concat([payload.sharee]),
);
},
removeCredentialSharee(payload: { credentialId: string, sharee: Partial<IUser> }): void {
Vue.set(
this.credentials[payload.credentialId],
'sharedWith',
(this.credentials[payload.credentialId].sharedWith || [])
.filter((sharee) => sharee.id !== payload.sharee.id),
);
},
},
});