mirror of
https://github.com/sismics/docs.git
synced 2024-11-22 05:57:57 +01:00
Closes #161: password recovery by email
This commit is contained in:
parent
332fd9d1f6
commit
4cf1f29e0a
@ -75,6 +75,8 @@ public class EmailUtil {
|
||||
email.setCharset("UTF-8");
|
||||
email.setHostName(ConfigUtil.getConfigStringValue(ConfigType.SMTP_HOSTNAME));
|
||||
email.setSmtpPort(ConfigUtil.getConfigIntegerValue(ConfigType.SMTP_PORT));
|
||||
email.setAuthentication(ConfigUtil.getConfigStringValue(ConfigType.SMTP_USERNAME),
|
||||
ConfigUtil.getConfigStringValue(ConfigType.SMTP_PASSWORD));
|
||||
email.addTo(recipientUser.getEmail(), recipientUser.getUsername());
|
||||
ConfigDao configDao = new ConfigDao();
|
||||
Config themeConfig = configDao.getById(ConfigType.THEME);
|
||||
@ -87,7 +89,7 @@ public class EmailUtil {
|
||||
}
|
||||
email.setFrom(ConfigUtil.getConfigStringValue(ConfigType.SMTP_FROM), appName);
|
||||
java.util.Locale userLocale = LocaleUtil.getLocale(System.getenv(Constants.DEFAULT_LANGUAGE_ENV));
|
||||
email.setSubject(subject);
|
||||
email.setSubject(appName + " - " + subject);
|
||||
email.setTextMsg(MessageUtil.getMessage(userLocale, "email.no_html.error"));
|
||||
|
||||
// Add automatic parameters
|
||||
|
@ -2,7 +2,7 @@
|
||||
<table style="width: 100%; font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif,'Apple Color Emoji','Segoe UI Emoji','Segoe UI Symbol';">
|
||||
<tr style="background: #242424; color: #fff;">
|
||||
<td style="padding: 12px; font-size: 16px; font-weight: bold;">
|
||||
${base_url}
|
||||
${app_name}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -1,32 +1,11 @@
|
||||
package com.sismics.docs.rest.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArrayBuilder;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.sismics.docs.core.constant.ConfigType;
|
||||
import com.sismics.docs.core.dao.jpa.*;
|
||||
import com.sismics.docs.core.dao.jpa.ConfigDao;
|
||||
import com.sismics.docs.core.dao.jpa.FileDao;
|
||||
import com.sismics.docs.core.dao.jpa.UserDao;
|
||||
import com.sismics.docs.core.event.RebuildIndexAsyncEvent;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.sismics.docs.core.model.jpa.File;
|
||||
import com.sismics.docs.core.model.jpa.User;
|
||||
import com.sismics.docs.core.util.ConfigUtil;
|
||||
@ -36,10 +15,28 @@ import com.sismics.docs.core.util.jpa.PaginatedLists;
|
||||
import com.sismics.docs.rest.constant.BaseFunction;
|
||||
import com.sismics.rest.exception.ForbiddenClientException;
|
||||
import com.sismics.rest.exception.ServerException;
|
||||
import com.sismics.rest.util.ValidationUtil;
|
||||
import com.sismics.util.context.ThreadLocalContext;
|
||||
import com.sismics.util.log4j.LogCriteria;
|
||||
import com.sismics.util.log4j.LogEntry;
|
||||
import com.sismics.util.log4j.MemoryAppender;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.log4j.Appender;
|
||||
import org.apache.log4j.Level;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonArrayBuilder;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.Query;
|
||||
import javax.ws.rs.*;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.DirectoryStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* General app REST resource.
|
||||
@ -126,6 +123,7 @@ public class AppResource extends BaseResource {
|
||||
* @apiParam {String} username SMTP username
|
||||
* @apiParam {String} password SMTP password
|
||||
* @apiError (client) ForbiddenError Access denied
|
||||
* @apiError (client) ValidationError Validation error
|
||||
* @apiPermission admin
|
||||
* @apiVersion 1.5.0
|
||||
*
|
||||
@ -147,18 +145,25 @@ public class AppResource extends BaseResource {
|
||||
throw new ForbiddenClientException();
|
||||
}
|
||||
checkBaseFunction(BaseFunction.ADMIN);
|
||||
ValidationUtil.validateRequired(hostname, "hostname");
|
||||
if (!Strings.isNullOrEmpty(portStr)) {
|
||||
ValidationUtil.validateInteger(portStr, "port");
|
||||
ValidationUtil.validateRequired(from, "from");
|
||||
}
|
||||
|
||||
// Just update the changed configuration
|
||||
ConfigDao configDao = new ConfigDao();
|
||||
if (!Strings.isNullOrEmpty(hostname)) {
|
||||
configDao.update(ConfigType.SMTP_HOSTNAME, hostname);
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(portStr)) {
|
||||
configDao.update(ConfigType.SMTP_PORT, portStr);
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(from)) {
|
||||
configDao.update(ConfigType.SMTP_FROM, from);
|
||||
if (username != null) {
|
||||
}
|
||||
if (!Strings.isNullOrEmpty(username)) {
|
||||
configDao.update(ConfigType.SMTP_USERNAME, username);
|
||||
}
|
||||
if (password != null) {
|
||||
if (!Strings.isNullOrEmpty(password)) {
|
||||
configDao.update(ConfigType.SMTP_PASSWORD, password);
|
||||
}
|
||||
|
||||
|
@ -28,6 +28,15 @@ angular.module('docs',
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('passwordreset', {
|
||||
url: '/passwordreset/:key',
|
||||
views: {
|
||||
'page': {
|
||||
templateUrl: 'partial/docs/passwordreset.html',
|
||||
controller: 'PasswordReset'
|
||||
}
|
||||
}
|
||||
})
|
||||
.state('tag', {
|
||||
url: '/tag',
|
||||
abstract: true,
|
||||
|
@ -46,22 +46,22 @@ angular.module('docs').controller('Login', function(Restangular, $scope, $rootSc
|
||||
$uibModal.open({
|
||||
templateUrl: 'partial/docs/passwordlost.html',
|
||||
controller: 'LoginModalPasswordLost'
|
||||
}).result.then(function (email) {
|
||||
if (name === null) {
|
||||
}).result.then(function (username) {
|
||||
if (username === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send a password lost email
|
||||
Restangular.one('user').post('passwordLost', {
|
||||
email: email
|
||||
Restangular.one('user').post('password_lost', {
|
||||
username: username
|
||||
}).then(function () {
|
||||
var title = $translate.instant('login.password_lost_sent_title');
|
||||
var msg = $translate.instant('login.password_lost_sent_message', { email: email });
|
||||
var msg = $translate.instant('login.password_lost_sent_message', { username: username });
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
}, function () {
|
||||
var title = $translate.instant('login.password_lost_error_title');
|
||||
var msg = $translate.instant('login.password_lost_error_message', { email: email });
|
||||
var msg = $translate.instant('login.password_lost_error_message');
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
});
|
||||
|
@ -4,8 +4,8 @@
|
||||
* Login modal password lost controller.
|
||||
*/
|
||||
angular.module('docs').controller('LoginModalPasswordLost', function ($scope, $uibModalInstance) {
|
||||
$scope.email = '';
|
||||
$scope.close = function(name) {
|
||||
$uibModalInstance.close(name);
|
||||
$scope.username = '';
|
||||
$scope.close = function(username) {
|
||||
$uibModalInstance.close(username);
|
||||
}
|
||||
});
|
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Password reset controller.
|
||||
*/
|
||||
angular.module('docs').controller('PasswordReset', function($scope, Restangular, $state, $stateParams, $translate, $dialog) {
|
||||
$scope.submit = function () {
|
||||
Restangular.one('user').post('password_reset', {
|
||||
key: $stateParams.key,
|
||||
password: $scope.password
|
||||
}).then(function () {
|
||||
$state.go('login');
|
||||
}, function () {
|
||||
var title = $translate.instant('passwordreset.error_title');
|
||||
var msg = $translate.instant('passwordreset.error_message');
|
||||
var btns = [{result: 'ok', label: $translate.instant('ok'), cssClass: 'btn-primary'}];
|
||||
$dialog.messageBox(title, msg, btns);
|
||||
});
|
||||
};
|
||||
});
|
@ -5,29 +5,29 @@
|
||||
*/
|
||||
angular.module('docs').controller('SettingsConfig', function($scope, $rootScope, Restangular) {
|
||||
// Get the app configuration
|
||||
Restangular.one('app').get().then(function(data) {
|
||||
Restangular.one('app').get().then(function (data) {
|
||||
$scope.app = data;
|
||||
});
|
||||
|
||||
// Enable/disable guest login
|
||||
$scope.changeGuestLogin = function(enabled) {
|
||||
$scope.changeGuestLogin = function (enabled) {
|
||||
Restangular.one('app').post('guest_login', {
|
||||
enabled: enabled
|
||||
}).then(function() {
|
||||
}).then(function () {
|
||||
$scope.app.guest_login = enabled;
|
||||
});
|
||||
};
|
||||
|
||||
// Fetch the current theme configuration
|
||||
Restangular.one('theme').get().then(function(data) {
|
||||
Restangular.one('theme').get().then(function (data) {
|
||||
$scope.theme = data;
|
||||
$rootScope.appName = $scope.theme.name;
|
||||
});
|
||||
|
||||
// Update the theme
|
||||
$scope.update = function() {
|
||||
$scope.update = function () {
|
||||
$scope.theme.name = $scope.theme.name.length === 0 ? 'Sismics Docs' : $scope.theme.name;
|
||||
Restangular.one('theme').post('', $scope.theme).then(function() {
|
||||
Restangular.one('theme').post('', $scope.theme).then(function () {
|
||||
var stylesheet = $('#theme-stylesheet')[0];
|
||||
stylesheet.href = stylesheet.href.replace(/\?.*|$/, '?' + new Date().getTime());
|
||||
$rootScope.appName = $scope.theme.name;
|
||||
@ -36,7 +36,7 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
|
||||
// Send an image
|
||||
$scope.sendingImage = false;
|
||||
$scope.sendImage = function(type, image) {
|
||||
$scope.sendImage = function (type, image) {
|
||||
// Build the payload
|
||||
var formData = new FormData();
|
||||
formData.append('image', image);
|
||||
@ -64,4 +64,11 @@ angular.module('docs').controller('SettingsConfig', function($scope, $rootScope,
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Edit SMTP config
|
||||
$scope.editSmtpConfig = function () {
|
||||
Restangular.one('app').post('config_smtp', $scope.smtp).then(function () {
|
||||
$scope.smtpUpdated = true;
|
||||
});
|
||||
};
|
||||
});
|
@ -49,6 +49,7 @@
|
||||
<script src="app/docs/controller/Main.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/Login.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/LoginModalPasswordLost.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/PasswordReset.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/Navigation.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/Footer.js" type="text/javascript"></script>
|
||||
<script src="app/docs/controller/document/Document.js" type="text/javascript"></script>
|
||||
|
@ -12,15 +12,21 @@
|
||||
"login_failed_message": "Username or password invalid",
|
||||
"password_lost_btn": "Password lost?",
|
||||
"password_lost_sent_title": "Password reset email sent",
|
||||
"password_lost_sent_message": "An email has been sent to <strong>{{ email }}</strong> to reset your password",
|
||||
"password_lost_sent_message": "An email has been sent to <strong>{{ username }}</strong> to reset your password",
|
||||
"password_lost_error_title": "Password reset error",
|
||||
"password_lost_error_message": "Unable to send a password reset email, please contact your administrator for a manual reset"
|
||||
},
|
||||
"passwordlost": {
|
||||
"title": "Password lost",
|
||||
"message": "Please enter your email address to receive a password reset link",
|
||||
"message": "Please enter your username to receive a password reset link. If you don't remember your username, please contact your administrator",
|
||||
"submit": "Reset my password"
|
||||
},
|
||||
"passwordreset": {
|
||||
"message": "Please enter a new password",
|
||||
"submit": "Change my password",
|
||||
"error_title": "Error changing your password",
|
||||
"error_message": "Your password recovery request is expired, please ask a new one on the login page"
|
||||
},
|
||||
"index": {
|
||||
"toggle_navigation": "Toggle navigation",
|
||||
"nav_documents": "Documents",
|
||||
@ -295,7 +301,14 @@
|
||||
"custom_css_placeholder": "Custom CSS to add after the main stylesheet",
|
||||
"logo": "Logo (squared size)",
|
||||
"background_image": "Background image",
|
||||
"uploading_image": "Uploading the image..."
|
||||
"uploading_image": "Uploading the image...",
|
||||
"title_smtp": "Email <small>configuration</small>",
|
||||
"smtp_hostname": "SMTP hostname",
|
||||
"smtp_port": "SMTP port",
|
||||
"smtp_from": "Sender e-mail",
|
||||
"smtp_username": "SMTP username",
|
||||
"smtp_password": "SMTP password",
|
||||
"smtp_updated": "SMTP configuration updated successfully"
|
||||
},
|
||||
"log": {
|
||||
"title": "Server <small>logs</small>",
|
||||
|
@ -5,11 +5,11 @@
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
<label for="share-result">{{ 'passwordlost.message' | translate }}</label>
|
||||
<input name="email" class="form-control" type="email" required id="share-result" ng-model="email" />
|
||||
<input name="username" class="form-control" type="text" required id="share-result" ng-model="username" />
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button ng-click="close(email)" class="btn btn-primary" ng-disabled="!form.$valid">
|
||||
<button ng-click="close(username)" class="btn btn-primary" ng-disabled="!form.$valid">
|
||||
<span class="glyphicon glyphicon-envelope"></span> {{ 'passwordlost.submit' | translate }}
|
||||
</button>
|
||||
<button ng-click="close(null)" class="btn btn-default">{{ 'cancel' | translate }}</button>
|
||||
|
25
docs-web/src/main/webapp/src/partial/docs/passwordreset.html
Normal file
25
docs-web/src/main/webapp/src/partial/docs/passwordreset.html
Normal file
@ -0,0 +1,25 @@
|
||||
<div class="row">
|
||||
<div class="col-xs-offset-1 col-xs-10 col-sm-offset-2 col-sm-8">
|
||||
<form class="form-horizontal" name="form" novalidate>
|
||||
<div class="form-group" ng-class="{ 'has-error': !form.password.$valid && form.$dirty }">
|
||||
<label for="inputPassword" class="col-sm-4 control-label">{{ 'passwordreset.message' | translate }}</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" name="password" ng-model="password" required ng-minlength="8" ng-maxlength="50" class="form-control" id="inputPassword">
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<span class="help-block" ng-show="form.password.$error.required && form.$dirty">{{ 'validation.required' | translate }}</span>
|
||||
<span class="help-block" ng-show="form.password.$error.minlength && form.$dirty">{{ 'validation.too_short' | translate }}</span>
|
||||
<span class="help-block" ng-show="form.password.$error.maxlength && form.$dirty">{{ 'validation.too_long' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-4 col-sm-5">
|
||||
<button class="btn btn-primary" ng-disabled="!form.$valid" ng-click="submit()">
|
||||
<span class="glyphicon glyphicon-lock"></span>
|
||||
{{ 'passwordreset.submit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -74,3 +74,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h1 translate="settings.config.title_smtp"></h1>
|
||||
<div uib-alert ng-class="'alert-success'" ng-show="smtpUpdated">{{ 'settings.config.smtp_updated' | translate }}</div>
|
||||
<form class="form-horizontal" name="smtpForm" ng-show="!smtpUpdated" novalidate>
|
||||
<div class="form-group" ng-class="{ 'has-error': !smtpForm.hostname.$valid && smtpForm.$dirty }">
|
||||
<label class="col-sm-2 control-label" for="smtpHostname">{{ 'settings.config.smtp_hostname' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="hostname" type="text" class="form-control" id="smtpHostname" ng-model="smtp.hostname" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': !smtpForm.port.$valid && smtpForm.$dirty }">
|
||||
<label class="col-sm-2 control-label" for="smtpPort">{{ 'settings.config.smtp_port' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="port" type="number" class="form-control" id="smtpPort" ng-model="smtp.port" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" ng-class="{ 'has-error': !smtpForm.from.$valid && smtpForm.$dirty }">
|
||||
<label class="col-sm-2 control-label" for="smtpFrom">{{ 'settings.config.smtp_from' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="from" type="email" class="form-control" id="smtpFrom" ng-model="smtp.from" />
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<span class="help-block" ng-show="smtpForm.from.$error.email && smtpForm.$dirty">{{ 'validation.email' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="smtpUsername">{{ 'settings.config.smtp_username' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="username" type="text" class="form-control" id="smtpUsername" ng-model="smtp.username" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 control-label" for="smtpPassword">{{ 'settings.config.smtp_password' | translate }}</label>
|
||||
<div class="col-sm-7">
|
||||
<input name="password" type="password" class="form-control" id="smtpPassword" ng-model="smtp.password" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary" ng-click="editSmtpConfig()" ng-disabled="!smtpForm.$valid">
|
||||
<span class="glyphicon glyphicon-pencil"></span> {{ 'edit' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
Loading…
Reference in New Issue
Block a user