diff --git a/backend/__init__.py b/backend/__init__.py index 8fcfa1024b..7e015d05d1 100644 --- a/backend/__init__.py +++ b/backend/__init__.py @@ -38,7 +38,9 @@ def format_url(endpoint): osm = OAuth2Session( - client_id=EnvironmentConfig.OAUTH_CLIENT_ID, scope=EnvironmentConfig.OAUTH_SCOPE + client_id=EnvironmentConfig.OAUTH_CLIENT_ID, + scope=EnvironmentConfig.OAUTH_SCOPE, + redirect_uri=EnvironmentConfig.OAUTH_REDIRECT_URI, ) # Import all models so that they are registered with SQLAlchemy diff --git a/backend/api/system/authentication.py b/backend/api/system/authentication.py index da582cde7d..cd8a52c5be 100644 --- a/backend/api/system/authentication.py +++ b/backend/api/system/authentication.py @@ -1,5 +1,6 @@ -from flask import current_app, redirect, request +from flask import current_app, request from flask_restful import Resource +from oauthlib.oauth2.rfc6749.errors import InvalidGrantError from backend import osm from backend.config import EnvironmentConfig @@ -20,16 +21,16 @@ def get(self): - application/json parameters: - in: query - name: callback_url + name: redirect_uri description: Route to redirect user once authenticated type: string default: /take/me/here responses: 200: - description: oauth token params + description: oauth2 params """ redirect_uri = request.args.get( - "redirect_uri", current_app.config["APP_BASE_URL"] + "redirect_uri", EnvironmentConfig.OAUTH_REDIRECT_URI ) authorize_url = f"{EnvironmentConfig.OSM_SERVER_URL}/oauth2/authorize" state = AuthenticationService.generate_random_state() @@ -50,6 +51,23 @@ def get(self): - system produces: - application/json + parameters: + - in: query + name: redirect_uri + description: Route to redirect user once authenticated + type: string + default: /take/me/here + required: false + - in: query + name: code + description: Code obtained after user authorization + type: string + required: true + - in: query + name: email_address + description: Email address to used for email notifications from TM. + type: string + required: false responses: 302: description: Redirects to login page, or login failed page @@ -62,26 +80,40 @@ def get(self): token_url = f"{EnvironmentConfig.OSM_SERVER_URL}/oauth2/token" authorization_code = request.args.get("code", None) if authorization_code is None: - return {"Error": "Missing code parameter"}, 500 + return {"Subcode": "InvalidData", "Error": "Missing code parameter"}, 500 email = request.args.get("email_address", None) - - osm_resp = osm.fetch_token( - token_url=token_url, - client_secret=EnvironmentConfig.OAUTH_CLIENT_SECRET, - code=authorization_code, + redirect_uri = request.args.get( + "redirect_uri", EnvironmentConfig.OAUTH_REDIRECT_URI ) - + osm.redirect_uri = redirect_uri + try: + osm_resp = osm.fetch_token( + token_url=token_url, + client_secret=EnvironmentConfig.OAUTH_CLIENT_SECRET, + code=authorization_code, + ) + except InvalidGrantError: + return { + "Error": "The provided authorization grant is invalid, expired or revoked", + "SubCode": "InvalidGrantError", + }, 400 if osm_resp is None: - current_app.logger.critical("No response from OSM") - return redirect(AuthenticationService.get_authentication_failed_url()) + current_app.logger.critical("Couldn't obtain token from OSM.") + return { + "Subcode": "TokenFetchError", + "Error": "Couldn't fetch token from OSM.", + }, 502 user_info_url = f"{EnvironmentConfig.OAUTH_API_URL}/user/details.json" osm_response = osm.get(user_info_url) # Get details for the authenticating user if osm_response.status_code != 200: current_app.logger.critical("Error response from OSM") - return redirect(AuthenticationService.get_authentication_failed_url()) + return { + "Subcode": "OSMServiceError", + "Error": "Couldn't fetch user details from OSM.", + }, 502 try: user_params = AuthenticationService.login_user(osm_response.json(), email) diff --git a/backend/config.py b/backend/config.py index 1db1421ce4..cf1781894b 100644 --- a/backend/config.py +++ b/backend/config.py @@ -123,6 +123,7 @@ class EnvironmentConfig: OAUTH_CLIENT_ID = os.getenv("TM_CLIENT_ID", None) OAUTH_CLIENT_SECRET = os.getenv("TM_CLIENT_SECRET", None) OAUTH_SCOPE = os.getenv("TM_SCOPE", None) + OAUTH_REDIRECT_URI = os.getenv("TM_REDIRECT_URI", None) # Some more definitions (not overridable) SQLALCHEMY_ENGINE_OPTIONS = { diff --git a/backend/services/users/user_service.py b/backend/services/users/user_service.py index 00784b6c39..6a647709ab 100644 --- a/backend/services/users/user_service.py +++ b/backend/services/users/user_service.py @@ -650,7 +650,7 @@ def get_recommended_projects(user_name: str, preferred_locale: str): len_projs = len(projs) if len_projs < limit: remaining_projs = ( - query.filter(Project.mapper_level == user.mapping_level) + query.filter(Project.difficulty == user.mapping_level) .limit(limit - len_projs) .all() ) diff --git a/frontend/src/components/dropdown.js b/frontend/src/components/dropdown.js index 0899d898ca..4565407a3c 100644 --- a/frontend/src/components/dropdown.js +++ b/frontend/src/components/dropdown.js @@ -175,9 +175,7 @@ export class _Dropdown extends React.PureComponent { onClick={this.toggleDropdown} className={`blue-dark ${this.props.className || ''}`} > -
- {this.getActiveOrDisplay()} -
+{this.getActiveOrDisplay()}