import datetime import functools import json import uuid import jwt from odoo import fields, http from odoo.http import request from odoo.tools import config JWT_ISSUER = 'odoo' JWT_AUDIENCE = 'odoo-api' def json_response(payload, status=200): return request.make_response( json.dumps(payload), headers=[('Content-Type', 'application/json')], status=status, ) def get_jwt_secret(): secret_key = config.get('jwt_secret') if not secret_key or len(secret_key) < 32: return None return secret_key def check_jwt_auth(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): auth_header = request.httprequest.headers.get('Authorization', '') if not auth_header.startswith('Bearer '): return json_response({'error': 'Bearer token is required.'}, status=401) secret_key = get_jwt_secret() if not secret_key: return json_response({'error': 'JWT secret is not configured.'}, status=500) token = auth_header.split(' ', 1)[1].strip() try: payload = jwt.decode( token, secret_key, algorithms=['HS256'], issuer=JWT_ISSUER, audience=JWT_AUDIENCE, options={'require': ['exp', 'iat', 'sub', 'client_id']}, ) user_id = int(payload['sub']) client = request.env['api.client'].sudo().search([ ('client_id', '=', payload['client_id']), ('active', '=', True), ('user_id', '=', user_id), ], limit=1) user = request.env['res.users'].sudo().browse(user_id).exists() if not client or not user or not user.active: return json_response({'error': 'Token subject is not allowed.'}, status=401) request.jwt_payload = payload request.jwt_client = client request.jwt_user_id = user_id request.update_env(user=user_id) except jwt.ExpiredSignatureError: return json_response({'error': 'Token has expired.'}, status=401) except (jwt.InvalidTokenError, ValueError): return json_response({'error': 'Token is invalid.'}, status=401) return func(self, *args, **kwargs) return wrapper class JwtAuthController(http.Controller): @http.route('/api/auth/token', type='http', auth='public', methods=['POST'], csrf=False) def generate_token(self, **kwargs): data = request.httprequest.get_json(silent=True) or kwargs client_id = data.get('client_id') client_secret = data.get('client_secret') or data.get('secret_key') if not client_id or not client_secret: return json_response({'error': 'client_id and client_secret are required.'}, status=400) secret_key = get_jwt_secret() if not secret_key: return json_response({'error': 'JWT secret is not configured.'}, status=500) client = request.env['api.client'].sudo().search([ ('client_id', '=', client_id), ('active', '=', True), ], limit=1) if not client or not client._check_secret(client_secret) or not client.user_id.active: return json_response({'error': 'Invalid credentials.'}, status=401) now = datetime.datetime.now(datetime.UTC) expires_at = now + datetime.timedelta(seconds=client.token_ttl) payload = { 'iss': JWT_ISSUER, 'aud': JWT_AUDIENCE, 'exp': expires_at, 'iat': now, 'jti': str(uuid.uuid4()), 'sub': str(client.user_id.id), 'client_id': client.client_id, } token = jwt.encode(payload, secret_key, algorithm='HS256') client.last_used = fields.Datetime.now() return json_response({ 'access_token': token, 'expires_in': client.token_ttl, 'token_type': 'Bearer', }) @http.route('/api/ifc/projects', type='http', auth='public', methods=['GET', 'POST'], csrf=False) @check_jwt_auth def get_ifc_projects(self, **kwargs): return json_response({ 'message': 'JWT token is valid.', 'user_id': request.env.user.id, 'user_name': request.env.user.name, 'client_id': request.jwt_payload.get('client_id'), 'data': ['Proyek_IFC_Gedung_A', 'Proyek_IFC_Infrastruktur_B'], })