mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
fix(core): Improve SAML connection test result views (#5981)
* improve test result views * refactor * lint fix
This commit is contained in:
parent
18d5156994
commit
4c994faec1
|
@ -13,7 +13,7 @@ import { getInitSSOFormView } from '../views/initSsoPost';
|
||||||
import { issueCookie } from '@/auth/jwt';
|
import { issueCookie } from '@/auth/jwt';
|
||||||
import { validate } from 'class-validator';
|
import { validate } from 'class-validator';
|
||||||
import type { PostBindingContext } from 'samlify/types/src/entity';
|
import type { PostBindingContext } from 'samlify/types/src/entity';
|
||||||
import { isSamlLicensedAndEnabled } from '../samlHelpers';
|
import { isConnectionTestRequest, isSamlLicensedAndEnabled } from '../samlHelpers';
|
||||||
import type { SamlLoginBinding } from '../types';
|
import type { SamlLoginBinding } from '../types';
|
||||||
import { AuthenticatedRequest } from '@/requests';
|
import { AuthenticatedRequest } from '@/requests';
|
||||||
import {
|
import {
|
||||||
|
@ -21,6 +21,8 @@ import {
|
||||||
getServiceProviderEntityId,
|
getServiceProviderEntityId,
|
||||||
getServiceProviderReturnUrl,
|
getServiceProviderReturnUrl,
|
||||||
} from '../serviceProvider.ee';
|
} from '../serviceProvider.ee';
|
||||||
|
import { getSamlConnectionTestSuccessView } from '../views/samlConnectionTestSuccess';
|
||||||
|
import { getSamlConnectionTestFailedView } from '../views/samlConnectionTestFailed';
|
||||||
|
|
||||||
@RestController('/sso/saml')
|
@RestController('/sso/saml')
|
||||||
export class SamlController {
|
export class SamlController {
|
||||||
|
@ -106,11 +108,15 @@ export class SamlController {
|
||||||
res: express.Response,
|
res: express.Response,
|
||||||
binding: SamlLoginBinding,
|
binding: SamlLoginBinding,
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
const loginResult = await this.samlService.handleSamlLogin(req, binding);
|
const loginResult = await this.samlService.handleSamlLogin(req, binding);
|
||||||
if (loginResult) {
|
// if RelayState is set to the test connection Url, this is a test connection
|
||||||
// return attributes if this is a test connection
|
if (isConnectionTestRequest(req)) {
|
||||||
if (req.body.RelayState && req.body.RelayState === getServiceProviderConfigTestReturnUrl()) {
|
if (loginResult.authenticatedUser) {
|
||||||
return res.status(202).send(loginResult.attributes);
|
return res.send(getSamlConnectionTestSuccessView(loginResult.attributes));
|
||||||
|
} else {
|
||||||
|
return res.send(getSamlConnectionTestFailedView('', loginResult.attributes));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (loginResult.authenticatedUser) {
|
if (loginResult.authenticatedUser) {
|
||||||
// Only sign in user if SAML is enabled, otherwise treat as test connection
|
// Only sign in user if SAML is enabled, otherwise treat as test connection
|
||||||
|
@ -125,8 +131,13 @@ export class SamlController {
|
||||||
return res.status(202).send(loginResult.attributes);
|
return res.status(202).send(loginResult.attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
throw new AuthError('SAML Authentication failed');
|
throw new AuthError('SAML Authentication failed');
|
||||||
|
} catch (error) {
|
||||||
|
if (isConnectionTestRequest(req)) {
|
||||||
|
return res.send(getSamlConnectionTestFailedView((error as Error).message));
|
||||||
|
}
|
||||||
|
throw new AuthError('SAML Authentication failed: ' + (error as Error).message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -139,14 +139,11 @@ export class SamlService {
|
||||||
async handleSamlLogin(
|
async handleSamlLogin(
|
||||||
req: express.Request,
|
req: express.Request,
|
||||||
binding: SamlLoginBinding,
|
binding: SamlLoginBinding,
|
||||||
): Promise<
|
): Promise<{
|
||||||
| {
|
|
||||||
authenticatedUser: User | undefined;
|
authenticatedUser: User | undefined;
|
||||||
attributes: SamlUserAttributes;
|
attributes: SamlUserAttributes;
|
||||||
onboardingRequired: boolean;
|
onboardingRequired: boolean;
|
||||||
}
|
}> {
|
||||||
| undefined
|
|
||||||
> {
|
|
||||||
const attributes = await this.getAttributesFromLoginResponse(req, binding);
|
const attributes = await this.getAttributesFromLoginResponse(req, binding);
|
||||||
if (attributes.email) {
|
if (attributes.email) {
|
||||||
const user = await Db.collections.User.findOne({
|
const user = await Db.collections.User.findOne({
|
||||||
|
@ -187,7 +184,11 @@ export class SamlService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return undefined;
|
return {
|
||||||
|
authenticatedUser: undefined,
|
||||||
|
attributes,
|
||||||
|
onboardingRequired: false,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async setSamlPreferences(prefs: SamlPreferences): Promise<SamlPreferences | undefined> {
|
async setSamlPreferences(prefs: SamlPreferences): Promise<SamlPreferences | undefined> {
|
||||||
|
|
|
@ -18,6 +18,8 @@ import {
|
||||||
isSamlCurrentAuthenticationMethod,
|
isSamlCurrentAuthenticationMethod,
|
||||||
setCurrentAuthenticationMethod,
|
setCurrentAuthenticationMethod,
|
||||||
} from '../ssoHelpers';
|
} from '../ssoHelpers';
|
||||||
|
import { getServiceProviderConfigTestReturnUrl } from './serviceProvider.ee';
|
||||||
|
import type { SamlConfiguration } from './types/requests';
|
||||||
/**
|
/**
|
||||||
* Check whether the SAML feature is licensed and enabled in the instance
|
* Check whether the SAML feature is licensed and enabled in the instance
|
||||||
*/
|
*/
|
||||||
|
@ -173,3 +175,7 @@ export function getMappedSamlAttributesFromFlowResult(
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isConnectionTestRequest(req: SamlConfiguration.AcsRequest): boolean {
|
||||||
|
return req.body.RelayState === getServiceProviderConfigTestReturnUrl();
|
||||||
|
}
|
||||||
|
|
42
packages/cli/src/sso/saml/views/samlConnectionTestFailed.ts
Normal file
42
packages/cli/src/sso/saml/views/samlConnectionTestFailed.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import type { SamlUserAttributes } from '../types/samlUserAttributes';
|
||||||
|
|
||||||
|
export function getSamlConnectionTestFailedView(
|
||||||
|
message: string,
|
||||||
|
attributes?: SamlUserAttributes,
|
||||||
|
): string {
|
||||||
|
return `
|
||||||
|
<http>
|
||||||
|
<head>
|
||||||
|
<title>n8n - SAML Connection Test Result</title>
|
||||||
|
<style>
|
||||||
|
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
||||||
|
h1 { color: rgb(240, 60, 60); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
||||||
|
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
||||||
|
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="text-align:center">
|
||||||
|
<h1>SAML Connection Test failed</h1>
|
||||||
|
<h2>${message ?? 'A common issue could be that no email attribute is set'}</h2>
|
||||||
|
<button onclick="window.close()">You can close this window now</button>
|
||||||
|
<p></p>
|
||||||
|
${
|
||||||
|
attributes
|
||||||
|
? `
|
||||||
|
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email:</strong> ${attributes?.email ?? '(n/a)'}</li>
|
||||||
|
<li><strong>First Name:</strong> ${attributes?.firstName ?? '(n/a)'}</li>
|
||||||
|
<li><strong>Last Name:</strong> ${attributes?.lastName ?? '(n/a)'}</li>
|
||||||
|
<li><strong>UPN:</strong> ${attributes?.userPrincipalName ?? '(n/a)'}</li>
|
||||||
|
</ul>`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</http>
|
||||||
|
`;
|
||||||
|
}
|
33
packages/cli/src/sso/saml/views/samlConnectionTestSuccess.ts
Normal file
33
packages/cli/src/sso/saml/views/samlConnectionTestSuccess.ts
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import type { SamlUserAttributes } from '../types/samlUserAttributes';
|
||||||
|
|
||||||
|
export function getSamlConnectionTestSuccessView(attributes: SamlUserAttributes): string {
|
||||||
|
return `
|
||||||
|
<http>
|
||||||
|
<head>
|
||||||
|
<title>n8n - SAML Connection Test Result</title>
|
||||||
|
<style>
|
||||||
|
body { background: rgb(251,252,254); font-family: 'Open Sans', sans-serif; padding: 10px; margin: auto; width: 500px; top: 40%; position: relative; }
|
||||||
|
h1 { color: rgb(0, 0, 0); font-size: 16px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
h2 { color: rgb(0, 0, 0); font-size: 12px; font-weight: 400; margin: 0 0 10px 0; }
|
||||||
|
button { border: 1px solid rgb(219, 223, 231); background: rgb(255, 255, 255); border-radius: 4px; padding: 10px; }
|
||||||
|
ul { border: 1px solid rgb(219, 223, 231); border-radius: 4px; padding: 10px; }
|
||||||
|
li { decoration: none; list-style: none; margin: 0 0 0px 0; color: rgb(125, 125, 125); font-size: 12px;}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="text-align:center">
|
||||||
|
<h1>SAML Connection Test was successful</h1>
|
||||||
|
<button onclick="window.close()">You can close this window now</button>
|
||||||
|
<p></p>
|
||||||
|
<h2>Here are the attributes returned by your SAML IdP:</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Email:</strong> ${attributes.email ?? '(n/a)'}</li>
|
||||||
|
<li><strong>First Name:</strong> ${attributes.firstName ?? '(n/a)'}</li>
|
||||||
|
<li><strong>Last Name:</strong> ${attributes.lastName ?? '(n/a)'}</li>
|
||||||
|
<li><strong>UPN:</strong> ${attributes.userPrincipalName ?? '(n/a)'}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</http>
|
||||||
|
`;
|
||||||
|
}
|
Loading…
Reference in a new issue