forked from Github-Mirrors/canaille
feat: sign in/out events are logged in #177
This commit is contained in:
parent
053156ec18
commit
920395c27f
5 changed files with 56 additions and 4 deletions
|
@ -1,3 +1,8 @@
|
|||
Added
|
||||
^^^^^
|
||||
|
||||
- Sign in/out events are logged in :issuer:`177`
|
||||
|
||||
[0.0.49] - 2024-04-08
|
||||
---------------------
|
||||
|
||||
|
|
|
@ -259,8 +259,9 @@ class CoreSettings(BaseModel):
|
|||
LOGGING: Optional[Union[str, Dict]] = None
|
||||
"""Configures the logging output using the python logging configuration format:
|
||||
|
||||
- if :py:data:`None`, everything is logged in the standard output
|
||||
the log level is :py:data:`~logging.DEBUG` if the :attr:`~canaille.app.configuration.RootSettings.DEBUG` setting is :py:data:`True`, else this is :py:data:`~logging.INFO`
|
||||
- if :py:data:`None`, everything is logged in the standard error output
|
||||
the log level is :py:data:`~logging.DEBUG` if the :attr:`~canaille.app.configuration.RootSettings.DEBUG`
|
||||
setting is :py:data:`True`, else this is :py:data:`~logging.INFO`
|
||||
- if this is a :class:`dict`, it is passed to :func:`logging.config.dictConfig`:
|
||||
- if this is a :class:`str`, it is expected to be a file path that will be passed
|
||||
to :func:`logging.config.fileConfig`
|
||||
|
|
|
@ -85,13 +85,20 @@ def password():
|
|||
)
|
||||
|
||||
success, message = BaseBackend.get().check_user_password(user, form.password.data)
|
||||
request_ip = request.remote_addr or "unknown IP"
|
||||
if not success:
|
||||
logout_user()
|
||||
current_app.logger.info(
|
||||
f'Failed login attempt for {session["attempt_login"]} from {request_ip}'
|
||||
)
|
||||
flash(message or _("Login failed, please check your information"), "error")
|
||||
return render_template(
|
||||
"password.html", form=form, username=session["attempt_login"]
|
||||
)
|
||||
|
||||
current_app.logger.info(
|
||||
f'Succeed login attempt for {session["attempt_login"]} from {request_ip}'
|
||||
)
|
||||
del session["attempt_login"]
|
||||
login_user(user)
|
||||
flash(
|
||||
|
@ -106,6 +113,9 @@ def logout():
|
|||
user = current_user()
|
||||
|
||||
if user:
|
||||
request_ip = request.remote_addr or "unknown IP"
|
||||
current_app.logger.info(f"Logout {user.identifier} from {request_ip}")
|
||||
|
||||
flash(
|
||||
_(
|
||||
"You have been disconnected. See you next time %(user)s",
|
||||
|
|
|
@ -126,6 +126,26 @@ def configuration(smtpd):
|
|||
"PASSWORD": smtpd.config.login_password,
|
||||
"FROM_ADDR": "admin@mydomain.tld",
|
||||
},
|
||||
"LOGGING": {
|
||||
"version": 1,
|
||||
"formatters": {
|
||||
"default": {
|
||||
"format": "[%(asctime)s] %(levelname)s in %(module)s: %(message)s",
|
||||
}
|
||||
},
|
||||
"handlers": {
|
||||
"wsgi": {
|
||||
"class": "logging.StreamHandler",
|
||||
"stream": "ext://sys.stdout",
|
||||
"formatter": "default",
|
||||
}
|
||||
},
|
||||
"root": {"level": "DEBUG", "handlers": ["wsgi"]},
|
||||
"loggers": {
|
||||
"faker": {"level": "WARNING"},
|
||||
},
|
||||
"disable_existing_loggers": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
return conf
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import datetime
|
||||
import logging
|
||||
from unittest import mock
|
||||
|
||||
from flask import g
|
||||
|
@ -20,7 +21,7 @@ def test_index(testclient, user):
|
|||
assert res.location == "/about"
|
||||
|
||||
|
||||
def test_signin_and_out(testclient, user):
|
||||
def test_signin_and_out(testclient, user, caplog):
|
||||
with testclient.session_transaction() as session:
|
||||
assert not session.get("user_id")
|
||||
|
||||
|
@ -40,6 +41,11 @@ def test_signin_and_out(testclient, user):
|
|||
"success",
|
||||
"Connection successful. Welcome John (johnny) Doe",
|
||||
) in res.flashes
|
||||
assert (
|
||||
"canaille",
|
||||
logging.INFO,
|
||||
"Succeed login attempt for user from unknown IP",
|
||||
) in caplog.record_tuples
|
||||
res = res.follow(status=302)
|
||||
res = res.follow(status=200)
|
||||
|
||||
|
@ -54,6 +60,11 @@ def test_signin_and_out(testclient, user):
|
|||
"success",
|
||||
"You have been disconnected. See you next time John (johnny) Doe",
|
||||
) in res.flashes
|
||||
assert (
|
||||
"canaille",
|
||||
logging.INFO,
|
||||
"Logout user from unknown IP",
|
||||
) in caplog.record_tuples
|
||||
res = res.follow(status=302)
|
||||
res = res.follow(status=200)
|
||||
|
||||
|
@ -74,7 +85,7 @@ def test_visitor_logout(testclient, user):
|
|||
assert not session.get("user_id")
|
||||
|
||||
|
||||
def test_signin_wrong_password(testclient, user):
|
||||
def test_signin_wrong_password(testclient, user, caplog):
|
||||
with testclient.session_transaction() as session:
|
||||
assert not session.get("user_id")
|
||||
|
||||
|
@ -86,6 +97,11 @@ def test_signin_wrong_password(testclient, user):
|
|||
res.form["password"] = "incorrect horse"
|
||||
res = res.form.submit(status=200)
|
||||
assert ("error", "Login failed, please check your information") in res.flashes
|
||||
assert (
|
||||
"canaille",
|
||||
logging.INFO,
|
||||
"Failed login attempt for user from unknown IP",
|
||||
) in caplog.record_tuples
|
||||
|
||||
|
||||
def test_signin_password_substring(testclient, user):
|
||||
|
|
Loading…
Reference in a new issue