Add separate password reset view
This commit is contained in:
@@ -151,6 +151,15 @@ gulp.task('js-login', function () {
|
|||||||
.pipe(gulp.dest('dist/js'));
|
.pipe(gulp.dest('dist/js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('js-passwordreset', function () {
|
||||||
|
return gulp.src(['src/js/passwordreset.js', 'src/js/utils.js'])
|
||||||
|
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||||
|
.pipe(sourcemaps.init())
|
||||||
|
.pipe(concat('passwordreset.js', { newLine: ';' }))
|
||||||
|
.pipe(sourcemaps.write())
|
||||||
|
.pipe(gulp.dest('dist/js'));
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('js-setupaccount', function () {
|
gulp.task('js-setupaccount', function () {
|
||||||
return gulp.src(['src/js/setupaccount.js', 'src/js/utils.js'])
|
return gulp.src(['src/js/setupaccount.js', 'src/js/utils.js'])
|
||||||
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
.pipe(ejs({ apiOrigin: apiOrigin, revision: revision, appstore: appstore }, {}, { ext: '.js' }))
|
||||||
@@ -187,7 +196,7 @@ gulp.task('js-restore', function () {
|
|||||||
.pipe(gulp.dest('dist/js'));
|
.pipe(gulp.dest('dist/js'));
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('js', gulp.series([ 'js-index', 'js-logs', 'js-filemanager', 'js-terminal', 'js-login', 'js-setupaccount', 'js-setup', 'js-setupdns', 'js-restore' ]));
|
gulp.task('js', gulp.series([ 'js-index', 'js-logs', 'js-filemanager', 'js-terminal', 'js-login', 'js-passwordreset', 'js-setupaccount', 'js-setup', 'js-setupdns', 'js-restore' ]));
|
||||||
|
|
||||||
// --------------
|
// --------------
|
||||||
// HTML
|
// HTML
|
||||||
@@ -266,6 +275,7 @@ gulp.task('watch', function (done) {
|
|||||||
gulp.watch(['src/js/filemanager.js', 'src/js/client.js', 'src/js/utils.js', 'src/components/*.js'], gulp.series(['js-filemanager']));
|
gulp.watch(['src/js/filemanager.js', 'src/js/client.js', 'src/js/utils.js', 'src/components/*.js'], gulp.series(['js-filemanager']));
|
||||||
gulp.watch(['src/js/terminal.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-terminal']));
|
gulp.watch(['src/js/terminal.js', 'src/js/client.js', 'src/js/utils.js'], gulp.series(['js-terminal']));
|
||||||
gulp.watch(['src/js/login.js', 'src/js/utils.js'], gulp.series(['js-login']));
|
gulp.watch(['src/js/login.js', 'src/js/utils.js'], gulp.series(['js-login']));
|
||||||
|
gulp.watch(['src/js/passwordreset.js', 'src/js/utils.js'], gulp.series(['js-passwordreset']));
|
||||||
gulp.watch(['src/js/setupaccount.js', 'src/js/utils.js'], gulp.series(['js-setupaccount']));
|
gulp.watch(['src/js/setupaccount.js', 'src/js/utils.js'], gulp.series(['js-setupaccount']));
|
||||||
gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/js/main.js', 'src/views/*.js', 'src/js/utils.js'], gulp.series(['js-index']));
|
gulp.watch(['src/js/index.js', 'src/js/client.js', 'src/js/main.js', 'src/views/*.js', 'src/js/utils.js'], gulp.series(['js-index']));
|
||||||
gulp.watch(['src/3rdparty/**/*'], gulp.series(['3rdparty']));
|
gulp.watch(['src/3rdparty/**/*'], gulp.series(['3rdparty']));
|
||||||
|
|||||||
158
dashboard/src/js/passwordreset.js
Normal file
158
dashboard/src/js/passwordreset.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
/* global angular, $, showdown */
|
||||||
|
|
||||||
|
// create main application module
|
||||||
|
var app = angular.module('Application', ['pascalprecht.translate', 'ngCookies']);
|
||||||
|
|
||||||
|
app.filter('markdown2html', function () {
|
||||||
|
var converter = new showdown.Converter({
|
||||||
|
simplifiedAutoLink: true,
|
||||||
|
strikethrough: true,
|
||||||
|
tables: true,
|
||||||
|
openLinksInNewWindow: true
|
||||||
|
});
|
||||||
|
|
||||||
|
return function (text) {
|
||||||
|
return converter.makeHtml(text);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// disable sce for footer https://code.angularjs.org/1.5.8/docs/api/ng/service/$sce
|
||||||
|
app.config(function ($sceProvider) {
|
||||||
|
$sceProvider.enabled(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.config(['$translateProvider', function ($translateProvider) {
|
||||||
|
$translateProvider.useStaticFilesLoader({
|
||||||
|
prefix: 'translation/',
|
||||||
|
suffix: '.json'
|
||||||
|
});
|
||||||
|
$translateProvider.preferredLanguage('en');
|
||||||
|
$translateProvider.fallbackLanguage('en');
|
||||||
|
}]);
|
||||||
|
|
||||||
|
// Add shorthand "tr" filter to avoid having ot use "translate"
|
||||||
|
// This is a copy of the code at https://github.com/angular-translate/angular-translate/blob/master/src/filter/translate.js
|
||||||
|
// If we find out how to get that function handle somehow dynamically we can use that, otherwise the copy is required
|
||||||
|
function translateFilterFactory($parse, $translate) {
|
||||||
|
var translateFilter = function (translationId, interpolateParams, interpolation, forceLanguage) {
|
||||||
|
if (!angular.isObject(interpolateParams)) {
|
||||||
|
var ctx = this || {
|
||||||
|
'__SCOPE_IS_NOT_AVAILABLE': 'More info at https://github.com/angular/angular.js/commit/8863b9d04c722b278fa93c5d66ad1e578ad6eb1f'
|
||||||
|
};
|
||||||
|
interpolateParams = $parse(interpolateParams)(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $translate.instant(translationId, interpolateParams, interpolation, forceLanguage);
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($translate.statefulFilter()) {
|
||||||
|
translateFilter.$stateful = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return translateFilter;
|
||||||
|
}
|
||||||
|
translateFilterFactory.displayName = 'translateFilterFactory';
|
||||||
|
app.filter('tr', translateFilterFactory);
|
||||||
|
|
||||||
|
|
||||||
|
app.controller('PasswordResetController', ['$scope', '$translate', '$http', function ($scope, $translate, $http) {
|
||||||
|
// Stupid angular location provider either wants html5 location mode or not, do the query parsing on my own
|
||||||
|
var search = decodeURIComponent(window.location.search).slice(1).split('&').map(function (item) { return item.indexOf('=') === -1 ? [item, true] : [item.slice(0, item.indexOf('=')), item.slice(item.indexOf('=')+1)]; }).reduce(function (o, k) { o[k[0]] = k[1]; return o; }, {});
|
||||||
|
|
||||||
|
$scope.initialized = false;
|
||||||
|
$scope.mode = '';
|
||||||
|
$scope.busy = false;
|
||||||
|
$scope.error = false;
|
||||||
|
$scope.status = null;
|
||||||
|
$scope.username = '';
|
||||||
|
$scope.password = '';
|
||||||
|
$scope.totpToken = '';
|
||||||
|
$scope.passwordResetIdentifier = '';
|
||||||
|
$scope.newPassword = '';
|
||||||
|
$scope.newPasswordRepeat = '';
|
||||||
|
var API_ORIGIN = '<%= apiOrigin %>' || window.location.origin;
|
||||||
|
|
||||||
|
$scope.onPasswordReset = function () {
|
||||||
|
$scope.busy = true;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
identifier: $scope.passwordResetIdentifier
|
||||||
|
};
|
||||||
|
|
||||||
|
function done() {
|
||||||
|
$scope.busy = false;
|
||||||
|
$scope.mode = 'passwordResetDone';
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.post(API_ORIGIN + '/api/v1/cloudron/password_reset_request', data).success(done).error(done);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.onNewPassword = function () {
|
||||||
|
$scope.busy = true;
|
||||||
|
|
||||||
|
var data = {
|
||||||
|
resetToken: search.resetToken,
|
||||||
|
password: $scope.newPassword,
|
||||||
|
totpToken: $scope.totpToken
|
||||||
|
};
|
||||||
|
|
||||||
|
function error(data, status) {
|
||||||
|
console.log('error', status);
|
||||||
|
$scope.busy = false;
|
||||||
|
|
||||||
|
if (status === 401) $scope.error = data.message;
|
||||||
|
else if (status === 409) $scope.error = 'Ask your admin for an invite link first';
|
||||||
|
else $scope.error = 'Unknown error';
|
||||||
|
}
|
||||||
|
|
||||||
|
$http.post(API_ORIGIN + '/api/v1/cloudron/password_reset', data).success(function (data, status) {
|
||||||
|
if (status !== 202) return error(data, status);
|
||||||
|
|
||||||
|
// set token to autologin
|
||||||
|
localStorage.token = data.accessToken;
|
||||||
|
|
||||||
|
$scope.mode = 'newPasswordDone';
|
||||||
|
}).error(function (data, status) {
|
||||||
|
error(data, status);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showPasswordReset = function () {
|
||||||
|
window.document.title = 'Password Reset Request';
|
||||||
|
$scope.mode = 'passwordReset';
|
||||||
|
$scope.passwordResetIdentifier = '';
|
||||||
|
setTimeout(function () { $('#inputPasswordResetIdentifier').focus(); }, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.showNewPassword = function () {
|
||||||
|
window.document.title = 'Set New Password';
|
||||||
|
$scope.mode = 'newPassword';
|
||||||
|
setTimeout(function () { $('#inputNewPassword').focus(); }, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
$http.get(API_ORIGIN + '/api/v1/cloudron/status').success(function (data, status) {
|
||||||
|
$scope.initialized = true;
|
||||||
|
|
||||||
|
if (status !== 200) return;
|
||||||
|
|
||||||
|
if (data.language) $translate.use(data.language);
|
||||||
|
|
||||||
|
$scope.status = data;
|
||||||
|
}).error(function () {
|
||||||
|
$scope.initialized = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init into the correct view
|
||||||
|
if (search.passwordReset) {
|
||||||
|
$scope.showPasswordReset();
|
||||||
|
} else if (search.resetToken) {
|
||||||
|
$scope.showNewPassword();
|
||||||
|
} else if (search.accessToken || search.access_token) { // auto-login feature
|
||||||
|
localStorage.token = search.accessToken || search.access_token;
|
||||||
|
window.location.href = '/';
|
||||||
|
} else {
|
||||||
|
$scope.showPasswordReset();
|
||||||
|
}
|
||||||
|
}]);
|
||||||
167
dashboard/src/passwordreset.html
Normal file
167
dashboard/src/passwordreset.html
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src <%= apiOrigin %> 'unsafe-inline' 'unsafe-eval' 'self'; img-src <%= apiOrigin %> 'self'" />
|
||||||
|
|
||||||
|
<!-- this gets changed once we get the status (because angular has not loaded yet, we see template string for a flash) -->
|
||||||
|
<title>Cloudron Password Reset</title>
|
||||||
|
<meta name="description" content="Cloudron Password Reset">
|
||||||
|
|
||||||
|
<link id="favicon" href="<%= apiOrigin %>/api/v1/cloudron/avatar" rel="icon" type="image/png">
|
||||||
|
|
||||||
|
<!-- Theme CSS -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="/theme.css?<%= revision %>">
|
||||||
|
|
||||||
|
<!-- Fontawesome -->
|
||||||
|
<link type="text/css" rel="stylesheet" href="/3rdparty/fontawesome/css/all.css?<%= revision %>"/>
|
||||||
|
|
||||||
|
<!-- jQuery-->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/jquery.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- async -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/async-3.2.0.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Bootstrap Core JavaScript -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/bootstrap.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Showdown (markdown converter) -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/showdown-1.9.1.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Angularjs scripts -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular.min.js?<%= revision %>"></script>
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-loader.min.js?<%= revision %>"></script>
|
||||||
|
<!-- <script type="text/javascript" src="/3rdparty/js/angular-md5.min.js"></script> -->
|
||||||
|
<!-- <script type="text/javascript" src="/3rdparty/js/angular-ui-notification.js"></script> -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-cookies.min.js?<%= revision %>"></script>
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/autofill-event.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Angular directives for bootstrap https://angular-ui.github.io/bootstrap/ -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/ui-bootstrap-tpls-1.3.3.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Angular translate https://angular-translate.github.io/ -->
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-translate.min.js?<%= revision %>"></script>
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-translate-loader-static-files.min.js?<%= revision %>"></script>
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-cookie.min.js?<%= revision %>"></script>
|
||||||
|
<script type="text/javascript" src="/3rdparty/js/angular-translate-storage-local.min.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
<!-- Setup Application -->
|
||||||
|
<script type="text/javascript" src="/js/passwordreset.js?<%= revision %>"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body ng-app="Application" ng-controller="PasswordResetController">
|
||||||
|
|
||||||
|
<div class="layout-root ng-cloak" ng-show="initialized">
|
||||||
|
|
||||||
|
<div class="layout-content" ng-show="mode === 'passwordReset'">
|
||||||
|
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="text-align: center;">
|
||||||
|
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||||
|
<br/>
|
||||||
|
<h2>{{ 'passwordReset.title' | tr }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<form name="passwordResetForm" ng-submit="onPasswordReset()">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="inputPasswordResetIdentifier">{{ 'passwordReset.usernameOrEmail' | tr }}</label>
|
||||||
|
<input type="text" class="form-control" id="inputPasswordResetIdentifier" name="passwordResetIdentifier" ng-model="passwordResetIdentifier" ng-disabled="busy" autofocus required>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<button class="btn btn-primary btn-outline pull-right" type="submit" ng-disabled="busy || passwordResetForm.$invalid"><i class="fa fa-circle-notch fa-spin" ng-show="busy"></i> {{ 'passwordReset.resetAction' | tr }}</button>
|
||||||
|
</form>
|
||||||
|
<a href="/" class="hand">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-content" ng-show="mode === 'passwordResetDone'">
|
||||||
|
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="text-align: center;">
|
||||||
|
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||||
|
<br/>
|
||||||
|
<h2>{{ 'passwordReset.emailSent.title' | tr }}</h2>
|
||||||
|
<br/>
|
||||||
|
<a href="/" class="btn btn-primary">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-content" ng-show="mode === 'newPassword'">
|
||||||
|
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="text-align: center;">
|
||||||
|
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||||
|
<br/>
|
||||||
|
<h2>{{ 'passwordReset.newPassword.title' | tr }}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<h4 class="has-error" ng-show="error">{{ error }}</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<form name="newPasswordForm" ng-submit="onNewPassword()">
|
||||||
|
<input type="password" style="display: none;"/>
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid }">
|
||||||
|
<label class="control-label" for="inputNewPassword">{{ 'passwordReset.newPassword.password' | tr }}</label>
|
||||||
|
<div class="control-label" ng-show="newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid">
|
||||||
|
<small ng-show="newPasswordForm.newPassword.$dirty && newPasswordForm.newPassword.$invalid">{{ 'passwordReset.newPassword.errorLength' | tr }}</small>
|
||||||
|
</div>
|
||||||
|
<input type="password" class="form-control" id="inputNewPassword" ng-model="newPassword" name="newPassword" ng-minlength="8" ng-maxlength="256" autofocus required password-reveal>
|
||||||
|
</div>
|
||||||
|
<div class="form-group" ng-class="{ 'has-error': newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat) }">
|
||||||
|
<label class="control-label" for="inputNewPasswordRepeat">{{ 'passwordReset.newPassword.passwordRepeat' | tr }}</label>
|
||||||
|
<div class="control-label" ng-show="newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat)">
|
||||||
|
<small ng-show="newPasswordForm.newPasswordRepeat.$dirty && (newPassword !== newPasswordRepeat)">{{ 'passwordReset.newPassword.errorMismatch' | tr }}</small>
|
||||||
|
</div>
|
||||||
|
<input type="password" class="form-control" id="inputNewPasswordRepeat" ng-model="newPasswordRepeat" name="newPasswordRepeat" required password-reveal>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="control-label" for="inputPasswordResetTotpToken">{{ 'login.2faToken' | tr }}</label>
|
||||||
|
<input type="text" class="form-control" name="passwordResetTotpToken" id="inputPasswordResetTotpToken" ng-model="totpToken" ng-disabled="busy" value="">
|
||||||
|
</div>
|
||||||
|
<br/>
|
||||||
|
<button class="btn btn-primary btn-outline pull-right" type="submit" ng-disabled="busy || newPasswordForm.$invalid || newPassword !== newPasswordRepeat"><i class="fa fa-circle-notch fa-spin" ng-show="busy"></i> {{ 'passwordReset.passwordChanged.submitAction' | tr }}</button>
|
||||||
|
</form>
|
||||||
|
<a href="/" class="hand">{{ 'passwordReset.backToLoginAction' | tr }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="layout-content" ng-show="mode === 'newPasswordDone'">
|
||||||
|
<div class="card" style="padding: 20px; margin-top: 100px; max-width: 620px;">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12" style="text-align: center;">
|
||||||
|
<img width="128" height="128" style="margin-top: -84px" src="<%= apiOrigin %>/api/v1/cloudron/avatar"/>
|
||||||
|
<br/>
|
||||||
|
<h2>{{ 'passwordReset.success.title' | tr }}</h2>
|
||||||
|
<br/>
|
||||||
|
<a href="/" class="btn btn-primary">{{ 'passwordReset.success.openDashboardAction' | tr }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer class="text-center">
|
||||||
|
<span class="text-muted" ng-bind-html="status.footer | markdown2html"></span>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@
|
|||||||
<input type="text" class="form-control" name="totpToken" id="inputTotpToken" value="">
|
<input type="text" class="form-control" name="totpToken" id="inputTotpToken" value="">
|
||||||
<p class="has-error" id="totpError"></p>
|
<p class="has-error" id="totpError"></p>
|
||||||
</div>
|
</div>
|
||||||
<a href="/login.html?passwordReset=1">Reset password</a>
|
<a href="/passwordreset.html">Reset password</a>
|
||||||
<button class="btn btn-primary btn-outline pull-right" type="submit">Log in</button>
|
<button class="btn btn-primary btn-outline pull-right" type="submit">Log in</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ async function logout(req, res) {
|
|||||||
await eventlog.add(eventlog.ACTION_USER_LOGOUT, AuditSource.fromRequest(req), { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
await eventlog.add(eventlog.ACTION_USER_LOGOUT, AuditSource.fromRequest(req), { userId: req.user.id, user: users.removePrivateFields(req.user) });
|
||||||
|
|
||||||
await safe(tokens.delByAccessToken(req.token.accessToken));
|
await safe(tokens.delByAccessToken(req.token.accessToken));
|
||||||
res.redirect('/login.html');
|
res.redirect('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function passwordResetRequest(req, res, next) {
|
async function passwordResetRequest(req, res, next) {
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ async function initializeExpressSync() {
|
|||||||
|
|
||||||
// login/logout routes
|
// login/logout routes
|
||||||
router.post('/api/v1/cloudron/login', json, password, routes.cloudron.login);
|
router.post('/api/v1/cloudron/login', json, password, routes.cloudron.login);
|
||||||
router.get ('/api/v1/cloudron/logout', token, routes.cloudron.logout); // this will invalidate the token if any and redirect to /login.html always
|
router.get ('/api/v1/cloudron/logout', token, routes.cloudron.logout); // this will invalidate the token if any and redirect to / always
|
||||||
router.post('/api/v1/cloudron/password_reset_request', json, routes.cloudron.passwordResetRequest);
|
router.post('/api/v1/cloudron/password_reset_request', json, routes.cloudron.passwordResetRequest);
|
||||||
router.post('/api/v1/cloudron/password_reset', json, routes.cloudron.passwordReset);
|
router.post('/api/v1/cloudron/password_reset', json, routes.cloudron.passwordReset);
|
||||||
router.post('/api/v1/cloudron/setup_account', json, routes.cloudron.setupAccount);
|
router.post('/api/v1/cloudron/setup_account', json, routes.cloudron.setupAccount);
|
||||||
|
|||||||
@@ -665,7 +665,7 @@ async function getPasswordResetLink(user, auditSource) {
|
|||||||
await update(user, { resetToken, resetTokenCreationTime }, auditSource);
|
await update(user, { resetToken, resetTokenCreationTime }, auditSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetLink = `${settings.dashboardOrigin()}/login.html?resetToken=${resetToken}`;
|
const resetLink = `${settings.dashboardOrigin()}/passwordreset.html?resetToken=${resetToken}`;
|
||||||
|
|
||||||
return resetLink;
|
return resetLink;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user