Skip to content

Commit

Permalink
Merge d8db706 into 25c6924
Browse files Browse the repository at this point in the history
  • Loading branch information
AmandaBirmingham committed Apr 24, 2020
2 parents 25c6924 + d8db706 commit c57c4ef
Show file tree
Hide file tree
Showing 11 changed files with 229 additions and 53 deletions.
30 changes: 30 additions & 0 deletions microsetta_private_api/example/client.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,36 @@ paths:
'302':
description: Redirecting to necessary action

'/workflow_update_email':
get:
operationId: microsetta_private_api.example.client_impl.get_workflow_update_email
responses:
'200':
description: Page asking user if they want to update their email
content:
text/html:
schema:
type: string
'302':
description: Redirecting to necessary action
post:
operationId: microsetta_private_api.example.client_impl.post_workflow_update_email
requestBody:
content:
application/x-www-form-urlencoded:
schema:
type: object
additionalProperties: true
responses:
'200':
description: Error report
content:
text/html:
schema:
type: string
'302':
description: Redirecting to necessary action

'/workflow_create_human_source':
get:
operationId: microsetta_private_api.example.client_impl.get_workflow_create_human_source
Expand Down
149 changes: 111 additions & 38 deletions microsetta_private_api/example/client_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,32 @@
WORKFLOW_URL = '/workflow'
HELP_EMAIL = "microsetta@ucsd.edu"
KIT_NAME_KEY = "kit_name"
EMAIL_CHECK_KEY = "email_checked"

ACCT_FNAME_KEY = "first_name"
ACCT_LNAME_KEY = "last_name"
ACCT_EMAIL_KEY = "email"
ACCT_ADDR_KEY = "address"
ACCT_WRITEABLE_KEYS = [ACCT_FNAME_KEY, ACCT_LNAME_KEY, ACCT_EMAIL_KEY,
ACCT_ADDR_KEY]

# States
NEEDS_REROUTE = "NeedsReroute"
NEEDS_LOGIN = "NeedsLogin"
NEEDS_ACCOUNT = "NeedsAccount"
NEEDS_EMAIL_CHECK = "NeedsEmailCheck"
NEEDS_HUMAN_SOURCE = "NeedsHumanSource"
NEEDS_SAMPLE = "NeedsSample"
NEEDS_PRIMARY_SURVEY = "NeedsPrimarySurvey"
ALL_DONE = "AllDone"


# Client might not technically care who the user is, but if they do, they
# get the token, validate it, and pull email out of it.
def parse_jwt(token):
decoded = jwt.decode(token, PUB_KEY, algorithms=['RS256'], verify=True)
return decoded["name"]
email_verified = decoded.get('email_verified', False)
return decoded["email"], email_verified


def rootpath():
Expand All @@ -56,6 +75,7 @@ def rootpath():

def home():
user = None
email_verified = False
acct_id = None
show_wizard = False

Expand All @@ -64,17 +84,23 @@ def home():
# If user leaves the page open, the token can expire before the
# session, so if our token goes back we need to force them to login
# again.
user = parse_jwt(session[TOKEN_KEY_NAME])
user, email_verified = parse_jwt(session[TOKEN_KEY_NAME])
except jwt.exceptions.ExpiredSignatureError:
return redirect('/logout')
workflow_needs, workflow_state = determine_workflow_state()
acct_id = workflow_state.get("account_id", None)
show_wizard = False # workflow_needs != ALL_DONE

if email_verified:
workflow_needs, workflow_state = determine_workflow_state()
if workflow_needs == NEEDS_REROUTE:
return workflow_state["reroute"]

acct_id = workflow_state.get("account_id", None)
show_wizard = False

