Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .semaphore/semaphore.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ blocks:
- '8.4'
- env_var: NODEJS_VERSION
values:
- '22'
- '24'
- node
- name: Install Testing Matrix
dependencies:
Expand Down Expand Up @@ -123,5 +123,5 @@ blocks:
- '8.4'
- env_var: NODEJS_VERSION
values:
- '22'
- '24'
- node
2 changes: 2 additions & 0 deletions client/src/i18n/en/enterprise.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"EDITING_ENTERPRISE" : "Editing enterprise",
"TITLE" : "Enterprise Management",
"UPDATE_LOGO":"Update the enterprise logo",
"SMTP_SETTINGS" : "SMTP Settings",
"SMTP_CONFIGURATION" : "SMTP Configuration",
"SETTINGS" : {
"TITLE" : "Enterprise Settings",
"ENABLE_PRICE_LOCK_LABEL" : "Lock Price Changes for Patient Invoicing",
Expand Down
13 changes: 13 additions & 0 deletions client/src/i18n/en/smtp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"SMTP" : {
"FROM_ADDRESS" : "Sender (from) Address",
"FROM_NAME" : "Sender (from) Name",
"HOST" : "SMTP Host",
"PASSWORD" : "Password",
"PORT" : "SMTP Port",
"SECURE_DESCRIPTION" : "Use TLS in the connection (recommended for port 465)",
"SECURE" : "Secure",
"TEST_CONNECTION" : "Test Connection",
"USERNAME" : "Username"
}
}
2 changes: 2 additions & 0 deletions client/src/i18n/fr/enterprise.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"EDITING_ENTERPRISE" : "Edition entreprise",
"TITLE" : "Gestion des entreprises",
"UPDATE_LOGO":"Mettre à jour le logo",
"SMTP_SETTINGS" : "Paramètres SMTP",
"SMTP_CONFIGURATION" : "Configuration SMTP",
"SETTINGS" : {
"TITLE" : "Parametres de l'Enterprise",
"ENABLE_PRICE_LOCK_LABEL" : "Verrouiller les changements de prix pour la facturation des patients",
Expand Down
13 changes: 13 additions & 0 deletions client/src/i18n/fr/smtp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"SMTP" : {
"FROM_ADDRESS" : "Adresse de l'expéditeur",
"FROM_NAME" : "Nom de l'expéditeur",
"HOST" : "Hôte SMTP",
"PASSWORD" : "Mot de passe",
"PORT" : "Port",
"SECURE_DESCRIPTION" : "Utiliser TLS dans la connexion (recommandé pour le port 465)",
"SECURE" : "Sécurisé",
"TEST_CONNECTION" : "Tester la connexion",
"USERNAME" : "Nom d'utilisateur"
}
}
5 changes: 5 additions & 0 deletions client/src/modules/enterprises/enterprises.html
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,14 @@
</div>

<div class="panel-footer text-right" id="submission">
<button type="button" class="btn btn-default" ng-click="EnterpriseCtrl.openSMTPSettingsModal()">
<i class="fa fa-envelope-o"></i> <span translate>ENTERPRISE.SMTP_SETTINGS</span>
</button>

<bh-loading-button loading-state="EnterpriseForm.$loading">
<span translate>FORM.BUTTONS.UPDATE</span>
</bh-loading-button>

</div>
</div>

Expand Down
7 changes: 5 additions & 2 deletions client/src/modules/enterprises/enterprises.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ angular.module('bhima.controllers')

EnterpriseController.$inject = [
'EnterpriseService', 'util', 'NotifyService', 'ProjectService', 'ModalService',
'ScrollService', 'SessionService', 'Upload', '$timeout',
'ScrollService', 'SessionService', 'Upload', '$timeout', 'SMTPService',
];

