Skip to content

Commit 07cacfa

Browse files
committed
Here is all the code from the twitch stream today exactly as typed.
1 parent f359e65 commit 07cacfa

File tree

14 files changed

+184
-13
lines changed

14 files changed

+184
-13
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,6 @@ app/ch13-validation/starter/.idea/inspectionProfiles/Project_Default.xml
175175
app/ch14_testing/final/.idea/inspectionProfiles/Project_Default.xml
176176
app/ch14_testing/starter/.idea/inspectionProfiles/Project_Default.xml
177177
app/ch15_deploy/final/.idea/inspectionProfiles/Project_Default.xml
178+
app/ch15_deploy/final/pypi_org/flask_session
179+
d43be29f-e2f2-41e5-8dde-ed049a1776b6.xml
180+
dataSources.local.xml

app/ch15_deploy/final/.idea/codeStyles/codeStyleConfig.xml

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ch15_deploy/final/.idea/dataSources.xml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ch15_deploy/final/.idea/dictionaries/mkennedy.xml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ch15_deploy/final/.idea/flask-deploy.iml

Lines changed: 1 addition & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ch15_deploy/final/.idea/misc.xml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/ch15_deploy/final/pypi_org/app.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22
import sys
33

44
import flask
5+
from flask_session import Session
6+
7+
from pypi_org.configs import app_config
58

69
folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
710
sys.path.insert(0, folder)
811
import pypi_org.data.db_session as db_session
912

1013
app = flask.Flask(__name__)
14+
app.config.from_object(app_config)
1115

1216

1317
def main():
1418
configure()
15-
app.run(debug=True, port=5006)
19+
app.run(debug=True, port=5006, host='localhost')
1620

1721

1822
def configure():
@@ -23,9 +27,15 @@ def configure():
2327

2428
setup_db()
2529
print("DB setup completed.")
30+
31+
setup_session_server()
2632
print("", flush=True)
2733

2834

