feat: add error handling for unaccepted passwords, add kratos error page
This commit is contained in:
parent
c120ab603a
commit
bf98fbd721
6 changed files with 95 additions and 25 deletions
|
@ -236,7 +236,7 @@ cat source_env.local
|
||||||
|
|
||||||
export HYDRA_ADMIN_URL=http://localhost:4445
|
export HYDRA_ADMIN_URL=http://localhost:4445
|
||||||
export KRATOS_PUBLIC_URL=http://localhost/api
|
export KRATOS_PUBLIC_URL=http://localhost/api
|
||||||
export KRATOS_ADMIN_URL=http://localhost:8000/admin
|
export KRATOS_ADMIN_URL=http://localhost:8000
|
||||||
export LOGIN_PANEL_URL=http://localhost/web
|
export LOGIN_PANEL_URL=http://localhost/web
|
||||||
export DATABASE_URL="mysql+pymysql://stackspin:stackspin@localhost/stackspin"
|
export DATABASE_URL="mysql+pymysql://stackspin:stackspin@localhost/stackspin"
|
||||||
```
|
```
|
||||||
|
|
|
@ -8,7 +8,7 @@ from helpers import KratosApi
|
||||||
class UserService:
|
class UserService:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_users():
|
def get_users():
|
||||||
res = KratosApi.get("/identities").json()
|
res = KratosApi.get("/admin/identities").json()
|
||||||
userList = []
|
userList = []
|
||||||
for r in res:
|
for r in res:
|
||||||
userList.append(UserService.__insertAppRoleToUser(r["id"], r))
|
userList.append(UserService.__insertAppRoleToUser(r["id"], r))
|
||||||
|
@ -17,7 +17,7 @@ class UserService:
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_user(id):
|
def get_user(id):
|
||||||
res = KratosApi.get("/identities/{}".format(id)).json()
|
res = KratosApi.get("/admin/identities/{}".format(id)).json()
|
||||||
return UserService.__insertAppRoleToUser(id, res)
|
return UserService.__insertAppRoleToUser(id, res)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -26,7 +26,7 @@ class UserService:
|
||||||
"schema_id": "default",
|
"schema_id": "default",
|
||||||
"traits": {"email": data["email"], "name": data["name"]},
|
"traits": {"email": data["email"], "name": data["name"]},
|
||||||
}
|
}
|
||||||
res = KratosApi.post("/identities", kratos_data).json()
|
res = KratosApi.post("/admin/identities", kratos_data).json()
|
||||||
|
|
||||||
appRole = AppRole(
|
appRole = AppRole(
|
||||||
user_id=res["id"],
|
user_id=res["id"],
|
||||||
|
@ -45,7 +45,7 @@ class UserService:
|
||||||
"schema_id": "default",
|
"schema_id": "default",
|
||||||
"traits": {"email": data["email"], "name": data["name"]},
|
"traits": {"email": data["email"], "name": data["name"]},
|
||||||
}
|
}
|
||||||
KratosApi.put("/identities/{}".format(id), kratos_data)
|
KratosApi.put("/admin/identities/{}".format(id), kratos_data)
|
||||||
|
|
||||||
app_role = AppRole.query.filter_by(user_id=id).first()
|
app_role = AppRole.query.filter_by(user_id=id).first()
|
||||||
if app_role:
|
if app_role:
|
||||||
|
|
|
@ -28,7 +28,7 @@ export TOKEN_URL="https://sso.init.stackspin.net/oauth2/token"
|
||||||
|
|
||||||
# Login facilitator paths
|
# Login facilitator paths
|
||||||
export KRATOS_PUBLIC_URL=http://localhost/kratos
|
export KRATOS_PUBLIC_URL=http://localhost/kratos
|
||||||
export KRATOS_ADMIN_URL=http://localhost:8000/admin
|
export KRATOS_ADMIN_URL=http://localhost:8000
|
||||||
export HYDRA_PUBLIC_URL="https://sso.init.stackspin.net"
|
export HYDRA_PUBLIC_URL="https://sso.init.stackspin.net"
|
||||||
export HYDRA_ADMIN_URL=http://localhost:4445
|
export HYDRA_ADMIN_URL=http://localhost:4445
|
||||||
export LOGIN_PANEL_URL=http://localhost/web/
|
export LOGIN_PANEL_URL=http://localhost/web/
|
||||||
|
|
|
@ -71,6 +71,25 @@ def settings():
|
||||||
|
|
||||||
return render_template("settings.html", api_url=KRATOS_PUBLIC_URL)
|
return render_template("settings.html", api_url=KRATOS_PUBLIC_URL)
|
||||||
|
|
||||||
|
@web.route("/error", methods=["GET"])
|
||||||
|
def error():
|
||||||
|
"""Show error messages from Kratos
|
||||||
|
|
||||||
|
Implements user-facing errors as described in https://www.ory.sh/docs/kratos/self-service/flows/user-facing-errors
|
||||||
|
|
||||||
|
:param id: error ID as given by Kratos
|
||||||
|
:return: redirect or settings page
|
||||||
|
"""
|
||||||
|
|
||||||
|
error_id = request.args.get("id")
|
||||||
|
api_response=""
|
||||||
|
try:
|
||||||
|
# Get Self-Service Errors
|
||||||
|
api_response = KRATOS_ADMIN.get_self_service_error(error_id)
|
||||||
|
except ory_kratos_client.ApiException as ex:
|
||||||
|
print("Exception when calling V0alpha2Api->get_self_service_error: %s\n" % ex)
|
||||||
|
|
||||||
|
return render_template("error.html", error_message=api_response)
|
||||||
|
|
||||||
@web.route("/login", methods=["GET", "POST"])
|
@web.route("/login", methods=["GET", "POST"])
|
||||||
def login():
|
def login():
|
||||||
|
|
|
@ -56,8 +56,8 @@ function flow_login() {
|
||||||
url: uri,
|
url: uri,
|
||||||
success: function(data) {
|
success: function(data) {
|
||||||
|
|
||||||
// Render login form (group: profile)
|
// Render login form (group: password)
|
||||||
var html = render_form(data, 'profile');
|
var html = render_form(data, 'password');
|
||||||
$("#contentLogin").html(html);
|
$("#contentLogin").html(html);
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -94,11 +94,15 @@ function flow_settings_validate() {
|
||||||
window.location.href = 'settings';
|
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();
|
||||||
|
|
||||||
// There was an error, Kratos does not specify what is
|
// For now, this code assumes that only the password can fail
|
||||||
// wrong. So we just show the general error message and
|
// validation. Other forms might need to be added in the future.
|
||||||
// let the user figure it out. We can re-use the flow-id
|
html = render_form(data, 'password')
|
||||||
$("#contentProfileSaveFailed").show();
|
$("#contentPassword").html(html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -134,12 +138,10 @@ function flow_settings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME: This seems to be not necessary anymore in kratos 0.9.0
|
|
||||||
// because they moved the password field to the profile group
|
|
||||||
// Render the password & profile form based on the fields we got
|
// Render the password & profile form based on the fields we got
|
||||||
// from the API
|
// from the API
|
||||||
// var html = render_form(data, 'profile');
|
var html = render_form(data, 'password');
|
||||||
// $("#contentPassword").html(html);
|
$("#contentPassword").html(html);
|
||||||
|
|
||||||
html = render_form(data, 'profile');
|
html = render_form(data, 'profile');
|
||||||
$("#contentProfile").html(html);
|
$("#contentProfile").html(html);
|
||||||
|
@ -251,9 +253,10 @@ function render_form(data, group) {
|
||||||
var name = node.attributes.name;
|
var name = node.attributes.name;
|
||||||
var type = node.attributes.type;
|
var type = node.attributes.type;
|
||||||
var value = node.attributes.value;
|
var value = node.attributes.value;
|
||||||
|
var messages = node.messages
|
||||||
|
|
||||||
if (node.group == 'default' || node.group == group) {
|
if (node.group == 'default' || node.group == group) {
|
||||||
var elm = getFormElement(type, name, value);
|
var elm = getFormElement(type, name, value, messages);
|
||||||
form += elm;
|
form += elm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -271,11 +274,18 @@ function render_form(data, group) {
|
||||||
// like "email" are also supported
|
// like "email" are also supported
|
||||||
// name: name of the field. Used when posting data
|
// name: name of the field. Used when posting data
|
||||||
// value: If there is already a value known, show it
|
// value: If there is already a value known, show it
|
||||||
function getFormElement(type, name, value) {
|
// messages: error messages related to the field
|
||||||
|
function getFormElement(type, name, value, messages) {
|
||||||
|
console.log("Getting form element", type, name, value, messages)
|
||||||
|
|
||||||
if (value == undefined) {
|
if (value == undefined) {
|
||||||
value = '';
|
value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof(messages) == "undefined") {
|
||||||
|
messages = []
|
||||||
|
}
|
||||||
|
|
||||||
if (name == 'email' || name == 'traits.email') {
|
if (name == 'email' || name == 'traits.email') {
|
||||||
return getFormInput(
|
return getFormInput(
|
||||||
'email',
|
'email',
|
||||||
|
@ -285,6 +295,7 @@ function getFormElement(type, name, value) {
|
||||||
'Please enter your e-mail address here',
|
'Please enter your e-mail address here',
|
||||||
'Please provide your e-mail address. We will send a recovery ' +
|
'Please provide your e-mail address. We will send a recovery ' +
|
||||||
'link to that e-mail address.',
|
'link to that e-mail address.',
|
||||||
|
messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +306,8 @@ function getFormElement(type, name, value) {
|
||||||
value,
|
value,
|
||||||
'Username',
|
'Username',
|
||||||
'Please provide an username',
|
'Please provide an username',
|
||||||
null
|
null,
|
||||||
|
messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +318,8 @@ function getFormElement(type, name, value) {
|
||||||
value,
|
value,
|
||||||
'Full name',
|
'Full name',
|
||||||
'Please provide your full name',
|
'Please provide your full name',
|
||||||
null
|
null,
|
||||||
|
messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -317,8 +330,9 @@ function getFormElement(type, name, value) {
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
'E-mail address',
|
'E-mail address',
|
||||||
'Please provide your e-mail address to login',
|
'Please provide your e-mail address to log in',
|
||||||
null
|
null,
|
||||||
|
messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,7 +343,8 @@ function getFormElement(type, name, value) {
|
||||||
value,
|
value,
|
||||||
'Password',
|
'Password',
|
||||||
'Please provide your password',
|
'Please provide your password',
|
||||||
null
|
null,
|
||||||
|
messages,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,7 +365,7 @@ function getFormElement(type, name, value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return getFormInput('input', name, value, name, null,null);
|
return getFormInput('input', name, value, name, null,null, messages);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -363,7 +378,12 @@ function getFormElement(type, name, value) {
|
||||||
// param label: Label to display above field
|
// param label: Label to display above field
|
||||||
// param placeHolder: Label to display in field if empty
|
// param placeHolder: Label to display in field if empty
|
||||||
// param help: Additional help text, displayed below the field in small font
|
// param help: Additional help text, displayed below the field in small font
|
||||||
function getFormInput(type, name, value, label, placeHolder, help) {
|
// param messages: Message about failed input
|
||||||
|
function getFormInput(type, name, value, label, placeHolder, help, messages) {
|
||||||
|
if (typeof(help) == "undefined" || help == null) {
|
||||||
|
help = ""
|
||||||
|
}
|
||||||
|
console.log("Messages: ", messages);
|
||||||
|
|
||||||
// Id field for help element
|
// Id field for help element
|
||||||
var nameHelp = name + "Help";
|
var nameHelp = name + "Help";
|
||||||
|
@ -372,6 +392,14 @@ function getFormInput(type, name, value, label, placeHolder, help) {
|
||||||
element += '<label for="'+name+'">'+label+'</label>';
|
element += '<label for="'+name+'">'+label+'</label>';
|
||||||
element += '<input type="'+type+'" class="form-control" id="'+name+'" name="'+name+'" ';
|
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']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If we are a password field, add a eye icon to reveal password
|
// If we are a password field, add a eye icon to reveal password
|
||||||
if (value) {
|
if (value) {
|
||||||
element += 'value="'+value+'" ';
|
element += 'value="'+value+'" ';
|
||||||
|
|
23
web/templates/error.html
Normal file
23
web/templates/error.html
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var api_url = '{{ api_url }}';
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
$(document).ready(function() {
|
||||||
|
flow_settings();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>Error: {{ error_message['error']['status'] }}</h2>
|
||||||
|
|
||||||
|
|
||||||
|
<div class=error-div>
|
||||||
|
{{ error_message['error']['message'] }}
|
||||||
|
{{ error_message['error']['reason'] }}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in a new issue