feat(shopfloor): fp_tablet_pin custom auth manager

Validates PIN hash + shop-branch role membership when the credential
type is fp_tablet_pin. Goes through Odoo's standard _check_credentials
chain so future 2FA / IP-gate modules layer cleanly on top.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-24 12:30:24 -04:00
parent 1dea752a29
commit dd0dc26232

View File

@@ -139,3 +139,50 @@ class ResUsers(models.Model):
'name': 'Set Tablet PIN',
'target': 'new',
}
def _check_credentials(self, credential, env):
"""Custom auth manager: accept `type='fp_tablet_pin'` credential.
Validates the PIN hash, that the user is active, and that they hold
at least one shop-branch plating role. On success, returns the auth
info dict Odoo's session expects. On failure, raises AccessDenied
so the standard auth chain returns a 401.
See docs/superpowers/specs/2026-05-24-tablet-pin-session-redesign-design.md
Section 2 — Auth path.
"""
from odoo.exceptions import AccessDenied
if isinstance(credential, dict) and credential.get('type') == 'fp_tablet_pin':
login = credential.get('login')
pin = credential.get('pin')
if not login or not pin:
raise AccessDenied()
user_sudo = self.sudo().search([('login', '=', login)], limit=1)
if not user_sudo or not user_sudo.active:
raise AccessDenied()
# Must hold a shop-branch role (otherwise they can't operate the tablet)
shop_branch_xmlids = (
'fusion_plating.group_fp_technician',
'fusion_plating.group_fp_shop_manager_v2',
'fusion_plating.group_fp_manager',
'fusion_plating.group_fp_quality_manager',
'fusion_plating.group_fp_owner',
)
shop_branch_ids = {
g.id for g in (
self.env.ref(x, raise_if_not_found=False)
for x in shop_branch_xmlids
) if g
}
user_group_ids = set(user_sudo.group_ids.ids)
if not (shop_branch_ids & user_group_ids):
raise AccessDenied()
# Verify the PIN hash. verify_tablet_pin already exists.
if not user_sudo.verify_tablet_pin(pin):
raise AccessDenied()
return {
'uid': user_sudo.id,
'auth_method': 'fp_tablet_pin',
'mfa': 'default',
}
return super()._check_credentials(credential, env)