Merge branch '96-notify-user-about-incorrect-email-pw-combination' into 'main'
Resolve "Notify user about incorrect email/pw combination" Closes #94 and #96 See merge request stackspin/dashboard!62
This commit is contained in:
commit
696ffba9fe
5 changed files with 303 additions and 323 deletions
|
@ -1,5 +1,12 @@
|
|||
# Changelog
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fix login welcome message
|
||||
- Clarify "set new password" button (#94)
|
||||
- Show error messages when login fails, for example when a wrong password was
|
||||
entered (#96)
|
||||
|
||||
## [0.5.1]
|
||||
|
||||
- Fix bug of missing "Monitoring" app access when creating a new user.
|
||||
|
|
|
@ -118,7 +118,13 @@ def login():
|
|||
identity = get_auth()
|
||||
|
||||
if identity:
|
||||
return render_template("loggedin.html", api_url=KRATOS_PUBLIC_URL, id=id)
|
||||
if 'name' in identity['traits']:
|
||||
# Add a space in front of the "name" so the template can put it
|
||||
# between "Welcome" and the comma
|
||||
name = " " + identity['traits']['name']
|
||||
else:
|
||||
name = ""
|
||||
return render_template("loggedin.html", api_url=KRATOS_PUBLIC_URL, name=name)
|
||||
|
||||
flow = request.args.get("flow")
|
||||
|
||||
|
|
261
backend/web/static/base.js
vendored
261
backend/web/static/base.js
vendored
|
@ -16,7 +16,6 @@
|
|||
|
||||
*/
|
||||
|
||||
|
||||
// Check if an auth flow is configured and redirect to auth page in that
|
||||
// case.
|
||||
function check_flow_auth() {
|
||||
|
@ -24,7 +23,7 @@ function check_flow_auth() {
|
|||
var url = Cookies.get('auth_url');
|
||||
|
||||
if (state == 'auth') {
|
||||
Cookies.set('flow_state','');
|
||||
Cookies.set('flow_state', '');
|
||||
window.location.href = url;
|
||||
}
|
||||
}
|
||||
|
@ -34,55 +33,50 @@ function check_flow_expired() {
|
|||
var state = Cookies.get('flow_state');
|
||||
|
||||
if (state == 'flow_expired') {
|
||||
Cookies.set('flow_state','');
|
||||
$("#contentFlowExpired").show();
|
||||
Cookies.set('flow_state', '');
|
||||
$('#contentFlowExpired').show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// The script executed on login flows
|
||||
function flow_login() {
|
||||
|
||||
var flow = $.urlParam('flow');
|
||||
var uri = api_url + 'self-service/login/flows?id=' + flow;
|
||||
|
||||
// Query the Kratos backend to know what fields to render for the
|
||||
// current flow
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
type: 'GET',
|
||||
url: uri,
|
||||
success: function(data) {
|
||||
|
||||
success: function (data) {
|
||||
// Render login form (group: password)
|
||||
var html = render_form(data, 'password');
|
||||
$("#contentLogin").html(html);
|
||||
var form_html = render_form(data, 'password');
|
||||
$('#contentLogin').html(form_html);
|
||||
|
||||
var messages_html = render_messages(data);
|
||||
$('#contentMessages').html(messages_html);
|
||||
},
|
||||
complete: function(obj) {
|
||||
|
||||
complete: function (obj) {
|
||||
// If we get a 410, the flow is expired, need to refresh the flow
|
||||
if (obj.status == 410) {
|
||||
Cookies.set('flow_state','flow_expired');
|
||||
Cookies.set('flow_state', 'flow_expired');
|
||||
// If we call the page without arguments, we get a new flow
|
||||
window.location.href = 'login';
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// This is called after a POST on settings. It tells if the save was
|
||||
// successful and display / handles based on that outcome
|
||||
function flow_settings_validate() {
|
||||
|
||||
var flow = $.urlParam('flow');
|
||||
var uri = api_url + 'self-service/settings/flows?id=' + flow;
|
||||
|
||||
$.ajax( {
|
||||
type: "GET",
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: uri,
|
||||
success: function(data) {
|
||||
|
||||
success: function (data) {
|
||||
// We had success. We save that fact in our flow_state
|
||||
// cookie and regenerate a new flow
|
||||
if (data.state == 'success') {
|
||||
|
@ -90,41 +84,37 @@ function flow_settings_validate() {
|
|||
|
||||
// Redirect to generate new flow ID
|
||||
window.location.href = 'settings';
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// There was an error, Kratos does not specify what is
|
||||
// wrong. So we just show the general error message and
|
||||
// let the user figure it out. We can re-use the flow-id
|
||||
$("#contentProfileSaveFailed").show();
|
||||
$('#contentProfileSaveFailed').show();
|
||||
|
||||
// For now, this code assumes that only the password can fail
|
||||
// validation. Other forms might need to be added in the future.
|
||||
html = render_form(data, 'password')
|
||||
$("#contentPassword").html(html)
|
||||
}
|
||||
html = render_form(data, 'password');
|
||||
$('#contentPassword').html(html);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Render the settings flow, this is where users can change their personal
|
||||
// settings, like name and password. The form contents are defined by Kratos
|
||||
function flow_settings() {
|
||||
|
||||
// Get the details from the current flow from kratos
|
||||
var flow = $.urlParam('flow');
|
||||
var uri = api_url + 'self-service/settings/flows?id=' + flow;
|
||||
$.ajax({
|
||||
type: "GET",
|
||||
type: 'GET',
|
||||
url: uri,
|
||||
success: function(data) {
|
||||
|
||||
var state = Cookies.get('flow_state')
|
||||
success: function (data) {
|
||||
var state = Cookies.get('flow_state');
|
||||
|
||||
// If we have confirmation the settings are saved, show the
|
||||
// notification
|
||||
if (state == 'settings_saved') {
|
||||
$("#contentProfileSaved").show();
|
||||
$('#contentProfileSaved').show();
|
||||
Cookies.set('flow_state', 'settings');
|
||||
}
|
||||
|
||||
|
@ -132,21 +122,19 @@ function flow_settings() {
|
|||
// so the user is not confused by other fields. The user
|
||||
// probably want to setup a password only first.
|
||||
if (state == 'recovery') {
|
||||
$("#contentProfile").hide();
|
||||
$('#contentProfile').hide();
|
||||
}
|
||||
|
||||
|
||||
// Render the password & profile form based on the fields we got
|
||||
// from the API
|
||||
var html = render_form(data, 'password');
|
||||
$("#contentPassword").html(html);
|
||||
$('#contentPassword').html(html);
|
||||
|
||||
html = render_form(data, 'profile');
|
||||
$("#contentProfile").html(html);
|
||||
$('#contentProfile').html(html);
|
||||
|
||||
// If the submit button is hit, execute the POST with Ajax.
|
||||
$("#formpassword").submit(function(e) {
|
||||
|
||||
$('#formpassword').submit(function (e) {
|
||||
// avoid to execute the actual submit of the form.
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -154,48 +142,40 @@ function flow_settings() {
|
|||
var url = form.attr('action');
|
||||
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: form.serialize(),
|
||||
complete: function(obj) {
|
||||
complete: function (obj) {
|
||||
// Validate the settings
|
||||
flow_settings_validate();
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
},
|
||||
complete: function(obj) {
|
||||
|
||||
complete: function (obj) {
|
||||
// If we get a 410, the flow is expired, need to refresh the flow
|
||||
if (obj.status == 410) {
|
||||
Cookies.set('flow_state','flow_expired');
|
||||
Cookies.set('flow_state', 'flow_expired');
|
||||
window.location.href = 'settings';
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function flow_recover() {
|
||||
var flow = $.urlParam('flow');
|
||||
var uri = api_url + 'self-service/recovery/flows?id=' + flow;
|
||||
|
||||
$.ajax( {
|
||||
type: "GET",
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: uri,
|
||||
success: function(data) {
|
||||
|
||||
success: function (data) {
|
||||
// Render the recover form, method 'link'
|
||||
var html = render_form(data, 'link');
|
||||
$("#contentRecover").html(html);
|
||||
$('#contentRecover').html(html);
|
||||
|
||||
// Do form post as an AJAX call
|
||||
$("#formlink").submit(function(e) {
|
||||
|
||||
$('#formlink').submit(function (e) {
|
||||
// avoid to execute the actual submit of the form.
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -205,30 +185,24 @@ function flow_recover() {
|
|||
// keep stat we are in recovery
|
||||
Cookies.set('flow_state', 'recovery');
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
type: 'POST',
|
||||
url: url,
|
||||
data: form.serialize(), // serializes the form's elements.
|
||||
success: function(data)
|
||||
{
|
||||
|
||||
success: function (data) {
|
||||
// Show the request is sent out
|
||||
$("#contentRecover").hide();
|
||||
$("#contentRecoverRequested").show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
$('#contentRecover').hide();
|
||||
$('#contentRecoverRequested').show();
|
||||
},
|
||||
complete: function(obj) {
|
||||
|
||||
});
|
||||
});
|
||||
},
|
||||
complete: function (obj) {
|
||||
// If we get a 410, the flow is expired, need to refresh the flow
|
||||
if (obj.status == 410) {
|
||||
Cookies.set('flow_state','flow_expired');
|
||||
Cookies.set('flow_state', 'flow_expired');
|
||||
window.location.href = 'recovery';
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -240,27 +214,40 @@ function flow_recover() {
|
|||
// data: data object as returned form the API
|
||||
// group: group to render.
|
||||
function render_form(data, group) {
|
||||
|
||||
// Create form
|
||||
var action = data.ui.action;
|
||||
var method = data.ui.method;
|
||||
var form = "<form id='form"+group+"' method='"+method+"' action='"+action+"'>";
|
||||
var form = "<form id='form" + group + "' method='" + method + "' action='" + action + "'>";
|
||||
|
||||
for (const node of data.ui.nodes) {
|
||||
|
||||
var name = node.attributes.name;
|
||||
var type = node.attributes.type;
|
||||
var value = node.attributes.value;
|
||||
var messages = node.messages
|
||||
var messages = node.messages;
|
||||
|
||||
if (node.group == 'default' || node.group == group) {
|
||||
var elm = getFormElement(type, name, value, messages);
|
||||
form += elm;
|
||||
}
|
||||
}
|
||||
form += "</form>";
|
||||
form += '</form>';
|
||||
return form;
|
||||
}
|
||||
|
||||
// Check if there are any general messages to show to the user and render them
|
||||
function render_messages(data) {
|
||||
var messages = data.ui.messages;
|
||||
if (messages == []) {
|
||||
return '';
|
||||
}
|
||||
var html = '<ul>';
|
||||
messages.forEach((message) => {
|
||||
html += '<li>';
|
||||
html += message.text;
|
||||
html += '</li>';
|
||||
});
|
||||
html += '</ul>';
|
||||
return html;
|
||||
}
|
||||
|
||||
// Return form element based on name, including help text (sub), placeholder etc.
|
||||
|
@ -274,14 +261,14 @@ function render_form(data, group) {
|
|||
// value: If there is already a value known, show it
|
||||
// messages: error messages related to the field
|
||||
function getFormElement(type, name, value, messages) {
|
||||
console.log("Getting form element", type, name, value, messages)
|
||||
console.log('Getting form element', type, name, value, messages);
|
||||
|
||||
if (value == undefined) {
|
||||
value = '';
|
||||
}
|
||||
|
||||
if (typeof(messages) == "undefined") {
|
||||
messages = []
|
||||
if (typeof messages == 'undefined') {
|
||||
messages = [];
|
||||
}
|
||||
|
||||
if (name == 'email' || name == 'traits.email') {
|
||||
|
@ -291,37 +278,19 @@ function getFormElement(type, name, value, messages) {
|
|||
value,
|
||||
'E-mail address',
|
||||
'Please enter your e-mail address here',
|
||||
'Please provide your e-mail address. We will send a recovery ' +
|
||||
'link to that e-mail address.',
|
||||
'Please provide your e-mail address. We will send a recovery link to that e-mail address.',
|
||||
messages,
|
||||
);
|
||||
}
|
||||
|
||||
if (name == 'traits.username') {
|
||||
return getFormInput(
|
||||
'name',
|
||||
name,
|
||||
value,
|
||||
'Username',
|
||||
'Please provide an username',
|
||||
null,
|
||||
messages,
|
||||
);
|
||||
return getFormInput('name', name, value, 'Username', 'Please provide an username', null, messages);
|
||||
}
|
||||
|
||||
if (name == 'traits.name') {
|
||||
return getFormInput(
|
||||
'name',
|
||||
name,
|
||||
value,
|
||||
'Full name',
|
||||
'Please provide your full name',
|
||||
null,
|
||||
messages,
|
||||
);
|
||||
return getFormInput('name', name, value, 'Full name', 'Please provide your full name', null, messages);
|
||||
}
|
||||
|
||||
|
||||
if (name == 'identifier') {
|
||||
return getFormInput(
|
||||
'email',
|
||||
|
@ -335,37 +304,37 @@ function getFormElement(type, name, value, messages) {
|
|||
}
|
||||
|
||||
if (name == 'password') {
|
||||
return getFormInput(
|
||||
'password',
|
||||
name,
|
||||
value,
|
||||
'Password',
|
||||
'Please provide your password',
|
||||
null,
|
||||
messages,
|
||||
return getFormInput('password', name, value, 'Password', 'Please provide your password', null, messages);
|
||||
}
|
||||
|
||||
if (type == 'hidden' || name == 'traits.uuid') {
|
||||
return (
|
||||
`
|
||||
<input type="hidden" class="form-control" id="` +
|
||||
name +
|
||||
`"
|
||||
name="` +
|
||||
name +
|
||||
`" value='` +
|
||||
value +
|
||||
`'>`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
if (type == 'hidden' || name == 'traits.uuid') {
|
||||
|
||||
return `
|
||||
<input type="hidden" class="form-control" id="`+name+`"
|
||||
name="`+name+`" value='`+value+`'>`;
|
||||
}
|
||||
|
||||
if (type == 'submit') {
|
||||
|
||||
return `<div class="form-group">
|
||||
<input type="hidden" name="`+name+`" value="`+value+`">
|
||||
return (
|
||||
`<div class="form-group">
|
||||
<input type="hidden" name="` +
|
||||
name +
|
||||
`" value="` +
|
||||
value +
|
||||
`">
|
||||
<button type="submit" class="btn btn-primary">Go!</button>
|
||||
</div>`;
|
||||
</div>`
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return getFormInput('input', name, value, name, null,null, messages);
|
||||
|
||||
|
||||
return getFormInput('input', name, value, name, null, null, messages);
|
||||
}
|
||||
|
||||
// Usually called by getFormElement, generic function to generate an
|
||||
|
@ -378,41 +347,45 @@ function getFormElement(type, name, value, messages) {
|
|||
// param help: Additional help text, displayed below the field in small font
|
||||
// param messages: Message about failed input
|
||||
function getFormInput(type, name, value, label, placeHolder, help, messages) {
|
||||
if (typeof(help) == "undefined" || help == null) {
|
||||
help = ""
|
||||
if (typeof help == 'undefined' || help == null) {
|
||||
help = '';
|
||||
}
|
||||
console.log("Messages: ", messages);
|
||||
console.log('Messages: ', messages);
|
||||
|
||||
// Id field for help element
|
||||
var nameHelp = name + "Help";
|
||||
var nameHelp = name + 'Help';
|
||||
|
||||
var element = '<div class="form-group">';
|
||||
element += '<label for="'+name+'">'+label+'</label>';
|
||||
element += '<input type="'+type+'" class="form-control" id="'+name+'" name="'+name+'" ';
|
||||
element += '<label for="' + name + '">' + label + '</label>';
|
||||
element += '<input type="' + type + '" class="form-control" id="' + name + '" name="' + name + '" ';
|
||||
|
||||
// messages get appended to help info
|
||||
if (messages.length) {
|
||||
for (message in messages) {
|
||||
console.log("adding message", messages[message])
|
||||
help += messages[message]['text']
|
||||
console.log('adding message', messages[message]);
|
||||
help += messages[message]['text'];
|
||||
}
|
||||
}
|
||||
|
||||
// If we are a password field, add a eye icon to reveal password
|
||||
if (value) {
|
||||
element += 'value="'+value+'" ';
|
||||
element += 'value="' + value + '" ';
|
||||
}
|
||||
if (help) {
|
||||
element += 'aria-describedby="' + nameHelp +'" ';
|
||||
element += 'aria-describedby="' + nameHelp + '" ';
|
||||
}
|
||||
if (placeHolder) {
|
||||
element += 'placeholder="'+placeHolder+'" ';
|
||||
element += 'placeholder="' + placeHolder + '" ';
|
||||
}
|
||||
element += ">";
|
||||
element += '>';
|
||||
|
||||
if (help) {
|
||||
element +=
|
||||
`<small id="`+nameHelp+`" class="form-text text-muted">` + help + `
|
||||
`<small id="` +
|
||||
nameHelp +
|
||||
`" class="form-text text-muted">` +
|
||||
help +
|
||||
`
|
||||
</small>`;
|
||||
}
|
||||
|
||||
|
@ -421,12 +394,10 @@ function getFormInput(type, name, value, label, placeHolder, help, messages) {
|
|||
return element;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// $.urlParam get parameters from the URI. Example: id = $.urlParam('id');
|
||||
$.urlParam = function(name) {
|
||||
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||
if (results==null) {
|
||||
$.urlParam = function (name) {
|
||||
var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
return decodeURI(results[1]) || 0;
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
|
@ -17,11 +15,9 @@
|
|||
|
||||
|
||||
|
||||
<div id="contentMessages"></div>
|
||||
<div id="contentWelcome">Welcome {{ id['name'] }},<br/><br/>
|
||||
You are already logged in.
|
||||
|
||||
|
||||
<div id="contentMessages"></div>
|
||||
<div id="contentWelcome">
|
||||
Welcome{{ name }}, you are logged in.
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<div id="contentMessages"></div>
|
||||
<div id="contentLogin"></div>
|
||||
<div id="contentHelp">
|
||||
<a href='recovery'>Forget password?</a> | <a href='https://stackspin.net'>About stackspin</a>
|
||||
<a href='recovery'>Set new password</a> | <a href='https://stackspin.net'>About stackspin</a>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
Loading…
Reference in a new issue