Files
task-team/odoo_modules/task_team_connector/controllers/oauth.py
Claude CLI Agent 6c93ae04d0 Fix login redirect — access result.data.token/user
Login/register pages now correctly unwrap API response {data: {token, user}}.
Cleaned up leftover apiFetch code.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 10:30:05 +00:00

143 lines
4.7 KiB
Python

# Task Team Connector — OAuth2 Authorization Server bridge
import json
import logging
import urllib.parse
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class TaskTeamOAuthController(http.Controller):
"""
Implements a minimal OAuth2 Authorization Code flow so that
Task Team can authenticate users via their Odoo credentials.
Flow:
1. Task Team redirects browser to GET /task_team/oauth/authorize
2. Odoo shows a consent page (user must be logged in)
3. User approves → Odoo redirects to redirect_uri?code=XXX&state=YYY
4. Task Team backend calls POST /task_team/oauth/token to exchange code
5. Response includes Odoo user info + tt_user_id
6. Task Team uses tt_user_id / email to issue its own JWT
"""
# ------------------------------------------------------------------
# Authorization endpoint
# ------------------------------------------------------------------
@http.route(
'/task_team/oauth/authorize',
type='http',
auth='user',
methods=['GET', 'POST'],
website=False,
)
def authorize(
self,
client_id=None,
redirect_uri=None,
response_type=None,
state=None,
**kwargs,
):
ICP = request.env['ir.config_parameter'].sudo()
expected_client_id = ICP.get_param('task_team_connector.oauth_client_id', '')
def _error(msg, status=400):
return request.make_response(
json.dumps({'error': msg}),
headers=[('Content-Type', 'application/json')],
status=status,
)
if not client_id or client_id != expected_client_id:
return _error('invalid_client')
if response_type != 'code':
return _error('unsupported_response_type')
if request.httprequest.method == 'POST':
# User approved consent → issue code
code = request.env['task.team.oauth.code'].sudo().generate_code(
user_id=request.env.user.id,
client_id=client_id,
redirect_uri=redirect_uri or '',
state=state or '',
)
params = urllib.parse.urlencode(
{'code': code, 'state': state or ''}, quote_via=urllib.parse.quote
)
return request.redirect(f'{redirect_uri}?{params}')
# GET → render consent form
return request.render(
'task_team_connector.oauth_consent_template',
{
'client_id': client_id,
'redirect_uri': redirect_uri,
'state': state,
'user_name': request.env.user.name,
},
)
# ------------------------------------------------------------------
# Token endpoint
# ------------------------------------------------------------------
@http.route(
'/task_team/oauth/token',
type='json',
auth='public',
methods=['POST'],
csrf=False,
)
def token(self, **kwargs):
data = request.get_json_data()
grant_type = data.get('grant_type')
client_id = data.get('client_id')
client_secret = data.get('client_secret')
code = data.get('code')
ICP = request.env['ir.config_parameter'].sudo()
expected_client_id = ICP.get_param('task_team_connector.oauth_client_id', '')
expected_secret = ICP.get_param('task_team_connector.oauth_client_secret', '')
if client_id != expected_client_id or client_secret != expected_secret:
return {'error': 'invalid_client'}
if grant_type != 'authorization_code':
return {'error': 'unsupported_grant_type'}
if not code:
return {'error': 'invalid_request'}
code_record = (
request.env['task.team.oauth.code'].sudo().validate_and_consume(code, client_id)
)
if not code_record:
return {'error': 'invalid_grant'}
user = code_record.user_id
tt_user_id = user._tt_get_or_create_user()
return {
'token_type': 'bearer',
'odoo_user_id': user.id,
'odoo_email': user.email,
'odoo_name': user.name,
'tt_user_id': tt_user_id,
}
# ------------------------------------------------------------------
# Userinfo endpoint
# ------------------------------------------------------------------
@http.route('/task_team/oauth/userinfo', type='json', auth='user', methods=['GET'])
def userinfo(self, **kwargs):
user = request.env.user
return {
'sub': str(user.id),
'email': user.email,
'name': user.name,
'tt_user_id': user.tt_user_id,
}