35+
def setup_session_server():
36+
Session(app)
37+
38+
2939
def setup_db():
3040
db_file = os.path.join(
3141
os.path.dirname(__file__),
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
b2c_tenant = "talkpythondemos"
2+
signupsignin_user_flow = "B2C_1_susi"
3+
editprofile_user_flow = "B2C_1_profileediting1"
4+
resetpassword_user_flow = "B2C_1_passwordreset1"
5+
authority_template = "https://{tenant}.b2clogin.com/{tenant}.onmicrosoft.com/{user_flow}"
6+
7+
CLIENT_ID = "115c67cd-3558-4bc4-9180-51c6d2d4fa45" # Application (client) ID of app registration
8+
9+
CLIENT_SECRET = "s.4i3ff~139.58~3DMuE42KvZ8-X4ytAS9" # Placeholder - for use ONLY during testing.
10+
# In a production app, we recommend you use a more secure method of storing your secret,
11+
# like Azure Key Vault. Or, use an environment variable as described in Flask's documentation:
12+
# https://flask.palletsprojects.com/en/1.1.x/config/#configuring-from-environment-variables
13+
# CLIENT_SECRET = os.getenv("CLIENT_SECRET")
14+
# if not CLIENT_SECRET:
15+
# raise ValueError("Need to define CLIENT_SECRET environment variable")
16+
17+
AUTHORITY = authority_template.format(
18+
tenant=b2c_tenant, user_flow=signupsignin_user_flow)
19+
20+
REDIRECT_PATH = "/account/auth" # Used for forming an absolute URL to your redirect URI.
21+
# The absolute URL must match the redirect URI you set
22+
# in the app's registration in the Azure portal.
23+
24+
# This is the API resource endpoint
25+
ENDPOINT = '' # Application ID URI of app registration in Azure portal
26+
27+
# These are the scopes you've exposed in the web API app registration in the Azure portal
28+
SCOPE = [] # Example with two exposed scopes: ["demo.read", "demo.write"]
29+
30+
SESSION_TYPE = "filesystem" # Specifies the token cache should be stored in server-side session
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import msal
2+
from flask import session
3+
4+
5+
def load_cache():
6+
cache = msal.SerializableTokenCache()
7+
if session.get("token_cache"):
8+
cache.deserialize(session["token_cache"])
9+
return cache
10+
11+
12+
def save_cache(cache):
13+
if cache.has_state_changed:
14+
session["token_cache"] = cache.serialize()

app/ch15_deploy/final/pypi_org/services/user_service.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ def create_user(name: str, email: str, password: str) -> Optional[User]:
4141

4242

4343
def hash_text(text: str) -> str:
44-
hashed_text = crypto.encrypt(text, rounds=171204)
44+
hashed_text = crypto.encrypt(text, rounds=171_204)
4545
return hashed_text
4646

4747

app/ch15_deploy/final/pypi_org/templates/shared/_layout.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
<li class="nav-item"><a class="nav-link" href="/account">Account</a></li>
4141
<li class="nav-item"><a class="nav-link" href="/account/logout" id="last_nav_link">Logout</a></li>
4242
{% else %}
43-
<li class="nav-item"><a class="nav-link" href="/account/login">Login</a></li>
44-
<li class="nav-item"><a class="nav-link" href="/account/register" id="last_nav_link">Register</a></li>
43+
<li class="nav-item"><a class="nav-link" href="/account/begin_auth">Login or Register</a></li>
44+
{# <li class="nav-item"><a class="nav-link" href="/account/register" id="last_nav_link">Register</a></li>#}
4545
{% endif %}
4646

4747
</ul>

app/ch15_deploy/final/pypi_org/viewmodels/shared/viewmodelbase.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
import uuid
12
from typing import Optional
23

34
import flask
5+
import msal
46
from flask import Request
57

8+
from pypi_org.configs import app_config
69
from pypi_org.infrastructure import request_dict, cookie_auth
710

811

@@ -15,4 +18,21 @@ def __init__(self):
1518
self.user_id: Optional[int] = cookie_auth.get_user_id_via_auth_cookie(self.request)
1619

1720
def to_dict(self):
18-
return self.__dict__
21+
d = self.__dict__
22+
d['build_auth_url'] = self.build_auth_url
23+
24+
return d
25+
26+
# noinspection PyMethodMayBeStatic
27+
def build_auth_url(self, authority=None, scopes=None, state=None):
28+
return self.build_msal_app(authority=authority).get_authorization_request_url(
29+
scopes or [],
30+
state=state or str(uuid.uuid4()),
31+
redirect_uri="http://localhost:5006/account/auth")
32+
33+
# noinspection PyMethodMayBeStatic
34+
def build_msal_app(self, cache=None, authority=None):
35+
return msal.ConfidentialClientApplication(
36+
app_config.CLIENT_ID, authority=authority or app_config.AUTHORITY,
37+
client_credential=app_config.CLIENT_SECRET, token_cache=cache)
38+

app/ch15_deploy/final/pypi_org/views/account_views.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,71 @@
1+
import uuid
2+
13
import flask
4+
from flask import session
25

6+
import pypi_org.infrastructure.cookie_auth as cookie_auth
7+
from pypi_org.configs import app_config
8+
from pypi_org.infrastructure import session_cache
39
from pypi_org.infrastructure.view_modifiers import response
410
from pypi_org.services import user_service
5-
import pypi_org.infrastructure.cookie_auth as cookie_auth
611
from pypi_org.viewmodels.account.index_viewmodel import IndexViewModel
712
from pypi_org.viewmodels.account.login_viewmodel import LoginViewModel
813
from pypi_org.viewmodels.account.register_viewmodel import RegisterViewModel
14+
from pypi_org.viewmodels.shared.viewmodelbase import ViewModelBase
915

1016
blueprint = flask.Blueprint('account', __name__, template_folder='templates')
1117

1218

19+
# ################### AZURE AUTH ############################
20+
21+
22+
@blueprint.route('/account/auth')
23+
def auth():
24+
vm = ViewModelBase()
25+
args = flask.request.args
26+
27+
if flask.request.args.get('state') != session.get("state"):
28+
return flask.redirect('/') # No-OP. Goes back to Index page
29+
if "error" in flask.request.args: # Authentication/Authorization failure
30+
31+
return f"There was an error logging in: Error: {args.get('error')}, details: {args.get('error_description')}."
32+
if flask.request.args.get('code'):
33+
cache = session_cache.load_cache()
34+
result = vm.build_msal_app(cache=cache).acquire_token_by_authorization_code(
35+
flask.request.args['code'],
36+
scopes=app_config.SCOPE, # Misspelled scope would cause an HTTP 400 error here
37+
redirect_uri='http://localhost:5006/account/auth')
38+
if "error" in result:
39+
return f"There was an error logging in: Error: {args.get('error')}, details: {args.get('error_description')}."
40+
41+
session_cache.save_cache(cache)
42+
# 'oid': '257af28c-d791-4287-bf95-b67578dae57e',
43+
claims = result['id_token_claims']
44+
45+
email = claims.get('emails', ['NONE'])[0].strip().lower()
46+
first_name = claims.get('given_name')
47+
last_name = claims.get('family_name')
48+
49+
user = user_service.find_user_by_email(email)
50+
if not user:
51+
user = user_service.create_user(f'{first_name} {last_name}', email, str(uuid.uuid4()))
52+
53+
resp = flask.redirect('/account')
54+
cookie_auth.set_auth(resp, user.id)
55+
return resp
56+
57+
return flask.redirect('/')
58+
59+
60+
@blueprint.route('/account/begin_auth')
61+
def begin_auth():
62+
vm = ViewModelBase()
63+
state = str(uuid.uuid4())
64+
session["state"] = state
65+
66+
return flask.redirect(vm.build_auth_url(state=state))
67+
68+
1369
# ################### INDEX #################################
1470

1571

@@ -85,7 +141,9 @@ def login_post():
85141

86142
@blueprint.route('/account/logout')
87143
def logout():
88-
resp = flask.redirect('/')
144+
resp = flask.redirect( # Also logout from your tenant's web session
145+
app_config.AUTHORITY + "/oauth2/v2.0/logout" +
146+
"?post_logout_redirect_uri=http://localhost:5006/")
89147
cookie_auth.logout(resp)
90-
148+
session.clear() # Wipe out user and its token cache from session
91149
return resp

app/ch15_deploy/final/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
flask
2+
flask-session
23
werkzeug
34
sqlalchemy
5+
msal
46

57
progressbar2
68
python-dateutil

0 commit comments

Comments
 (0)