mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2026-08-11 06:10:20 +02:00
fix(core): Redact credentials (#13263)
This commit is contained in:
@@ -130,7 +130,7 @@ export class CredentialsController {
|
||||
}
|
||||
|
||||
const mergedCredentials = deepCopy(credentials);
|
||||
const decryptedData = this.credentialsService.decrypt(storedCredential);
|
||||
const decryptedData = this.credentialsService.decrypt(storedCredential, true);
|
||||
|
||||
// When a sharee (or project viewer) opens a credential, the fields and the
|
||||
// credential data are missing so the payload will be empty
|
||||
@@ -143,14 +143,14 @@ export class CredentialsController {
|
||||
mergedCredentials,
|
||||
);
|
||||
|
||||
if (mergedCredentials.data && storedCredential) {
|
||||
if (mergedCredentials.data) {
|
||||
mergedCredentials.data = this.credentialsService.unredact(
|
||||
mergedCredentials.data,
|
||||
decryptedData,
|
||||
);
|
||||
}
|
||||
|
||||
return await this.credentialsService.test(req.user, mergedCredentials);
|
||||
return await this.credentialsService.test(req.user.id, mergedCredentials);
|
||||
}
|
||||
|
||||
@Post('/')
|
||||
@@ -176,18 +176,22 @@ export class CredentialsController {
|
||||
@Patch('/:credentialId')
|
||||
@ProjectScope('credential:update')
|
||||
async updateCredentials(req: CredentialRequest.Update) {
|
||||
const { credentialId } = req.params;
|
||||
const {
|
||||
body,
|
||||
user,
|
||||
params: { credentialId },
|
||||
} = req;
|
||||
|
||||
const credential = await this.sharedCredentialsRepository.findCredentialForUser(
|
||||
credentialId,
|
||||
req.user,
|
||||
user,
|
||||
['credential:update'],
|
||||
);
|
||||
|
||||
if (!credential) {
|
||||
this.logger.info('Attempt to update credential blocked due to lack of permissions', {
|
||||
credentialId,
|
||||
userId: req.user.id,
|
||||
userId: user.id,
|
||||
});
|
||||
throw new NotFoundError(
|
||||
'Credential to be updated not found. You can only update credentials owned by you',
|
||||
@@ -199,6 +203,8 @@ export class CredentialsController {
|
||||
}
|
||||
|
||||
const decryptedData = this.credentialsService.decrypt(credential, true);
|
||||
// We never want to allow users to change the oauthTokenData
|
||||
delete body.data?.oauthTokenData;
|
||||
const preparedCredentialData = await this.credentialsService.prepareUpdateData(
|
||||
req.body,
|
||||
decryptedData,
|
||||
|
||||
@@ -87,7 +87,7 @@ export class EnterpriseCredentialsService {
|
||||
if (credential) {
|
||||
// Decrypt the data if we found the credential with the `credential:update`
|
||||
// scope.
|
||||
decryptedData = this.credentialsService.decrypt(credential, true);
|
||||
decryptedData = this.credentialsService.decrypt(credential);
|
||||
} else {
|
||||
// Otherwise try to find them with only the `credential:read` scope. In
|
||||
// that case we return them without the decrypted data.
|
||||
@@ -109,6 +109,11 @@ export class EnterpriseCredentialsService {
|
||||
const { data: _, ...rest } = credential;
|
||||
|
||||
if (decryptedData) {
|
||||
// We never want to expose the oauthTokenData to the frontend, but it
|
||||
// expects it to check if the credential is already connected.
|
||||
if (decryptedData?.oauthTokenData) {
|
||||
decryptedData.oauthTokenData = true;
|
||||
}
|
||||
return { data: decryptedData, ...rest };
|
||||
}
|
||||
|
||||
|
||||
@@ -112,9 +112,16 @@ export class CredentialsService {
|
||||
|
||||
if (includeData) {
|
||||
credentials = credentials.map((c: CredentialsEntity & ScopesField) => {
|
||||
const data = c.scopes.includes('credential:update') ? this.decrypt(c) : undefined;
|
||||
// We never want to expose the oauthTokenData to the frontend, but it
|
||||
// expects it to check if the credential is already connected.
|
||||
if (data?.oauthTokenData) {
|
||||
data.oauthTokenData = true;
|
||||
}
|
||||
|
||||
return {
|
||||
...c,
|
||||
data: c.scopes.includes('credential:update') ? this.decrypt(c) : undefined,
|
||||
data,
|
||||
} as unknown as CredentialsEntity;
|
||||
});
|
||||
}
|
||||
@@ -441,8 +448,8 @@ export class CredentialsService {
|
||||
await this.credentialsRepository.remove(credential);
|
||||
}
|
||||
|
||||
async test(user: User, credentials: ICredentialsDecrypted) {
|
||||
return await this.credentialsTester.testCredentials(user, credentials.type, credentials);
|
||||
async test(userId: User['id'], credentials: ICredentialsDecrypted) {
|
||||
return await this.credentialsTester.testCredentials(userId, credentials.type, credentials);
|
||||
}
|
||||
|
||||
// Take data and replace all sensitive values with a sentinel value.
|
||||
@@ -553,7 +560,7 @@ export class CredentialsService {
|
||||
if (sharing) {
|
||||
// Decrypt the data if we found the credential with the `credential:update`
|
||||
// scope.
|
||||
decryptedData = this.decrypt(sharing.credentials, true);
|
||||
decryptedData = this.decrypt(sharing.credentials);
|
||||
} else {
|
||||
// Otherwise try to find them with only the `credential:read` scope. In
|
||||
// that case we return them without the decrypted data.
|
||||
@@ -569,6 +576,11 @@ export class CredentialsService {
|
||||
const { data: _, ...rest } = credential;
|
||||
|
||||
if (decryptedData) {
|
||||
// We never want to expose the oauthTokenData to the frontend, but it
|
||||
// expects it to check if the credential is already connected.
|
||||
if (decryptedData?.oauthTokenData) {
|
||||
decryptedData.oauthTokenData = true;
|
||||
}
|
||||
return { data: decryptedData, ...rest };
|
||||
}
|
||||
return { ...rest };
|
||||
|
||||
@@ -172,7 +172,7 @@ export class CredentialsTester {
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
async testCredentials(
|
||||
user: User,
|
||||
userId: User['id'],
|
||||
credentialType: string,
|
||||
credentialsDecrypted: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
@@ -186,7 +186,7 @@ export class CredentialsTester {
|
||||
|
||||
if (credentialsDecrypted.data) {
|
||||
try {
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(userId);
|
||||
credentialsDecrypted.data = this.credentialsHelper.applyDefaultsAndOverwrites(
|
||||
additionalData,
|
||||
credentialsDecrypted.data,
|
||||
@@ -292,7 +292,7 @@ export class CredentialsTester {
|
||||
},
|
||||
};
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(user.id, node.parameters);
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(userId, node.parameters);
|
||||
|
||||
const executeData: IExecuteData = { node, data: {}, source: null };
|
||||
const executeFunctions = new ExecuteContext(
|
||||
|
||||
Reference in New Issue
Block a user