fix(core): Improve SAML connection test result views (#5981)

* improve test result views

* refactor

* lint fix
This commit is contained in:
Michael Auerswald 2023-04-14 15:49:10 +02:00 committed by GitHub
parent 18d5156994
commit 4c994faec1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 16 deletions

View file

@ -13,7 +13,7 @@ import { getInitSSOFormView } from '../views/initSsoPost';
import { issueCookie } from '@/auth/jwt';
import { validate } from 'class-validator';
import type { PostBindingContext } from 'samlify/types/src/entity';
import { isSamlLicensedAndEnabled } from '../samlHelpers';
import { isConnectionTestRequest, isSamlLicensedAndEnabled } from '../samlHelpers';
import type { SamlLoginBinding } from '../types';
import { AuthenticatedRequest } from '@/requests';
import {
@ -21,6 +21,8 @@ import {
getServiceProviderEntityId,
getServiceProviderReturnUrl,
} from '../serviceProvider.ee';
import { getSamlConnectionTestSuccessView } from '../views/samlConnectionTestSuccess';
import { getSamlConnectionTestFailedView } from '../views/samlConnectionTestFailed';
@RestController('/sso/saml')
export class SamlController {
@ -106,11 +108,15 @@ export class SamlController {
res: express.Response,
binding: SamlLoginBinding,
) {
const loginResult = await this.samlService.handleSamlLogin(req, binding);
if (loginResult) {
// return attributes if this is a test connection
if (req.body.RelayState && req.body.RelayState === getServiceProviderConfigTestReturnUrl()) {
return res.status(202).send(loginResult.attributes);
try {
const loginResult = await this.samlService.handleSamlLogin(req, binding);
// if RelayState is set to the test connection Url, this is a test connection
if (isConnectionTestRequest(req)) {
if (loginResult.authenticatedUser) {
return res.send(getSamlConnectionTestSuccessView(loginResult.attributes));
} else {
return res.send(getSamlConnectionTestFailedView('', loginResult.attributes));
}
}
if (loginResult.authenticatedUser) {
// 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);
}
}
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);
}
throw new AuthError('SAML Authentication failed');
}
/**

View file

@ -139,14 +139,11 @@ export class SamlService {
async handleSamlLogin(
req: express.Request,
binding: SamlLoginBinding,
): Promise<
| {
authenticatedUser: User | undefined;
attributes: SamlUserAttributes;
onboardingRequired: boolean;
}
| undefined
> {
): Promise<{
authenticatedUser: User | undefined;
attributes: SamlUserAttributes;
onboardingRequired: boolean;
}> {
const attributes = await this.getAttributesFromLoginResponse(req, binding);
if (attributes.email) {
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> {

View file

@ -18,6 +18,8 @@ import {
isSamlCurrentAuthenticationMethod,
setCurrentAuthenticationMethod,
} 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
*/
@ -173,3 +175,7 @@ export function getMappedSamlAttributesFromFlowResult(
}
return result;
}
export function isConnectionTestRequest(req: SamlConfiguration.AcsRequest): boolean {
return req.body.RelayState === getServiceProviderConfigTestReturnUrl();
}

View 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>
`;
}

View 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>
`;
}