import logging from datetime import datetime, timedelta import jwt as pyjwt from odoo import http from odoo.exceptions import AccessDenied from odoo.http import request from .base import EncoachMixin _logger = logging.getLogger(__name__) class AuthController(EncoachMixin, http.Controller): def _create_jwt(self, user, secret, expires_hours=72): payload = { 'user_id': user.id, 'email': user.login, 'iat': datetime.utcnow(), 'exp': datetime.utcnow() + timedelta(hours=expires_hours), } return pyjwt.encode(payload, secret, algorithm='HS256') @http.route('/api/login', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def login(self, **kwargs): try: body = self._get_json_body() email = (body.get('credential') or body.get('email') or '').strip() password = body.get('password', '') if not email or not password: return self._error_response('Email and password are required', 400) user = request.env['res.users'].sudo().search([ ('login', '=', email), ], limit=1) if not user: return self._error_response('Invalid email or password', 401) try: credential = {'type': 'password', 'login': email, 'password': password} request.env['res.users'].sudo().authenticate( credential, {'interactive': False}, ) except AccessDenied: return self._error_response('Invalid email or password', 401) secret = request.env['ir.config_parameter'].sudo().get_param('encoach.jwt_secret') if not secret: return self._error_response('Server configuration error', 500) token = self._create_jwt(user, secret) return self._json_response({ 'user': self._serialize(user), 'token': token, }) except Exception: _logger.exception("Login error") return self._error_response('Internal server error', 500) @http.route('/api/logout', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def logout(self, **kwargs): try: return self._json_response({'ok': True}) except Exception: _logger.exception("Logout error") return self._error_response('Internal server error', 500) @http.route('/api/reset', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def reset_password(self, **kwargs): try: body = self._get_json_body() email = body.get('email', '').strip() new_password = body.get('password', '') token = body.get('token', '') if not email: return self._error_response('Email is required', 400) user = request.env['res.users'].sudo().search([('login', '=', email)], limit=1) if not user: return self._json_response({'ok': True}) if token and new_password: secret = request.env['ir.config_parameter'].sudo().get_param('encoach.jwt_secret') try: payload = pyjwt.decode(token, secret, algorithms=['HS256']) except pyjwt.InvalidTokenError: return self._error_response('Invalid or expired reset token', 400) if payload.get('user_id') != user.id or payload.get('purpose') != 'password_reset': return self._error_response('Invalid reset token', 400) user.sudo().write({'password': new_password}) return self._json_response({'ok': True}) return self._json_response({'ok': True}) except Exception: _logger.exception("Password reset error") return self._error_response('Internal server error', 500) @http.route('/api/reset/sendVerification', type='http', auth='public', methods=['POST', 'OPTIONS'], csrf=False, cors='*') def send_verification(self, **kwargs): try: body = self._get_json_body() email = body.get('email', '').strip() if not email: return self._error_response('Email is required', 400) return self._json_response({'ok': True}) except Exception: _logger.exception("Send verification error") return self._error_response('Internal server error', 500)