feat(shopfloor): /fp/tablet/lock_session destroys tech session

Writes lock event (manual/idle/ceiling) with duration computed from
the open unlock event. Then logout + re-authenticate as kiosk via
the password stored in ir.config_parameter['fp.tablet.kiosk_password'].

Falls back to 'needs_kiosk_relogin' if the kiosk password is missing
(sysadmin must log in manually). Logs every event for forensic
review.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 12:54:08 -04:00
parent 086ff512b6
commit 4911088dc1

View File

@@ -321,6 +321,100 @@ class FpTabletController(http.Controller):
'tech_name': target.name,
}
# ======================================================================
# /fp/tablet/lock_session — destroy tech session + re-auth as kiosk
# ======================================================================
@http.route('/fp/tablet/lock_session', type='jsonrpc', auth='user')
def lock_session(self, reason='manual'):
"""Lock the tablet — destroy the tech's session and re-auth the
browser as fp_tablet_kiosk. Audit-log the event.
`reason` is one of 'manual' / 'idle' / 'ceiling' — controls which
event_type gets written. The corresponding event_type names:
manual -> manual_lock
idle -> idle_lock
ceiling -> ceiling_lock
Anything else falls back to manual_lock.
"""
from ._tablet_session_audit import write_event, _sha256_session_sid
env = request.env
now = fields.Datetime.now()
sid = request.session.sid
tech_id = env.uid
# Determine the audit event_type
event_type_map = {
'manual': 'manual_lock',
'idle': 'idle_lock',
'ceiling': 'ceiling_lock',
}
event_type = event_type_map.get(reason, 'manual_lock')
# Find the matching open session_event so we can compute duration.
# We look for the most recent unlock for this user without a
# session_ended_at — that's the open one.
SessionEvent = env['fp.tablet.session.event'].sudo()
open_event = SessionEvent.search([
('event_type', '=', 'unlock'),
('user_id', '=', tech_id),
('session_ended_at', '=', False),
], order='create_date desc', limit=1)
session_started_at = (
open_event.session_started_at if open_event else False
)
duration_seconds = None
if session_started_at:
duration_seconds = int((now - session_started_at).total_seconds())
# Write the lock event BEFORE destroying the session (we lose
# env.uid after logout).
write_event(env,
event_type=event_type,
user_id=tech_id,
session_id_hash=_sha256_session_sid(sid),
session_started_at=session_started_at,
session_ended_at=now,
duration_seconds=duration_seconds)
_logger.info(
'Tablet locked (reason=%s) for uid %s (sid %s..) duration=%ss',
reason, tech_id, sid[:8] if sid else '', duration_seconds,
)
# Destroy the tech session
request.session.logout(keep_db=True)
# Re-authenticate as the kiosk user. Credential comes from
# ir.config_parameter (auto-generated on first install in
# post-migrate.py).
kiosk_login = 'fp_tablet_kiosk@enplating.local'
kiosk_password = env['ir.config_parameter'].sudo().get_param(
'fp.tablet.kiosk_password', ''
)
if not kiosk_password:
_logger.error(
'fp.tablet.kiosk_password missing from ir.config_parameter; '
'cannot re-auth tablet as kiosk. The browser will need '
'manual login.'
)
return {'ok': True, 'locked_at': now.isoformat(),
'needs_kiosk_relogin': True}
try:
request.session.authenticate(
request.db,
{'type': 'password',
'login': kiosk_login,
'password': kiosk_password},
)
except Exception as e:
_logger.exception(
'Failed to re-auth tablet as kiosk: %s', e)
return {'ok': True, 'locked_at': now.isoformat(),
'needs_kiosk_relogin': True}
return {'ok': True, 'locked_at': now.isoformat()}
# ======================================================================
# /fp/tablet/tiles — lock-screen tile grid
# ======================================================================