n8n
Version:
n8n Workflow Automation Tool
338 lines • 16.2 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SamlService = void 0;
const typedi_1 = __importStar(require("typedi"));
const n8n_workflow_1 = require("n8n-workflow");
const serviceProvider_ee_1 = require("./serviceProvider.ee");
const ssoHelpers_1 = require("../ssoHelpers");
const constants_1 = require("./constants");
const samlHelpers_1 = require("./samlHelpers");
const axios_1 = __importDefault(require("axios"));
const https_1 = __importDefault(require("https"));
const samlValidator_1 = require("./samlValidator");
const Logger_1 = require("../../Logger");
const user_repository_1 = require("../../databases/repositories/user.repository");
const settings_repository_1 = require("../../databases/repositories/settings.repository");
const bad_request_error_1 = require("../../errors/response-errors/bad-request.error");
const auth_error_1 = require("../../errors/response-errors/auth.error");
const url_service_1 = require("../../services/url.service");
let SamlService = class SamlService {
get samlPreferences() {
return {
...this._samlPreferences,
loginEnabled: (0, samlHelpers_1.isSamlLoginEnabled)(),
loginLabel: (0, samlHelpers_1.getSamlLoginLabel)(),
};
}
constructor(logger, urlService) {
this.logger = logger;
this.urlService = urlService;
this._samlPreferences = {
mapping: {
email: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress',
firstName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstname',
lastName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/lastname',
userPrincipalName: 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn',
},
metadata: '',
metadataUrl: '',
ignoreSSL: false,
loginBinding: 'redirect',
acsBinding: 'post',
authnRequestsSigned: false,
loginEnabled: false,
loginLabel: 'SAML',
wantAssertionsSigned: true,
wantMessageSigned: true,
relayState: this.urlService.getInstanceBaseUrl(),
signatureConfig: {
prefix: 'ds',
location: {
reference: '/samlp:Response/saml:Issuer',
action: 'after',
},
},
};
}
async init() {
await this.loadFromDbAndApplySamlPreferences(false);
if ((0, samlHelpers_1.isSamlLicensedAndEnabled)()) {
await this.loadSamlify();
await this.loadFromDbAndApplySamlPreferences(true);
}
}
async loadSamlify() {
if (this.samlify === undefined) {
this.logger.debug('Loading samlify library into memory');
this.samlify = await Promise.resolve().then(() => __importStar(require('samlify')));
}
this.samlify.setSchemaValidator({
validate: async (response) => {
const valid = await (0, samlValidator_1.validateResponse)(response);
if (!valid) {
throw new n8n_workflow_1.ApplicationError('Invalid SAML response');
}
},
});
}
getIdentityProviderInstance(forceRecreate = false) {
if (this.samlify === undefined) {
throw new n8n_workflow_1.ApplicationError('Samlify is not initialized');
}
if (this.identityProviderInstance === undefined || forceRecreate) {
this.identityProviderInstance = this.samlify.IdentityProvider({
metadata: this._samlPreferences.metadata,
});
}
return this.identityProviderInstance;
}
getServiceProviderInstance() {
if (this.samlify === undefined) {
throw new n8n_workflow_1.ApplicationError('Samlify is not initialized');
}
return (0, serviceProvider_ee_1.getServiceProviderInstance)(this._samlPreferences, this.samlify);
}
async getLoginRequestUrl(relayState, binding) {
var _a;
await this.loadSamlify();
if (binding === undefined)
binding = (_a = this._samlPreferences.loginBinding) !== null && _a !== void 0 ? _a : 'redirect';
if (binding === 'post') {
return {
binding,
context: this.getPostLoginRequestUrl(relayState),
};
}
else {
return {
binding,
context: this.getRedirectLoginRequestUrl(relayState),
};
}
}
getRedirectLoginRequestUrl(relayState) {
const sp = this.getServiceProviderInstance();
sp.entitySetting.relayState = relayState !== null && relayState !== void 0 ? relayState : this.urlService.getInstanceBaseUrl();
const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'redirect');
return loginRequest;
}
getPostLoginRequestUrl(relayState) {
const sp = this.getServiceProviderInstance();
sp.entitySetting.relayState = relayState !== null && relayState !== void 0 ? relayState : this.urlService.getInstanceBaseUrl();
const loginRequest = sp.createLoginRequest(this.getIdentityProviderInstance(), 'post');
return loginRequest;
}
async handleSamlLogin(req, binding) {
const attributes = await this.getAttributesFromLoginResponse(req, binding);
if (attributes.email) {
const lowerCasedEmail = attributes.email.toLowerCase();
const user = await typedi_1.default.get(user_repository_1.UserRepository).findOne({
where: { email: lowerCasedEmail },
relations: ['authIdentities'],
});
if (user) {
if (user.authIdentities.find((e) => e.providerType === 'saml' && e.providerId === attributes.userPrincipalName)) {
return {
authenticatedUser: user,
attributes,
onboardingRequired: false,
};
}
else {
const updatedUser = await (0, samlHelpers_1.updateUserFromSamlAttributes)(user, attributes);
const onboardingRequired = !updatedUser.firstName || !updatedUser.lastName;
return {
authenticatedUser: updatedUser,
attributes,
onboardingRequired,
};
}
}
else {
if ((0, ssoHelpers_1.isSsoJustInTimeProvisioningEnabled)()) {
const newUser = await (0, samlHelpers_1.createUserFromSamlAttributes)(attributes);
return {
authenticatedUser: newUser,
attributes,
onboardingRequired: true,
};
}
}
}
return {
authenticatedUser: undefined,
attributes,
onboardingRequired: false,
};
}
async setSamlPreferences(prefs) {
await this.loadSamlify();
await this.loadPreferencesWithoutValidation(prefs);
if (prefs.metadataUrl) {
const fetchedMetadata = await this.fetchMetadataFromUrl();
if (fetchedMetadata) {
this._samlPreferences.metadata = fetchedMetadata;
}
}
else if (prefs.metadata) {
const validationResult = await (0, samlValidator_1.validateMetadata)(prefs.metadata);
if (!validationResult) {
throw new n8n_workflow_1.ApplicationError('Invalid SAML metadata');
}
}
this.getIdentityProviderInstance(true);
const result = await this.saveSamlPreferencesToDb();
return result;
}
async loadPreferencesWithoutValidation(prefs) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
this._samlPreferences.loginBinding = (_a = prefs.loginBinding) !== null && _a !== void 0 ? _a : this._samlPreferences.loginBinding;
this._samlPreferences.metadata = (_b = prefs.metadata) !== null && _b !== void 0 ? _b : this._samlPreferences.metadata;
this._samlPreferences.mapping = (_c = prefs.mapping) !== null && _c !== void 0 ? _c : this._samlPreferences.mapping;
this._samlPreferences.ignoreSSL = (_d = prefs.ignoreSSL) !== null && _d !== void 0 ? _d : this._samlPreferences.ignoreSSL;
this._samlPreferences.acsBinding = (_e = prefs.acsBinding) !== null && _e !== void 0 ? _e : this._samlPreferences.acsBinding;
this._samlPreferences.signatureConfig =
(_f = prefs.signatureConfig) !== null && _f !== void 0 ? _f : this._samlPreferences.signatureConfig;
this._samlPreferences.authnRequestsSigned =
(_g = prefs.authnRequestsSigned) !== null && _g !== void 0 ? _g : this._samlPreferences.authnRequestsSigned;
this._samlPreferences.wantAssertionsSigned =
(_h = prefs.wantAssertionsSigned) !== null && _h !== void 0 ? _h : this._samlPreferences.wantAssertionsSigned;
this._samlPreferences.wantMessageSigned =
(_j = prefs.wantMessageSigned) !== null && _j !== void 0 ? _j : this._samlPreferences.wantMessageSigned;
if (prefs.metadataUrl) {
this._samlPreferences.metadataUrl = prefs.metadataUrl;
}
else if (prefs.metadata) {
this._samlPreferences.metadataUrl = undefined;
this._samlPreferences.metadata = prefs.metadata;
}
await (0, samlHelpers_1.setSamlLoginEnabled)((_k = prefs.loginEnabled) !== null && _k !== void 0 ? _k : (0, samlHelpers_1.isSamlLoginEnabled)());
(0, samlHelpers_1.setSamlLoginLabel)((_l = prefs.loginLabel) !== null && _l !== void 0 ? _l : (0, samlHelpers_1.getSamlLoginLabel)());
}
async loadFromDbAndApplySamlPreferences(apply = true) {
const samlPreferences = await typedi_1.default.get(settings_repository_1.SettingsRepository).findOne({
where: { key: constants_1.SAML_PREFERENCES_DB_KEY },
});
if (samlPreferences) {
const prefs = (0, n8n_workflow_1.jsonParse)(samlPreferences.value);
if (prefs) {
if (apply) {
await this.setSamlPreferences(prefs);
}
else {
await this.loadPreferencesWithoutValidation(prefs);
}
return prefs;
}
}
return;
}
async saveSamlPreferencesToDb() {
const samlPreferences = await typedi_1.default.get(settings_repository_1.SettingsRepository).findOne({
where: { key: constants_1.SAML_PREFERENCES_DB_KEY },
});
const settingsValue = JSON.stringify(this.samlPreferences);
let result;
if (samlPreferences) {
samlPreferences.value = settingsValue;
result = await typedi_1.default.get(settings_repository_1.SettingsRepository).save(samlPreferences, {
transaction: false,
});
}
else {
result = await typedi_1.default.get(settings_repository_1.SettingsRepository).save({
key: constants_1.SAML_PREFERENCES_DB_KEY,
value: settingsValue,
loadOnStartup: true,
}, { transaction: false });
}
if (result)
return (0, n8n_workflow_1.jsonParse)(result.value);
return;
}
async fetchMetadataFromUrl() {
await this.loadSamlify();
if (!this._samlPreferences.metadataUrl)
throw new bad_request_error_1.BadRequestError('Error fetching SAML Metadata, no Metadata URL set');
try {
const agent = new https_1.default.Agent({
rejectUnauthorized: !this._samlPreferences.ignoreSSL,
});
const response = await axios_1.default.get(this._samlPreferences.metadataUrl, { httpsAgent: agent });
if (response.status === 200 && response.data) {
const xml = (await response.data);
const validationResult = await (0, samlValidator_1.validateMetadata)(xml);
if (!validationResult) {
throw new bad_request_error_1.BadRequestError(`Data received from ${this._samlPreferences.metadataUrl} is not valid SAML metadata.`);
}
return xml;
}
}
catch (error) {
throw new bad_request_error_1.BadRequestError(`Error fetching SAML Metadata from ${this._samlPreferences.metadataUrl}: ${error}`);
}
return;
}
async getAttributesFromLoginResponse(req, binding) {
let parsedSamlResponse;
if (!this._samlPreferences.mapping)
throw new bad_request_error_1.BadRequestError('Error fetching SAML Attributes, no Attribute mapping set');
try {
await this.loadSamlify();
parsedSamlResponse = await this.getServiceProviderInstance().parseLoginResponse(this.getIdentityProviderInstance(), binding, req);
}
catch (error) {
throw new auth_error_1.AuthError(`SAML Authentication failed. Could not parse SAML response. ${error instanceof Error ? error.message : error}`);
}
const { attributes, missingAttributes } = (0, samlHelpers_1.getMappedSamlAttributesFromFlowResult)(parsedSamlResponse, this._samlPreferences.mapping);
if (!attributes) {
throw new auth_error_1.AuthError('SAML Authentication failed. Invalid SAML response.');
}
if (missingAttributes.length > 0) {
throw new auth_error_1.AuthError(`SAML Authentication failed. Invalid SAML response (missing attributes: ${missingAttributes.join(', ')}).`);
}
return attributes;
}
};
exports.SamlService = SamlService;
exports.SamlService = SamlService = __decorate([
(0, typedi_1.Service)(),
__metadata("design:paramtypes", [Logger_1.Logger,
url_service_1.UrlService])
], SamlService);
//# sourceMappingURL=saml.service.ee.js.map