mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(editor): Use BroadcastChannel instead of window.opener for OAuth callback window (#9779)
This commit is contained in:
parent
a1046607bf
commit
87cb199745
46
cypress/e2e/43-oauth-flow.cy.ts
Normal file
46
cypress/e2e/43-oauth-flow.cy.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { CredentialsPage, CredentialsModal } from '../pages';
|
||||||
|
|
||||||
|
const credentialsPage = new CredentialsPage();
|
||||||
|
const credentialsModal = new CredentialsModal();
|
||||||
|
|
||||||
|
describe('Credentials', () => {
|
||||||
|
it('create and connect with Google OAuth2', () => {
|
||||||
|
// Open credentials page
|
||||||
|
cy.visit(credentialsPage.url, {
|
||||||
|
onBeforeLoad(win) {
|
||||||
|
cy.stub(win, 'open').as('windowOpen');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a new Google OAuth2 credential
|
||||||
|
credentialsPage.getters.emptyListCreateCredentialButton().click();
|
||||||
|
credentialsModal.getters.newCredentialTypeOption('Google OAuth2 API').click();
|
||||||
|
credentialsModal.getters.newCredentialTypeButton().click();
|
||||||
|
|
||||||
|
// Fill in the key/secret and save
|
||||||
|
credentialsModal.actions.fillField('clientId', 'test-key');
|
||||||
|
credentialsModal.actions.fillField('clientSecret', 'test-secret');
|
||||||
|
credentialsModal.actions.save();
|
||||||
|
|
||||||
|
// Connect to Google
|
||||||
|
credentialsModal.getters.oauthConnectButton().click();
|
||||||
|
cy.get('@windowOpen').should(
|
||||||
|
'have.been.calledOnceWith',
|
||||||
|
Cypress.sinon.match(
|
||||||
|
'https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&prompt=consent&client_id=test-key&redirect_uri=http%3A%2F%2Flocalhost%3A5678%2Frest%2Foauth2-credential%2Fcallback&response_type=code',
|
||||||
|
),
|
||||||
|
'OAuth Authorization',
|
||||||
|
'scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Emulate successful save using BroadcastChannel
|
||||||
|
cy.window().then(() => {
|
||||||
|
const channel = new BroadcastChannel('oauth-callback');
|
||||||
|
channel.postMessage('success');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check that the credential was saved and connected successfully
|
||||||
|
credentialsModal.getters.saveButton().should('contain.text', 'Saved');
|
||||||
|
credentialsModal.getters.oauthConnectSuccessBanner().should('be.visible');
|
||||||
|
});
|
||||||
|
});
|
|
@ -9,15 +9,16 @@ export class CredentialsModal extends BasePage {
|
||||||
newCredentialTypeOption: (credentialType: string) =>
|
newCredentialTypeOption: (credentialType: string) =>
|
||||||
cy.getByTestId('new-credential-type-select-option').contains(credentialType),
|
cy.getByTestId('new-credential-type-select-option').contains(credentialType),
|
||||||
newCredentialTypeButton: () => cy.getByTestId('new-credential-type-button'),
|
newCredentialTypeButton: () => cy.getByTestId('new-credential-type-button'),
|
||||||
connectionParameters: () => cy.getByTestId('credential-connection-parameter'),
|
|
||||||
connectionParameter: (fieldName: string) =>
|
connectionParameter: (fieldName: string) =>
|
||||||
this.getters.connectionParameters().find(`:contains('${fieldName}') .n8n-input input`),
|
this.getters.credentialInputs().find(`:contains('${fieldName}') .n8n-input input`),
|
||||||
name: () => cy.getByTestId('credential-name'),
|
name: () => cy.getByTestId('credential-name'),
|
||||||
nameInput: () => cy.getByTestId('credential-name').find('input'),
|
nameInput: () => cy.getByTestId('credential-name').find('input'),
|
||||||
// Saving of the credentials takes a while on the CI so we need to increase the timeout
|
// Saving of the credentials takes a while on the CI so we need to increase the timeout
|
||||||
saveButton: () => cy.getByTestId('credential-save-button', { timeout: 5000 }),
|
saveButton: () => cy.getByTestId('credential-save-button', { timeout: 5000 }),
|
||||||
deleteButton: () => cy.getByTestId('credential-delete-button'),
|
deleteButton: () => cy.getByTestId('credential-delete-button'),
|
||||||
closeButton: () => this.getters.editCredentialModal().find('.el-dialog__close').first(),
|
closeButton: () => this.getters.editCredentialModal().find('.el-dialog__close').first(),
|
||||||
|
oauthConnectButton: () => cy.getByTestId('oauth-connect-button'),
|
||||||
|
oauthConnectSuccessBanner: () => cy.getByTestId('oauth-connect-success-banner'),
|
||||||
credentialsEditModal: () => cy.getByTestId('credential-edit-dialog'),
|
credentialsEditModal: () => cy.getByTestId('credential-edit-dialog'),
|
||||||
credentialsAuthTypeSelector: () => cy.getByTestId('node-auth-type-selector'),
|
credentialsAuthTypeSelector: () => cy.getByTestId('node-auth-type-selector'),
|
||||||
credentialAuthTypeRadioButtons: () =>
|
credentialAuthTypeRadioButtons: () =>
|
||||||
|
@ -69,6 +70,13 @@ export class CredentialsModal extends BasePage {
|
||||||
this.getters.closeButton().click();
|
this.getters.closeButton().click();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fillField: (fieldName: string, value: string) => {
|
||||||
|
this.getters
|
||||||
|
.credentialInputs()
|
||||||
|
.getByTestId(`parameter-input-${fieldName}`)
|
||||||
|
.find('input')
|
||||||
|
.type(value);
|
||||||
|
},
|
||||||
createNewCredential: (type: string, closeModal = true) => {
|
createNewCredential: (type: string, closeModal = true) => {
|
||||||
this.getters.newCredentialModal().should('be.visible');
|
this.getters.newCredentialModal().should('be.visible');
|
||||||
this.getters.newCredentialTypeSelect().should('be.visible');
|
this.getters.newCredentialTypeSelect().should('be.visible');
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
<html>
|
<html>
|
||||||
<script>
|
<script>
|
||||||
(function messageParent() {
|
(function messageParent() {
|
||||||
window.opener.postMessage('success', '*');
|
const broadcastChannel = new BroadcastChannel('oauth-callback');
|
||||||
|
broadcastChannel.postMessage('success');
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
Failed to connect. The window can be closed now.
|
Failed to connect. The window can be closed now.
|
||||||
<script>
|
<script>
|
||||||
(function messageParent() { window.opener?.postMessage('error', '*'); })();
|
(function messageParent() {
|
||||||
|
const broadcastChannel = new BroadcastChannel('oauth-callback');
|
||||||
|
broadcastChannel.postMessage('error');
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -39,6 +39,7 @@
|
||||||
:button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
|
:button-label="$locale.baseText('credentialEdit.credentialConfig.reconnect')"
|
||||||
:button-title="$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')"
|
:button-title="$locale.baseText('credentialEdit.credentialConfig.reconnectOAuth2Credential')"
|
||||||
@click="$emit('oauth')"
|
@click="$emit('oauth')"
|
||||||
|
data-test-id="oauth-connect-success-banner"
|
||||||
>
|
>
|
||||||
<template v-if="isGoogleOAuthType" #button>
|
<template v-if="isGoogleOAuthType" #button>
|
||||||
<p
|
<p
|
||||||
|
@ -118,6 +119,7 @@
|
||||||
"
|
"
|
||||||
:is-google-o-auth-type="isGoogleOAuthType"
|
:is-google-o-auth-type="isGoogleOAuthType"
|
||||||
@click="$emit('oauth')"
|
@click="$emit('oauth')"
|
||||||
|
data-test-id="oauth-connect-button"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<n8n-text v-if="isMissingCredentials" color="text-base" size="medium">
|
<n8n-text v-if="isMissingCredentials" color="text-base" size="medium">
|
||||||
|
|
|
@ -1090,20 +1090,17 @@ export default defineComponent({
|
||||||
|
|
||||||
const params =
|
const params =
|
||||||
'scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700';
|
'scrollbars=no,resizable=yes,status=no,titlebar=noe,location=no,toolbar=no,menubar=no,width=500,height=700';
|
||||||
const oauthPopup = window.open(url, 'OAuth2 Authorization', params);
|
const oauthPopup = window.open(url, 'OAuth Authorization', params);
|
||||||
|
|
||||||
this.credentialData = {
|
this.credentialData = {
|
||||||
...this.credentialData,
|
...this.credentialData,
|
||||||
oauthTokenData: null as unknown as CredentialInformation,
|
oauthTokenData: null as unknown as CredentialInformation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const oauthChannel = new BroadcastChannel('oauth-callback');
|
||||||
const receiveMessage = (event: MessageEvent) => {
|
const receiveMessage = (event: MessageEvent) => {
|
||||||
// // TODO: Add check that it came from n8n
|
|
||||||
// if (event.origin !== 'http://example.org:8080') {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
if (event.data === 'success') {
|
if (event.data === 'success') {
|
||||||
window.removeEventListener('message', receiveMessage, false);
|
oauthChannel.removeEventListener('message', receiveMessage);
|
||||||
|
|
||||||
// Set some kind of data that status changes.
|
// Set some kind of data that status changes.
|
||||||
// As data does not get displayed directly it does not matter what data.
|
// As data does not get displayed directly it does not matter what data.
|
||||||
|
@ -1118,8 +1115,7 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
oauthChannel.addEventListener('message', receiveMessage);
|
||||||
window.addEventListener('message', receiveMessage, false);
|
|
||||||
},
|
},
|
||||||
async onAuthTypeChanged(type: string): Promise<void> {
|
async onAuthTypeChanged(type: string): Promise<void> {
|
||||||
if (!this.activeNodeType?.credentials) {
|
if (!this.activeNodeType?.credentials) {
|
||||||
|
|
Loading…
Reference in a new issue