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>
143 lines
4.7 KiB
Python
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,
|
|
}
|