diff --git a/app/main/views/index.py b/app/main/views/index.py index 8b965991c..8d00305c7 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -35,24 +35,24 @@ def index(): if current_user and current_user.is_authenticated: return redirect(url_for("main.choose_account")) - token = generate_token( + # make and store the state + state = generate_token( str(request.remote_addr), current_app.config["SECRET_KEY"], current_app.config["DANGEROUS_SALT"], ) - url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") - # handle unit tests - - current_app.logger.warning(f"############### {str(request.remote_addr)}") + state_key = f"login-nonce-{unquote(state)}" + redis_client.set(state_key, state) + # make and store the nonce nonce = secrets.token_urlsafe() + nonce_key = f"login-nonce-{unquote(nonce)}" + redis_client.set(nonce_key, nonce) - redis_key = f"login-nonce-{unquote(nonce)}" - redis_client.set(redis_key, nonce) - + url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") if url is not None: url = url.replace("NONCE", nonce) - url = url.replace("STATE", token) + url = url.replace("STATE", state) return render_template( "views/signedout.html", sms_rate=CURRENT_SMS_RATE, diff --git a/app/main/views/sign_in.py b/app/main/views/sign_in.py index 9c8fbb3f0..34f5badb3 100644 --- a/app/main/views/sign_in.py +++ b/app/main/views/sign_in.py @@ -66,8 +66,8 @@ def _get_access_token(code): # pragma: no cover response_json = response.json() id_token = get_id_token(response_json) nonce = id_token["nonce"] - redis_key = f"login-nonce-{unquote(nonce)}" - stored_nonce = redis_client.get(redis_key).decode("utf8") + nonce_key = f"login-nonce-{unquote(nonce)}" + stored_nonce = redis_client.get(nonce_key).decode("utf8") if nonce != stored_nonce: current_app.logger.error(f"Nonce Error: {nonce} != {stored_nonce}") @@ -99,6 +99,12 @@ def _do_login_dot_gov(): # $ pragma: no cover # start login.gov code = request.args.get("code") state = request.args.get("state") + state_key = f"login-state-{unquote(state)}" + stored_state = redis_client.get(state_key).decode("utf8") + if state != stored_state: + current_app.logger.error(f"State Error: {state} != {stored_state}") + abort(403) + login_gov_error = request.args.get("error") if login_gov_error: @@ -203,21 +209,23 @@ def sign_in(): # pragma: no cover return redirect(redirect_url) return redirect(url_for("main.show_accounts_or_dashboard")) - token = generate_token( + state = generate_token( str(request.remote_addr), current_app.config["SECRET_KEY"], current_app.config["DANGEROUS_SALT"], ) - url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") + state_key = f"login-state-{unquote(state)}" + redis_client.set(state_key, state) nonce = secrets.token_urlsafe() - redis_key = f"login-nonce-{unquote(nonce)}" - redis_client.set(redis_key, nonce) + nonce_key = f"login-nonce-{unquote(nonce)}" + redis_client.set(nonce_key, nonce) + url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") # handle unit tests if url is not None: url = url.replace("NONCE", nonce) - url = url.replace("STATE", token) + url = url.replace("STATE", state) return render_template( "views/signin.html", diff --git a/app/notify_client/invite_api_client.py b/app/notify_client/invite_api_client.py index 711cc1f55..d45137981 100644 --- a/app/notify_client/invite_api_client.py +++ b/app/notify_client/invite_api_client.py @@ -1,12 +1,15 @@ import secrets from urllib.parse import unquote +from flask import current_app, request + from app import redis_client from app.notify_client import NotifyAdminAPIClient, _attach_current_user, cache from app.utils.user_permissions import ( all_ui_permissions, translate_permissions_from_ui_to_db, ) +from notifications_utils.url_safe_token import generate_token class InviteApiClient(NotifyAdminAPIClient): @@ -37,11 +40,21 @@ class InviteApiClient(NotifyAdminAPIClient): } data = _attach_current_user(data) + # make and store the state + state = generate_token( + str(request.remote_addr), + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + ) + state_key = f"login-state-{unquote(state)}" + redis_client.set(state_key, state) + # make and store the nonce nonce = secrets.token_urlsafe() redis_key = f"login-nonce-{unquote(nonce)}" redis_client.set(f"{redis_key}", nonce) # save the nonce to redis. data["nonce"] = nonce # This is passed to api for the invite url. + data["state"] = state # This is passed to api for the invite url. resp = self.post(url=f"/service/{service_id}/invite", data=data) return resp["data"]