import time import jwt from flask import ( Blueprint, request, jsonify, current_app, make_response, ) from flask_cors import cross_origin from atlas_um.tokens.bearer_tokens import DNABearerToken from atlas_um.audit import user_audit from atlas_um.extensions import csrf from atlas_um.consts import SystemEvents from atlas_um.helpers.domains import get_allowed_root_domain from atlas_um.helpers.tokens import get_safe_repr from atlas_um.logs import logger from atlas_um.settings import Settings from atlas_um.tokens.services import RotateTokensService token = Blueprint("token", __name__) @token.route("", methods=["GET", "OPTIONS", "POST"]) @cross_origin(supports_credentials=True, origins=Settings.ORIGINS) @csrf.exempt def get_token(): """ Returns token in the response body.user_aud Intended to use in cross origin environments where we need to exchange cookie token for token in response body. Doing token validation as an extra security measure and to get the expected expiration time. """ token = request.cookies.get( current_app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME") ) if not token: logger.warning("No access token in cookies") return "Access denied", 401 refresh_token = request.cookies.get( current_app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) public_key = current_app.config["DNA_IDENTITY_PUBLIC_KEY"] try: claimset = jwt.decode( token, public_key, DNABearerToken.ENCODING_ALGORITHM ) except jwt.PyJWTError: logger.bind( token=get_safe_repr(token), refresh_token=get_safe_repr(refresh_token), ).warning("Unable to validate the token") return "Access denied", 401 expires_in = claimset["exp"] - int(time.time()) return jsonify( { "access_token": token, "refresh_token": refresh_token, "token_type": "Bearer", "expires_in": expires_in, } ) @token.route("/refresh", methods=["OPTIONS", "POST"]) @cross_origin(supports_credentials=True, origins=Settings.ORIGINS) @csrf.exempt def refresh_token_view(): """ Returns refreshed tokens in the response body, and also updates them in cookies. """ refresh_token = request.form.get("refresh_token") or request.cookies.get( current_app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME") ) if not refresh_token: logger.warning("No refresh token in the request") return "Access denied", 401 resource_group_hint_name = request.form.get("resource_group") result = RotateTokensService.execute( refresh_token, resource_group_hint_name ) if result.is_left: logger.bind( error=result.value, refresh_token=get_safe_repr(refresh_token) ).warning("Error rotating the refresh token") return "Access denied", 401 old_refresh_token = refresh_token refresh_token, dna_bearer_token, dna_account = result.value resp = make_response( jsonify( { "access_token": dna_bearer_token, "refresh_token": refresh_token, "token_type": "Bearer", "expires_in": DNABearerToken.DEFAULT_TIME_TO_LIVE, } ) ) cookie_max_age = current_app.config.get("TOKEN_COOKIES_MAX_AGE") domain = get_allowed_root_domain() domain_cookie = f".{domain}" if domain else None resp.set_cookie( current_app.config.get("DNA_BEARER_TOKEN_COOKIE_NAME"), dna_bearer_token, httponly=True, domain=domain_cookie, secure=True, max_age=cookie_max_age, ) resp.set_cookie( current_app.config.get("DNA_REFRESH_TOKEN_COOKIE_NAME"), refresh_token, httponly=True, domain=domain_cookie, secure=True, max_age=cookie_max_age, ) user_audit.log( SystemEvents.successful_token_refresh, user_id=dna_account.sub, old_refresh_token=get_safe_repr(old_refresh_token), new_refresh_token=get_safe_repr(refresh_token), access_token=get_safe_repr(dna_bearer_token), ) logger.bind( dna_account=dna_account, old_refresh_token=get_safe_repr(old_refresh_token), new_refresh_token=get_safe_repr(refresh_token), access_token=get_safe_repr(dna_bearer_token), domain=domain, domain_cookie=domain_cookie, ).info("Processed token refresh") return resp