/**
Expand All @@ -12,7 +12,7 @@ EnterpriseController.$inject = [
* @description
* This controller binds the basic CRUD operations on the enterprise.
*/
function EnterpriseController(Enterprises, util, Notify, Projects, Modal, ScrollTo, Session, Upload, $timeout) {
function EnterpriseController(Enterprises, util, Notify, Projects, Modal, ScrollTo, Session, Upload, $timeout, SMTP) {
const vm = this;

vm.enterprise = {};
Expand All @@ -22,6 +22,9 @@ function EnterpriseController(Enterprises, util, Notify, Projects, Modal, Scroll
vm.hasEnterprise = false;
vm.maxLogoFileSize = '2MB';

// bind the openSMTPSettingsModal method
vm.openSMTPSettingsModal = () => SMTP.openSMTPModal();

let $touched = false;

// bind methods
Expand Down
130 changes: 130 additions & 0 deletions client/src/modules/enterprises/modals/bhSmtp-modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<form name="SMTPForm" bh-submit="ModalCtrl.submit(SMTPForm)" novalidate data-edit-password-modal>
<div class="modal-header">
<ol class="headercrumb">
<li class="static" translate>ENTERPRISE.TITLE</li>
<li class="title" translate>ENTERPRISE.SMTP_CONFIGURATION</li>
</ol>
</div>

<div class="modal-body">

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.smtp_host.$error }">
<label class="control-label" translate>SMTP.HOST</label>
<input
type="text"
name="smtp_host"
ng-model="ModalCtrl.smtp.smtp_host"
class="form-control"
required />

<div class="help-block" ng-messages="SMTPForm.smtp_host.$error" ng-show="SMTPForm.$submitted && SMTPForm.smtp_host.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.smtp_port.$error }">
<label class="control-label" translate>SMTP.PORT</label>
<input
type="text"
name="smtp_port"
ng-model="ModalCtrl.smtp.smtp_port"
class="form-control" />

<div class="help-block" ng-messages="SMTPForm.smtp_port.$error" ng-show="SMTPForm.$submitted && SMTPForm.smtp_port.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group" >
<div class="checkbox">
<label>
<input type="checkbox" name="smtp_secure" ng-true-value="1" ng-false-value="0"
ng-model="ModalCtrl.user.smtp_secure">
<span translate>SMTP.SECURE</span>
</label>
</div>
<div class="help-block">
<p translate>SMTP.SECURE_DESCRIPTION</p>
</div>
</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.smtp_username.$error }">
<label class="control-label" translate>SMTP.USERNAME</label>
<input
type="text"
name="smtp_username"
ng-model="ModalCtrl.smtp.smtp_username"
class="form-control"
required />

<div class="help-block" ng-messages="SMTPForm.smtp_username.$error" ng-show="SMTPForm.$submitted && SMTPForm.smtp_username.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.smtp_port.$error }">
<label class="control-label" translate>SMTP.PASSWORD</label>
<input
type="password"
name="smtp_password"
ng-model="ModalCtrl.smtp.smtp_password"
class="form-control"
required />

<div class="help-block" ng-messages="SMTPForm.smtp_port.$error" ng-show="SMTPForm.$submitted && SMTPForm.smtp_port.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.from_address.$error }">
<label class="control-label" translate>SMTP.FROM_ADDRESS</label>
<input
type="text"
name="from_address"
ng-model="ModalCtrl.smtp.from_address"
class="form-control" />

<div class="help-block" ng-messages="SMTPForm.from_address.$error" ng-show="SMTPForm.$submitted && SMTPForm.from_address.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>

<div class="form-group has-feedback" ng-class="{ 'has-error' : SMTPForm.$submitted && SMTPForm.from_name.$error }">
<label class="control-label" translate>SMTP.FROM_NAME</label>
<input
type="text"
name="from_name"
ng-model="ModalCtrl.smtp.from_name"
class="form-control"
required />

<div class="help-block" ng-messages="SMTPForm.from_name.$error" ng-show="SMTPForm.$submitted && SMTPForm.from_name.$error">
<div ng-messages-include="modules/templates/messages.tmpl.html"></div>
</div>
</div>
</div>

<div class="modal-footer">
<button
type="button"
class="btn btn-default"
ng-click="ModalCtrl.cancel()"
data-method="cancel"
translate>
FORM.BUTTONS.CANCEL
</button>

<button
type="button"
class="btn btn-default"
ng-click="ModalCtrl.testConnection()"
data-method="test-connection"
translate>
SMTP.TEST_CONNECTION
</button>

<bh-loading-button loading-state="SMTPForm.$loading">
<span translate>FORM.BUTTONS.SUBMIT</span>
</bh-loading-button>
</div>
</form>
70 changes: 70 additions & 0 deletions client/src/modules/enterprises/modals/bhSmtp.ctrl.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
angular.module('bhima.controllers')
.controller('SMTPModalController', SMTPModalController);

SMTPModalController.$inject = [
'SessionService', '$uibModalInstance', '$stateParams', 'NotifyService', 'SMTPService',
];

/**
* @controller SMTPModalController
* @description Controller for the SMTP configuration modal.
*/
function SMTPModalController(Session, Instance, $stateParams, Notify, SMTP) {
const vm = this;

vm.smtp = {};

/* ------------------------------------------------------------------------ */
vm.cancel = () => Instance.close(false);
vm.submit = submit;
vm.testConnection = testConnection;

// loads a new set of cashboxes from the server that the user has management right.
function startup() {
toggleLoadingIndicator();
SMTP.read()
.then(rows => {

if (rows.length > 0) {
[vm.smtp] = rows;
}

vm.isCreateState = (rows.length === 0);
})
.catch(Notify.handleError)
.finally(toggleLoadingIndicator);
}

function toggleLoadingIndicator() {
vm.loading = !vm.loading;
}

function testConnection() {
toggleLoadingIndicator();

SMTP.testConnection(vm.smtp)
.then(() => {
Notify.success('SMTP.CONNECTION.SUCCESS');
})
.catch(Notify.handleError)
.finally(toggleLoadingIndicator);
}

function submit(form) {
if (form.$invalid) { return 0; }

const promise = (vm.isCreateState)
? SMTP.create(vm.smtp)
: SMTP.update(vm.smtp.id, vm.smtp);

return promise.then(() => {
const translateKey = (vm.isCreateState) ? 'FORM.INFO.CREATE_SUCCESS' : 'FORM.INFO.UPDATE_SUCCESS';
Notify.success(translateKey);
return Instance.close(true);
})
.catch(Notify.handleError);
}

// start up the module
startup();
}
26 changes: 26 additions & 0 deletions client/src/modules/enterprises/modals/bhSmtp.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
angular.module('bhima.services')
.service('SMTPService', SMTPService);

SMTPService.$inject = ['PrototypeApiService', '$uibModal'];

function SMTPService(Api, Modal) {
const baseUrl = '/smtp/';
const service = new Api(baseUrl);

// allows the user to test the SMTP connection before
// saving to the database.
service.testConnection = (body) => {
return service.$http.post(`${baseUrl}test-connection`, body)
.then(service.util.unwrapHttpResponse);
};

// opens the SMTP modal to configure the SMTP settings.
service.openSMTPModal = () => {
return Modal.open({
templateUrl : 'modules/enterprises/modals/bhSmtp-modal.html',
controller : 'SMTPModalController as ModalCtrl',
}).result;
};

return service;
}
3 changes: 3 additions & 0 deletions server/config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const locations = require('../controllers/admin/locations');
const groups = require('../controllers/groups');
const entities = require('../controllers/admin/entities');
const cron = require('../controllers/admin/cron');
const smtp = require('../controllers/admin/smtp');
const cronEmailReport = require('../controllers/admin/cronEmailReport');

// payroll routes
Expand Down Expand Up @@ -1210,4 +1211,6 @@ exports.configure = function configure(app) {
app.post('/funding_sources', fundingSource.create);
app.delete('/funding_sources/:uuid', fundingSource.delete);
app.put('/funding_sources/:uuid', fundingSource.update);

app.use('/smtp', smtp);
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The SMTP configuration endpoints are protected by general authentication (requiring a logged-in user) but lack specific admin-level authorization. SMTP configuration is a sensitive system-level setting that should only be accessible to administrators, not all authenticated users. Consider adding role-based access control to restrict these endpoints to admin users only, similar to how other admin operations are protected in the codebase.

Copilot uses AI. Check for mistakes.
};
Loading