feat(fusion_login_audit): hook successful login via _update_last_login
Overrides res.users._update_last_login to create a fusion.login.audit row with result=success after the parent runs. The write goes through sudo() + mail_create_nolog=True. Any exception in the audit path is caught and logged but never propagates — a broken audit table must never block a real user from logging in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -100,3 +100,37 @@ class ResUsers(models.Model):
|
||||
del _credential # explicit no-op — locks down the read surface
|
||||
|
||||
return vals
|
||||
|
||||
def _fc_record_login_event(self, result, failure_reason=None,
|
||||
user_id=None, attempted_login=None,
|
||||
_credential=None):
|
||||
"""Build vals + create the audit row via sudo. Never raises.
|
||||
|
||||
Audit writes are wrapped so that a broken audit table can never
|
||||
block a real user from logging in. The exception is logged and
|
||||
swallowed; auth proceeds normally.
|
||||
"""
|
||||
try:
|
||||
vals = self._fc_build_event_vals(
|
||||
result=result,
|
||||
attempted_login=attempted_login
|
||||
or (self.login if self else None)
|
||||
or 'unknown',
|
||||
failure_reason=failure_reason,
|
||||
user_id=user_id or (self.id if self else None),
|
||||
_credential=_credential,
|
||||
)
|
||||
self.env['fusion.login.audit'].sudo().with_context(
|
||||
mail_create_nolog=True
|
||||
).create(vals)
|
||||
except Exception:
|
||||
_logger.exception(
|
||||
"fusion_login_audit: failed to record %s row for %s",
|
||||
result, attempted_login or (self.login if self else 'unknown'),
|
||||
)
|
||||
|
||||
def _update_last_login(self):
|
||||
result = super()._update_last_login()
|
||||
# Self is the singleton recordset of the user that just logged in.
|
||||
self._fc_record_login_event(result='success')
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user