UNPKG

passbolt-styleguide

Version:

Passbolt styleguide contains common styling assets used by the different sites, plugin, etc.

350 lines (313 loc) 10.8 kB
/** * Passbolt ~ Open source password manager for teams * Copyright (c) Passbolt SA (https://www.passbolt.com) * * Licensed under GNU Affero General Public License version 3 of the or any later version. * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * * @copyright Copyright (c) Passbolt SA (https://www.passbolt.com) * @license https://opensource.org/licenses/AGPL-3.0 AGPL License * @link https://www.passbolt.com Passbolt(tm) * @since 5.11.0 */ import EntityV2 from "../abstract/entityV2"; import EntitySchema from "../abstract/entitySchema"; import EntityValidationError from "../abstract/entityValidationError"; import SmtpSettingsEntity from "./smtpSettingsEntity"; import SmtpNoneAuthenticationEntity from "./smtpNoneAuthenticationEntity"; import SmtpUsernameAuthenticationEntity from "./smtpUsernameAuthenticationEntity"; import SmtpUsernamePasswordAuthenticationEntity from "./smtpUsernamePasswordAuthenticationEntity"; import SmtpOAuthCredentialsGrantSettingsEntity from "./smtpOAuthCredentialsGrantSettingsEntity"; import DomainUtil from "../../../../react-extension/lib/Domain/DomainUtil"; const ENTITY_NAME = "SmtpSettingsForm"; const AUTHENTICATION_METHOD_NONE = "none"; const AUTHENTICATION_METHOD_USERNAME = "username"; const AUTHENTICATION_METHOD_USERNAME_PASSWORD = "username_password"; const AUTHENTICATION_METHOD_OAUTH = "oauth"; const AUTH_ENTITY_CLASSES = [ SmtpNoneAuthenticationEntity, SmtpUsernameAuthenticationEntity, SmtpUsernamePasswordAuthenticationEntity, SmtpOAuthCredentialsGrantSettingsEntity, ]; const AUTH_FIELDS = [ ...new Set(AUTH_ENTITY_CLASSES.flatMap((EntityClass) => Object.keys(EntityClass.getDefaultData()))), ]; /** * Form entity for SMTP settings with superset schema (all auth fields optional). */ class SmtpSettingsFormEntity extends EntityV2 { /** * Get the entity schema * @returns {object} */ static getSchema() { const baseSchema = SmtpSettingsEntity.getSchema(); // Derive auth field schemas from auth entities, made nullable for form permissiveness const authProperties = {}; for (const field of AUTH_FIELDS) { for (const EntityClass of AUTH_ENTITY_CLASSES) { const prop = EntityClass.getSchema().properties[field]; if (prop) { authProperties[field] = { ...prop, nullable: true }; break; } } } return { type: "object", required: [...baseSchema.required], properties: { ...baseSchema.properties, ...authProperties, provider: { type: "string", nullable: true, }, }, }; } /** * Marshall the entity props before validation. * Converts port from string to integer for schema validation. * @protected */ marshall() { if (typeof this._props.port === "string") { const parsed = parseInt(this._props.port, 10); this._props.port = isNaN(parsed) ? this._props.port : parsed; } } /** * Override validate to ensure both schema and build rules run, even if schema has errors. * This allows all errors to be collected simultaneously for form display. * @param {object} [options] Options * @returns {EntityValidationError|null} */ validate(options = {}) { this.marshall(); let schemaError = null; const schema = options?.schema ?? this.cachedSchema; try { EntitySchema.validate(this.constructor.name, { ...this._props }, schema); } catch (error) { if (!(error instanceof EntityValidationError)) { throw error; } schemaError = error; } let buildRulesError = null; try { this.validateBuildRules(options?.validateBuildRules); } catch (error) { if (!(error instanceof EntityValidationError)) { throw error; } buildRulesError = error; } // Merge errors if (!schemaError && !buildRulesError) { return null; } const mergedError = schemaError || new EntityValidationError(); if (buildRulesError) { for (const field in buildRulesError.details) { // Only add build rule errors if schema didn't already flag the field if (!mergedError.hasError(field)) { const fieldErrors = buildRulesError.details[field]; for (const rule in fieldErrors) { mergedError.addError(field, rule, fieldErrors[rule]); } } } } return mergedError; } /** * Validate the entity build rules. * @param {object} [options] Options. * @param {object} [options.siteSettings] The application site settings for email validation. * @throws {EntityValidationError} */ validateBuildRules() { const errors = new EntityValidationError(); const client = this._props.client; // Validate client hostname if non-empty if (client && client.length > 0) { if (!DomainUtil.isValidHostname(client)) { errors.addError("client", "hostname", "SMTP client should be a valid domain or IP address"); } } if (errors.hasErrors()) { throw errors; } } /** * Returns only form-managed properties suitable for UI binding. * @returns {object} */ toFormDto() { const dto = { host: this._props.host ?? "", port: this._props.port ?? "", tls: this._props.tls ?? true, client: this._props.client ?? "", sender_name: this._props.sender_name ?? "", sender_email: this._props.sender_email ?? "", provider: this._props.provider ?? null, source: this._props.source ?? null, }; for (const field of AUTH_FIELDS) { dto[field] = this._props[field] ?? null; } return dto; } /** * Returns a DTO suitable for creating an API entity via SmtpSettingsEntity.createFromSettings(). * Strips form-only fields and normalizes values. * @returns {object} */ toApiDto() { const dto = this.toDto(); delete dto.provider; // Normalize empty client to null for the API if (dto.client === "") { dto.client = null; } return dto; } /** * Detects the provider based on the current host/port/tls settings. * @param {Array<object>} providersList The list of available SMTP providers. * @returns {string} The provider ID string or "other". */ detectProvider(providersList) { const host = this._props.host; const port = parseInt(this._props.port, 10); const tls = this._props.tls; for (let i = 0; i < providersList.length; i++) { const provider = providersList[i]; const foundConfiguration = provider.availableConfigurations?.find( (config) => config.host === host && config.port === port && config.tls === tls, ); if (foundConfiguration) { return provider.id; } } return "other"; } /** * Changes the authentication method by resetting all auth fields and applying the target entity's defaults. * @param {string} method One of "none", "username", "username_password", "oauth". */ changeAuthenticationMethod(method) { const entityClass = this._getAuthenticationEntityClass(method); const defaults = entityClass.getDefaultData(); const currentValues = {}; for (const field of AUTH_FIELDS) { // Save current values currentValues[field] = this._props[field]; // Reset all auth fields to null this._props[field] = null; } // Apply defaults, preserving existing values for active fields for (const [key, defaultValue] of Object.entries(defaults)) { if (defaultValue !== null) { this._props[key] = currentValues[key] ?? defaultValue; } } } /** * Maps an authentication method string to the corresponding entity class. * @param {string} method The authentication method. * @returns {typeof EntityV2} The entity class. * @private */ _getAuthenticationEntityClass(method) { switch (method) { case AUTHENTICATION_METHOD_NONE: return SmtpNoneAuthenticationEntity; case AUTHENTICATION_METHOD_USERNAME: return SmtpUsernameAuthenticationEntity; case AUTHENTICATION_METHOD_USERNAME_PASSWORD: return SmtpUsernamePasswordAuthenticationEntity; case AUTHENTICATION_METHOD_OAUTH: return SmtpOAuthCredentialsGrantSettingsEntity; default: throw new Error(`Unknown authentication method: ${method}`); } } /** * Applies provider default configuration to the entity. * @param {object} providerData The provider object with defaultConfiguration and id. */ applyProviderDefaults(providerData) { if (providerData.defaultConfiguration) { const config = providerData.defaultConfiguration; if (config.host !== undefined) { this._props.host = config.host; } if (config.port !== undefined) { this._props.port = config.port; } if (config.tls !== undefined) { this._props.tls = config.tls; } } this._props.provider = providerData.id; } /** * Returns the current authentication method based on the auth fields state. * @returns {string} "oauth", "none", "username", or "username_password". */ getAuthenticationMethod() { if (this._props.client_id !== null && this._props.client_id !== undefined) { return AUTHENTICATION_METHOD_OAUTH; } if (this._props.username === null || this._props.username === undefined) { return AUTHENTICATION_METHOD_NONE; } if (this._props.password === null || this._props.password === undefined) { return AUTHENTICATION_METHOD_USERNAME; } return AUTHENTICATION_METHOD_USERNAME_PASSWORD; } /** * Creates a default empty SmtpSettingsFormEntity for initializing the form. * @returns {SmtpSettingsFormEntity} */ static createDefault() { return new SmtpSettingsFormEntity( { host: "", port: "", tls: true, client: "", sender_email: "", sender_name: "Passbolt", ...SmtpUsernamePasswordAuthenticationEntity.getDefaultData(), }, { validate: false }, ); } static get ENTITY_NAME() { return ENTITY_NAME; } static get AUTHENTICATION_METHOD_NONE() { return AUTHENTICATION_METHOD_NONE; } static get AUTHENTICATION_METHOD_USERNAME() { return AUTHENTICATION_METHOD_USERNAME; } static get AUTHENTICATION_METHOD_USERNAME_PASSWORD() { return AUTHENTICATION_METHOD_USERNAME_PASSWORD; } static get AUTHENTICATION_METHOD_OAUTH() { return AUTHENTICATION_METHOD_OAUTH; } static get AUTH_FIELDS() { return AUTH_FIELDS; } } export default SmtpSettingsFormEntity;