# Note: home.jinja2 sends the user directly to authrocket to complete the
# login if they aren't logged in yet.
return render_template('home.jinja2',
user=user,
email_verified=email_verified,
acct_id=acct_id,
show_wizard=show_wizard,
endpoint=SERVER_CONFIG["endpoint"],
Expand All @@ -88,36 +114,52 @@ def authrocket_callback(token):

def logout():
if TOKEN_KEY_NAME in session:
del session[TOKEN_KEY_NAME]
# delete these keys if they are here, otherwise ignore
session.pop(TOKEN_KEY_NAME, None)
session.pop(KIT_NAME_KEY, None)
session.pop(EMAIL_CHECK_KEY, None)
return redirect("/home")


# States
NEEDS_REROUTE = "NeedsReroute"
NEEDS_LOGIN = "NeedsLogin"
NEEDS_ACCOUNT = "NeedsAccount"
NEEDS_HUMAN_SOURCE = "NeedsHumanSource"
NEEDS_SAMPLE = "NeedsSample"
NEEDS_PRIMARY_SURVEY = "NeedsPrimarySurvey"
ALL_DONE = "AllDone"


def determine_workflow_state():
current_state = {}
if TOKEN_KEY_NAME not in session:
return NEEDS_LOGIN, current_state

# Do they need to make an account? YES-> create_acct.html
needs_reroute, accts_output = ApiRequest.get("/accounts")
# if there's an error, reroute to error page
if needs_reroute:
current_state["reroute"] = accts_output
return NEEDS_REROUTE, current_state

if len(accts_output) == 0:
return NEEDS_ACCOUNT, current_state
# NB: Overwriting outputs from get call above
needs_reroute, accts_output = ApiRequest.post("/accounts/legacies")
if needs_reroute:
current_state["reroute"] = accts_output
return NEEDS_REROUTE, current_state
# if no legacy account found, need new account
if len(accts_output) == 0:
return NEEDS_ACCOUNT, current_state

acct_id = accts_output[0]["account_id"]
current_state['account_id'] = acct_id

# If we haven't yet checked for email mismatches and gotten user decision:
if not session.get(EMAIL_CHECK_KEY, False):
# Does email in our accounts table match email in authrocket?
needs_reroute, email_match = ApiRequest.get(
"/accounts/%s/email_match" % acct_id)
if needs_reroute:
current_state["reroute"] = email_match
return NEEDS_REROUTE, current_state
# if they don't match AND the user hasn't already refused update
if not email_match["email_match"]:
return NEEDS_EMAIL_CHECK, current_state

session[EMAIL_CHECK_KEY] = True

# Do they have a human source? NO-> consent.html
needs_reroute, sources_output = ApiRequest.get(
"/accounts/%s/sources" % (acct_id,), params={"source_type": "human"})
Expand Down Expand Up @@ -173,6 +215,8 @@ def workflow():
return redirect("/home")
elif next_state == NEEDS_ACCOUNT:
return redirect("/workflow_create_account")
elif next_state == NEEDS_EMAIL_CHECK:
return redirect("/workflow_update_email")
elif next_state == NEEDS_HUMAN_SOURCE:
return redirect("/workflow_create_human_source")
elif next_state == NEEDS_PRIMARY_SURVEY:
Expand All @@ -192,7 +236,7 @@ def get_workflow_create_account():
if next_state != NEEDS_ACCOUNT:
return redirect(WORKFLOW_URL)

email = parse_jwt(session[TOKEN_KEY_NAME])
email, _ = parse_jwt(session[TOKEN_KEY_NAME])
return render_template('create_acct.jinja2',
authorized_email=email)

Expand All @@ -204,10 +248,10 @@ def post_workflow_create_account(body):
session[KIT_NAME_KEY] = kit_name

api_json = {
"first_name": body['first_name'],
"last_name": body['last_name'],
"email": body['email'],
"address": {
ACCT_FNAME_KEY: body['first_name'],
ACCT_LNAME_KEY: body['last_name'],
ACCT_EMAIL_KEY: body['email'],
ACCT_ADDR_KEY: {
"street": body['street'],
"city": body['city'],
"state": body['state'],
Expand All @@ -224,6 +268,45 @@ def post_workflow_create_account(body):
return redirect(WORKFLOW_URL)


def get_workflow_update_email():
next_state, current_state = determine_workflow_state()
if next_state != NEEDS_EMAIL_CHECK:
return redirect(WORKFLOW_URL)

return render_template("update_email.jinja2")


def post_workflow_update_email(body):
next_state, current_state = determine_workflow_state()
if next_state != NEEDS_EMAIL_CHECK:
return redirect(WORKFLOW_URL)

# if the customer wants to update their email:
update_email = body["do_update"] == "Yes"
if update_email:
# get the existing account object
acct_id = current_state["account_id"]
do_return, acct_output = ApiRequest.get('/accounts/%s' % acct_id)
if do_return:
return acct_output

# change the email to the one in the authrocket account
authrocket_email, _ = parse_jwt(session[TOKEN_KEY_NAME])
acct_output[ACCT_EMAIL_KEY] = authrocket_email
# retain only writeable fields; KeyError if any of them missing
mod_acct = {k: acct_output[k] for k in ACCT_WRITEABLE_KEYS}

# write back the updated account info
do_return, put_output = ApiRequest.put(
'/accounts/%s' % acct_id, json=mod_acct)
if do_return:
return put_output

# even if they decided NOT to update, don't ask again this session
session[EMAIL_CHECK_KEY] = True
return redirect(WORKFLOW_URL)


def get_workflow_create_human_source():
next_state, current_state = determine_workflow_state()
if next_state != NEEDS_HUMAN_SOURCE:
Expand Down Expand Up @@ -348,16 +431,6 @@ def post_workflow_fill_primary_survey():
return redirect(WORKFLOW_URL)


# def view_account(account_id):
# if TOKEN_KEY_NAME not in session:
# return redirect(WORKFLOW_URL)
#
# sources = ApiRequest.get('/accounts/%s/sources' % account_id)
# return render_template('account.jinja2',
# acct_id=account_id,
# sources=sources)


def get_source(account_id, source_id):
next_state, current_state = determine_workflow_state()
if next_state != ALL_DONE:
Expand Down Expand Up @@ -487,14 +560,14 @@ def build_params(cls, params):

@classmethod
def _check_response(cls, response):
do_return = True
error_code = response.status_code
output = None

if response.status_code == 401:
# redirect to home page for login
if response.status_code == 401 or response.status_code == 403:
# output is redirect to home page for login or email verification
output = redirect("/home")
elif response.status_code >= 400:
# redirect to general error page
# output is general error page
error_txt = quote(response.text)
mailto_url = "mailto:{0}?subject={1}&body={2}".format(
HELP_EMAIL, quote("minimal interface error"), error_txt)
Expand All @@ -503,11 +576,11 @@ def _check_response(cls, response):
mailto_url=mailto_url,
error_msg=response.text)
else:
do_return = False
error_code = 0 # there is a response code but no *error* code
if response.text:
output = response.json()

return do_return, output
return error_code, output

@classmethod
def get(cls, path, params=None):
Expand Down
2 changes: 1 addition & 1 deletion microsetta_private_api/templates/create_acct.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</script>
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand Down
2 changes: 1 addition & 1 deletion microsetta_private_api/templates/error.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<link rel="stylesheet" href="/static/css/minimal_interface.css" />
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand Down
29 changes: 21 additions & 8 deletions microsetta_private_api/templates/home.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<link rel="stylesheet" href="/static/css/minimal_interface.css" />
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand All @@ -25,13 +25,8 @@

{% if user %}
<a href="{{authrocket_url}}/logout?redirect_uri={{endpoint}}/logout">Log Out</a>

{% if show_wizard %}
<div class="alert alert-primary" role="alert">
Need help getting started? <a href="/workflow" class="alert-link">Click here to set up your account</a>
</div>
{% endif %}
<div class="container">
{% if email_verified %}
<div class="list-group">
{% if acct_id %}
<a href="/workflow" class="list-group-item list-group-item-action">{{user}}'s account</a>
Expand All @@ -41,8 +36,26 @@
</a>
{% endif %}
</div>
{% else %}
<p>
It appears that your email has not yet been verified.
Please check your email account (and spam folder) for a verification email
from "The Microsetta Initiative" and follow its instructions.
This email will come from our authentication service AuthRocket,
and use the email address "noreply@loginrocket.com".
</p>
<p>
If you cannot locate the original verification email, have it resent by
clicking <a href = "{{ authrocket_url }}/profile">here</a> to view your AuthRocket profile and then
clicking the "resend verification email" link.
</p>
<p>
<button class = "btn btn-primary" onclick="window.location.replace('{{authrocket_url}}/login?redirect_uri={{endpoint}}/authrocket_callback');">
Refresh
</button>
</p>
{% endif %}
</div>

{% else %}
You are not logged in <br/>
<a href="{{authrocket_url}}/login?redirect_uri={{endpoint}}/authrocket_callback">Log In</a> or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</script>
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand Down
2 changes: 1 addition & 1 deletion microsetta_private_api/templates/sample.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
<link rel="stylesheet" href="/static/css/minimal_interface.css" />
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand Down
2 changes: 1 addition & 1 deletion microsetta_private_api/templates/sitebase.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<link rel="stylesheet" href="/static/css/minimal_interface.css" />
</head>
<body>
<a href="http://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<a href="https://microsetta.ucsd.edu" title="microsetta.ucsd.edu">
<img src="/static/img/logo-co.png" class="resize">
<br />
<br />
Expand Down

0 comments on commit c57c4ef

Please sign in to comment.