diff --git a/python-flask-admin-portal-example/app.py b/python-flask-admin-portal-example/app.py index 55bf472..476cbed 100644 --- a/python-flask-admin-portal-example/app.py +++ b/python-flask-admin-portal-example/app.py @@ -1,21 +1,22 @@ +from email.mime import base import os - -from flask import Flask, redirect, render_template, request, url_for +from flask import Flask, redirect, render_template, request import workos -from workos import client as workos_client -from workos import portal from flask_lucide import Lucide +from workos.types import DomainDataInput # Flask Setup -DEBUG = False app = Flask(__name__) lucide = Lucide(app) # WorkOS Setup -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.project_id = os.getenv("WORKOS_CLIENT_ID") -workos.base_api_url = "http://localhost:7000/" if DEBUG else workos.base_api_url +base_api_url = os.getenv("WORKOS_BASE_API_URL") +workos_client = workos.WorkOSClient( + api_key=os.getenv("WORKOS_API_KEY"), + client_id=os.getenv("WORKOS_CLIENT_ID"), + base_url=base_api_url, +) @app.route("/") @@ -32,15 +33,23 @@ def provision_enterprise(): # Check if a matching domain already exists and set global org_id if there is a match orgs = workos_client.organizations.list_organizations(domains=organization_domains) - if len(orgs["data"]) > 0: - org_id = orgs["data"][0]["id"] + if len(orgs.data) > 0: + org_id = orgs.data[0].id # Otherwise create a new Organization and set the global org_id else: + domain_data = list( + map( + lambda domain: DomainDataInput(domain=domain, state="verified"), + organization_domains, + ) + ) + organization = workos_client.organizations.create_organization( - {"name": organization_name, "domains": organization_domains} + name=organization_name, + domain_data=domain_data, ) - org_id = organization["id"] + org_id = organization.id return render_template("org_logged_in.html") @@ -48,5 +57,14 @@ def provision_enterprise(): @app.route("/launch_admin_portal", methods=["GET", "POST"]) def launch_admin_portal(): intent = request.args.get("intent") - portal_link = workos_client.portal.generate_link(organization=org_id, intent=intent) - return redirect(portal_link["link"]) + + if intent is None: + return "Missing intent parameter", 400 + + if not intent in tuple(("audit_logs", "dsync", "log_streams", "sso")): + return "Invalid intent parameter", 400 + + portal_link = workos_client.portal.generate_link( + organization_id=org_id, intent=intent + ) + return redirect(portal_link.link) diff --git a/python-flask-admin-portal-example/requirements.txt b/python-flask-admin-portal-example/requirements.txt index 3dc5a0f..4015efe 100644 --- a/python-flask-admin-portal-example/requirements.txt +++ b/python-flask-admin-portal-example/requirements.txt @@ -1,7 +1,7 @@ certifi==2021.5.30 charset-normalizer==2.0.6 click==8.0.1 -Flask==2.0.1 +Flask==2.0.3 idna==3.2 itsdangerous==2.0.1 Jinja2==3.0.1 @@ -9,6 +9,6 @@ MarkupSafe==2.0.1 requests==2.26.0 urllib3==1.26.7 Werkzeug==2.0.1 -workos>=1.23.3 +workos>=5.0.0 python-dotenv flask-lucide==0.2.0 \ No newline at end of file diff --git a/python-flask-audit-logs-example/app.py b/python-flask-audit-logs-example/app.py index 35e107e..05e3d00 100644 --- a/python-flask-audit-logs-example/app.py +++ b/python-flask-audit-logs-example/app.py @@ -1,17 +1,16 @@ import json import os -from urllib.parse import urlparse, parse_qs -from flask import Flask, session, redirect, render_template, request, url_for +from flask import Flask, session, redirect, render_template, request import workos from datetime import datetime, timedelta from audit_log_events import ( user_organization_set, ) +from workos.audit_logs import AuditLogEvent from flask_lucide import Lucide # Flask Setup -DEBUG = False app = Flask(__name__) app.secret_key = os.getenv("APP_SECRET_KEY") @@ -19,9 +18,12 @@ # WorkOS Setup -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.project_id = os.getenv("WORKOS_CLIENT_ID") -workos.base_api_url = "http://localhost:7000/" if DEBUG else workos.base_api_url +base_api_url = os.getenv("WORKOS_BASE_API_URL") +workos_client = workos.WorkOSClient( + api_key=os.getenv("WORKOS_API_KEY"), + client_id=os.getenv("WORKOS_CLIENT_ID"), + base_url=base_api_url, +) def to_pretty_json(value): @@ -34,14 +36,14 @@ def to_pretty_json(value): @app.route("/", methods=["POST", "GET"]) def index(): try: - link = workos.client.portal.generate_link( - organization=session["organization_id"], intent="audit_logs" + link = workos_client.portal.generate_link( + organization_id=session["organization_id"], intent="audit_logs" ) today = datetime.today() last_month = today - timedelta(days=30) return render_template( "send_events.html", - link=link["link"], + link=link.link, organization_id=session["organization_id"], org_name=session["organization_name"], last_month_iso=last_month.isoformat(), @@ -50,14 +52,14 @@ def index(): except KeyError: before = request.args.get("before") after = request.args.get("after") - organizations = workos.client.organizations.list_organizations( - before=before, after=after, limit=5, order=None + organizations = workos_client.organizations.list_organizations( + before=before, after=after, limit=5, order="desc" ) - before = organizations["listMetadata"]["before"] - after = organizations["listMetadata"]["after"] + before = organizations.list_metadata.before + after = organizations.list_metadata.after return render_template( "login.html", - organizations=organizations["data"], + organizations=organizations.data, before=before, after=after, ) @@ -65,14 +67,14 @@ def index(): @app.route("/set_org", methods=["POST", "GET"]) def set_org(): - organization_id = request.args.get("id") + organization_id = request.args.get("id") or request.form["organization_id"] + session["organization_id"] = organization_id - organization_set = workos.client.audit_logs.create_event( - organization_id, user_organization_set + workos_client.audit_logs.create_event( + organization_id=organization_id, event=user_organization_set ) - org = workos.client.organizations.get_organization(organization_id) - org_name = org["name"] - session["organization_name"] = org_name + org = workos_client.organizations.get_organization(organization_id) + session["organization_name"] = org.name return redirect("/") @@ -87,28 +89,32 @@ def send_event(): ) organization_id = session["organization_id"] - event = { - "action": "user.organization_deleted", - "version": int(event_version), - "occurred_at": datetime.now().isoformat(), - "actor": { - "type": actor_type, - "name": actor_name, - "id": "user_01GBNJC3MX9ZZJW1FSTF4C5938", - }, - "targets": [ - { - "type": target_type, - "name": target_name, - "id": "team_01GBNJD4MKHVKJGEWK42JNMBGS", + event = AuditLogEvent( + { + "action": "user.organization_deleted", + "version": int(event_version), + "occurred_at": datetime.now().isoformat(), + "actor": { + "type": actor_type, + "name": actor_name, + "id": "user_01GBNJC3MX9ZZJW1FSTF4C5938", }, - ], - "context": { - "location": "123.123.123.123", - "user_agent": "Chrome/104.0.0.0", - }, - } - organization_set = workos.client.audit_logs.create_event(organization_id, event) + "targets": [ + { + "type": target_type, + "name": target_name, + "id": "team_01GBNJD4MKHVKJGEWK42JNMBGS", + }, + ], + "context": { + "location": "123.123.123.123", + "user_agent": "Chrome/104.0.0.0", + }, + } + ) + organization_set = workos_client.audit_logs.create_event( + organization_id=organization_id, event=event + ) return redirect("/") @@ -147,15 +153,15 @@ def get_events(): try: - create_export_response = workos.client.audit_logs.create_export( - organization=organization_id, + create_export_response = workos_client.audit_logs.create_export( + organization_id=organization_id, range_start=request.form["range-start"], range_end=request.form["range-end"], actions=actions, - actors=actors, + actor_names=actors, targets=targets, ) - session["export_id"] = create_export_response.to_dict()["id"] + session["export_id"] = create_export_response.id return redirect("export_events") except Exception as e: @@ -163,17 +169,24 @@ def get_events(): return redirect("/") if event_type == "access_csv": export_id = session["export_id"] - fetch_export_response = workos.client.audit_logs.get_export(export_id) - return redirect(fetch_export_response.to_dict()["url"]) + fetch_export_response = workos_client.audit_logs.get_export(export_id) + if fetch_export_response.url is None: + return redirect("/") + + return redirect(fetch_export_response.url) @app.route("/events", methods=["GET"]) def events(): - link = workos.client.portal.generate_link( - organization=session["organization_id"], intent=request.args.get("intent") + intent = request.args.get("intent") + if not intent == "audit_logs": + return redirect("/") + + link = workos_client.portal.generate_link( + organization_id=session["organization_id"], intent=intent ) - return redirect(link["link"]) + return redirect(link.link) @app.route("/logout") diff --git a/python-flask-audit-logs-example/audit_log_events.py b/python-flask-audit-logs-example/audit_log_events.py index c01224e..bf5b542 100644 --- a/python-flask-audit-logs-example/audit_log_events.py +++ b/python-flask-audit-logs-example/audit_log_events.py @@ -1,20 +1,23 @@ from datetime import datetime +from workos.audit_logs import AuditLogEvent -user_organization_set = { - "action": "user.organization_set", - "occurred_at": datetime.now().isoformat(), - "actor": { - "type": "user", - "id": "user_01GBNJC3MX9ZZJW1FSTF4C5938", - }, - "targets": [ - { - "type": "team", - "id": "team_01GBNJD4MKHVKJGEWK42JNMBGS", +user_organization_set = AuditLogEvent( + { + "action": "user.organization_set", + "occurred_at": datetime.now().isoformat(), + "actor": { + "type": "user", + "id": "user_01GBNJC3MX9ZZJW1FSTF4C5938", }, - ], - "context": { - "location": "123.123.123.123", - "user_agent": "Chrome/104.0.0.0", - }, -} + "targets": [ + { + "type": "organization", + "id": "team_01GBNJD4MKHVKJGEWK42JNMBGS", + }, + ], + "context": { + "location": "123.123.123.123", + "user_agent": "Chrome/104.0.0.0", + }, + } +) diff --git a/python-flask-audit-logs-example/requirements.txt b/python-flask-audit-logs-example/requirements.txt index 6f25511..d5dc8b3 100644 --- a/python-flask-audit-logs-example/requirements.txt +++ b/python-flask-audit-logs-example/requirements.txt @@ -1,5 +1,6 @@ Flask==2.0.3 Jinja2==3.1.1 -workos>=1.23.3 +workos>=5.0.0 python-dotenv -flask-lucide==0.2.0 \ No newline at end of file +flask-lucide==0.2.0 +Werkzeug==2.0.1 diff --git a/python-flask-directory-sync-example/app.py b/python-flask-directory-sync-example/app.py index 4ed86ae..86d8868 100644 --- a/python-flask-directory-sync-example/app.py +++ b/python-flask-directory-sync-example/app.py @@ -1,13 +1,11 @@ import os from flask import Flask, render_template, request import workos -from workos import client as workos_client -from flask_socketio import SocketIO, emit +from flask_socketio import SocketIO import json from flask_lucide import Lucide -DEBUG = False app = Flask(__name__) lucide = Lucide(app) @@ -16,11 +14,10 @@ socketio = SocketIO(app) if __name__ == "__main__": - socketio.run(app) + socketio.run(app) # type: ignore -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.base_api_url = "http://localhost:5000/" if DEBUG else workos.base_api_url -workos.client_id = os.getenv("WORKOS_CLIENT_ID") +base_api_url = os.getenv("WORKOS_BASE_API_URL") +workos_client = workos.WorkOSClient(api_key=os.getenv("WORKOS_API_KEY"), client_id=os.getenv("WORKOS_CLIENT_ID"), base_url=base_api_url) directory_id = os.getenv("DIRECTORY_ID") @@ -35,49 +32,104 @@ def to_pretty_json(value): def home(): before = request.args.get("before") after = request.args.get("after") - directories = workos.client.directory_sync.list_directories( - before=before, after=after, limit=5, order=None + directories = workos_client.directory_sync.list_directories( + before=before, after=after, limit=5 ) - before = directories["list_metadata"]["before"] - after = directories["list_metadata"]["after"] + + before = directories.list_metadata.before + after = directories.list_metadata.after return render_template( - "home.html", directories=directories["data"], before=before, after=after + "home.html", directories=directories.data, before=before, after=after ) @app.route("/directory") def directory(): directory_id = request.args.get("id") - directory = workos.client.directory_sync.get_directory(directory_id) - return render_template("directory.html", directory=directory, id=directory["id"]) + if not directory_id: + return "No directory ID provided", 400 + directory = workos_client.directory_sync.get_directory(directory_id) + + return render_template( + "directory.html", directory=directory.model_dump(), id=directory.id + ) @app.route("/users") def directory_users(): directory_id = request.args.get("id") - users = workos.client.directory_sync.list_users(directory=directory_id, limit=100) + users = workos_client.directory_sync.list_users(directory_id=directory_id, limit=100) return render_template("users.html", users=users) +@app.route("/user") +def directory_user(): + user_id = request.args.get("id") + if not user_id: + return "No user ID provided", 400 + user = workos_client.directory_sync.get_user(user_id) + + return render_template("user.html", user=user.model_dump(), id=user_id) + + @app.route("/groups") def directory_groups(): directory_id = request.args.get("id") - groups = workos_client.directory_sync.list_groups(directory=directory_id, limit=100) + groups = workos_client.directory_sync.list_groups(directory_id=directory_id, limit=100) return render_template("groups.html", groups=groups) +@app.route("/group") +def directory_group(): + group_id = request.args.get("id") + if not group_id: + return "No user ID provided", 400 + + group = workos_client.directory_sync.get_group(group_id) + + return render_template("group.html", group=group.model_dump(), id=group_id) + + +@app.route("/events") +def events(): + after = request.args.get("after") + events = workos_client.events.list_events( + events=[ + "dsync.activated", + "dsync.deleted", + "dsync.group.created", + "dsync.group.deleted", + "dsync.group.updated", + "dsync.user.created", + "dsync.user.deleted", + "dsync.user.updated", + "dsync.group.user_added", + "dsync.group.user_removed", + ], + after=after, + limit=20, + ) + + after = events.list_metadata.after + events_data = list(map(lambda event: event.model_dump(), events.data)) + return render_template("events.html", events=events_data, after=after) + + @app.route("/webhooks", methods=["GET", "POST"]) def webhooks(): + signing_secret = os.getenv("WEBHOOKS_SECRET") if request.data: - payload = request.get_data() - sig_header = request.headers["WorkOS-Signature"] - response = workos_client.webhooks.verify_event( - payload=payload, sig_header=sig_header, secret=os.getenv("WEBHOOKS_SECRET") - ) - - message = json.dumps(response) - socketio.emit("webhook_received", message) + if signing_secret: + payload = request.get_data() + sig_header = request.headers["WorkOS-Signature"] + response = workos_client.webhooks.verify_event( + event_body=payload, event_signature=sig_header, secret=signing_secret + ) + message = json.dumps(response.dict()) + socketio.emit("webhook_received", message) + else: + print("No signing secret configured") # Return a 200 to prevent retries based on validation return render_template("webhooks.html") diff --git a/python-flask-directory-sync-example/requirements.txt b/python-flask-directory-sync-example/requirements.txt index 9fd871d..1463a94 100644 --- a/python-flask-directory-sync-example/requirements.txt +++ b/python-flask-directory-sync-example/requirements.txt @@ -1,6 +1,7 @@ -Flask>=1.1.2 -workos>=1.23.3 -urllib3==1.26.7 +Flask==2.0.3 +workos>=5.0.0 +urllib3>=2 python-dotenv flask_socketio -flask-lucide==0.2.0 \ No newline at end of file +flask-lucide==0.2.0 +Werkzeug==2.0.1 diff --git a/python-flask-directory-sync-example/static/home.css b/python-flask-directory-sync-example/static/home.css index 82298a4..dc22ff8 100644 --- a/python-flask-directory-sync-example/static/home.css +++ b/python-flask-directory-sync-example/static/home.css @@ -133,6 +133,10 @@ h1 { bottom: 20%; } +.event_bodies { + position: inherit !important; +} + .logged_in_div_left h1 { color: #111111; font-size: 75px; @@ -143,14 +147,12 @@ h1 { } .home-hero-gradient { - background-image: linear-gradient( - 45deg, - #a163f1, - #6363f1 22%, - #3498ea 40%, - #40dfa3 67%, - rgba(64, 223, 163, 0) - ); + background-image: linear-gradient(45deg, + #a163f1, + #6363f1 22%, + #3498ea 40%, + #40dfa3 67%, + rgba(64, 223, 163, 0)); background-size: 150% 100%; background-repeat: no-repeat; -webkit-background-clip: text; @@ -192,14 +194,11 @@ div.text_box { overflow: scroll; border-width: 3px; border-style: solid; - border-image: linear-gradient( - #a163f1, + border-image: linear-gradient(#a163f1, #6363f1 22%, #3498ea 40%, #40dfa3 67%, - rgba(64, 223, 163, 0) - ) - 0 100%; + rgba(64, 223, 163, 0)) 0 100%; } .logged_in_nav { @@ -245,6 +244,10 @@ pre.prettyprint { border: none !important; } +pre.overflow_scroll { + overflow: scroll; +} + .text_input { border: 1px solid #555555; border-radius: 10px; @@ -354,6 +357,15 @@ th { padding: 15px 40px; } +tr.event_summary { + text-align: left; +} + +tr.selected_event { + font-weight: bold; + background-color: #ebebf2; +} + .width-75 { width: 75%; } @@ -418,6 +430,6 @@ th { border: none; } -#noborder > :first-child { +.prettyprinted> :first-child { display: none; -} +} \ No newline at end of file diff --git a/python-flask-directory-sync-example/templates/directory.html b/python-flask-directory-sync-example/templates/directory.html index fff8525..67d3f22 100644 --- a/python-flask-directory-sync-example/templates/directory.html +++ b/python-flask-directory-sync-example/templates/directory.html @@ -8,7 +8,7 @@
- workos logo + workos logo
@@ -21,7 +21,7 @@
- +

Directory Details

diff --git a/python-flask-directory-sync-example/templates/events.html b/python-flask-directory-sync-example/templates/events.html new file mode 100644 index 0000000..081e20f --- /dev/null +++ b/python-flask-directory-sync-example/templates/events.html @@ -0,0 +1,98 @@ + + + + + + + + +
+
+
+ workos logo +
+
+
+ + + + +
+
+
+ {% if events|length > 0 %} +
+
+ + + + + + + + + {% for event in events %} + + + + + {% endfor %} + +
EventTime
{{ event['event'] }}{{ event['created_at'] }}
+
+ {% if after %} +
+ +
+ {% endif %} +
+
+
+
+ {% for event in events %} + + {% endfor %} +
+ {% else %} +
No more events
+ {% endif %} +
+ + + + + \ No newline at end of file diff --git a/python-flask-directory-sync-example/templates/group.html b/python-flask-directory-sync-example/templates/group.html new file mode 100644 index 0000000..31f2d9c --- /dev/null +++ b/python-flask-directory-sync-example/templates/group.html @@ -0,0 +1,39 @@ + + + + + + + +
+
+
+ workos logo +
+
+
+ + + + +
+
+
+ +
+
+

Group Details

+
+
+                        {{group|tojson_pretty}}
+                    
+
+
+
+
+ + + + \ No newline at end of file diff --git a/python-flask-directory-sync-example/templates/groups.html b/python-flask-directory-sync-example/templates/groups.html index b9adf91..8e2b590 100644 --- a/python-flask-directory-sync-example/templates/groups.html +++ b/python-flask-directory-sync-example/templates/groups.html @@ -4,11 +4,11 @@ - +
- workos logo + workos logo
@@ -21,37 +21,40 @@
- +
-
+

Groups Details

+ class='button button-sm button-outline'>Back
{% if groups['data']|length > 0 %}
- +
- - + + + - {% for group in groups['data'] %} - + {% for group in groups['data'] %} + - - - {% endfor %} + + + + {% endfor %}
NameIDNameIDView Group
{{group['name']}}{{group['id']}}
{{group['id']}}{{ + lucide.icon('settings-2', stroke_width=1) }}
{% else %}
No Groups Found
- {% endif %} + {% endif %}
diff --git a/python-flask-directory-sync-example/templates/home.html b/python-flask-directory-sync-example/templates/home.html index b789309..6fd171f 100644 --- a/python-flask-directory-sync-example/templates/home.html +++ b/python-flask-directory-sync-example/templates/home.html @@ -5,11 +5,11 @@ - +
- workos logo + workos logo
@@ -27,39 +27,41 @@

Select a Directory

- -
+ +
- - - + + + - {% for i in directories %} - + {% for i in directories %} + - - - - {% endfor %} -
OrganizationIDView SettingsOrganizationIDView Settings
{{ i['name'] }}{{ i['id'] }}{{ lucide.icon('settings-2', stroke_width=1) }}
-
+ {{ i['id'] }} + {{ + lucide.icon('settings-2', stroke_width=1) }} + + {% endfor %} + +
{% if after %} +
{% endif %} {% if before %}
-
- {% endif %} -
+
+ {% endif %} +
diff --git a/python-flask-directory-sync-example/templates/user.html b/python-flask-directory-sync-example/templates/user.html new file mode 100644 index 0000000..6a3be83 --- /dev/null +++ b/python-flask-directory-sync-example/templates/user.html @@ -0,0 +1,36 @@ + + + + + + + +
+
+
+ workos logo +
+
+
+ + + + +
+
+
+
+

User Details

+
+
+                        {{user|tojson_pretty}}
+                
+
+
+
+ + + + \ No newline at end of file diff --git a/python-flask-directory-sync-example/templates/users.html b/python-flask-directory-sync-example/templates/users.html index 80d2678..ebafb41 100644 --- a/python-flask-directory-sync-example/templates/users.html +++ b/python-flask-directory-sync-example/templates/users.html @@ -4,11 +4,11 @@ - +
- workos logo + workos logo
@@ -20,36 +20,39 @@
-
+
-
+
-

User Details

+

Directory Users

+ class='button button-sm button-outline'>Back
{% if users['data']|length > 0 %}
- +
- - + + + - {% for user in users['data'] %} - + {% for user in users['data'] %} + - - - {% endfor %} + + + + {% endfor %}
NameEmailNameEmailView User
{{user['first_name']}} {{user['last_name']}}{{user['username']}}
{{user['username']}}{{ + lucide.icon('settings-2', stroke_width=1) }}
{% else %}
No Users Found
- {% endif %} + {% endif %}
diff --git a/python-flask-directory-sync-example/templates/webhooks.html b/python-flask-directory-sync-example/templates/webhooks.html index 0151baa..2d0b8f2 100644 --- a/python-flask-directory-sync-example/templates/webhooks.html +++ b/python-flask-directory-sync-example/templates/webhooks.html @@ -8,7 +8,7 @@
- workos logo + workos logo
diff --git a/python-flask-magic-link-example/app.py b/python-flask-magic-link-example/app.py index 0c77435..268b16d 100644 --- a/python-flask-magic-link-example/app.py +++ b/python-flask-magic-link-example/app.py @@ -1,17 +1,18 @@ import os import json -from flask import Flask, redirect, render_template, request, url_for +from flask import Flask, render_template, request import workos -from workos import client as workos_client # Flask Setup -DEBUG = False app = Flask(__name__) # WorkOS Setup -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.project_id = os.getenv("WORKOS_CLIENT_ID") -workos.base_api_url = "http://localhost:5000/" if DEBUG else workos.base_api_url +base_api_url = os.getenv("WORKOS_BASE_API_URL") +workos_client = workos.WorkOSClient( + api_key=os.getenv("WORKOS_API_KEY"), + client_id=os.getenv("WORKOS_CLIENT_ID"), + base_url=base_api_url, +) redirect_uri = "http://localhost:5000/success" @@ -33,23 +34,23 @@ def passwordless_auth(): email = request.form["email"] session = workos_client.passwordless.create_session( - {"email": email, "type": "MagicLink", "redirect_uri": redirect_uri} + email=email, type="MagicLink", redirect_uri=redirect_uri ) # Send a custom email using your own service - print(email, session["link"]) + print(email, session.link) # Finally, redirect to a "Check your email" page return render_template( - "serve_magic_link.html", email=email, magic_link=session["link"] + "serve_magic_link.html", email=email, magic_link=session.link ) @app.route("/success") def success(): code = request.args.get("code") - profile = workos.client.sso.get_profile_and_token(code) - p_profile = profile.to_dict() - raw_profile = p_profile["profile"] + if not code: + return "No code provided" + profile = workos_client.sso.get_profile_and_token(code) - return render_template("success.html", raw_profile=raw_profile) + return render_template("success.html", raw_profile=profile.dict()) diff --git a/python-flask-magic-link-example/requirements.txt b/python-flask-magic-link-example/requirements.txt index 41c8843..1f413cb 100644 --- a/python-flask-magic-link-example/requirements.txt +++ b/python-flask-magic-link-example/requirements.txt @@ -1,4 +1,5 @@ -Flask==2.0.0 -workos>=1.23.3 +Flask==2.0.3 +workos>=5.0.0 urllib3==1.26.7 +Werkzeug==2.0.1 python-dotenv \ No newline at end of file diff --git a/python-flask-mfa-example/app.py b/python-flask-mfa-example/app.py index bb29221..86a7de0 100644 --- a/python-flask-mfa-example/app.py +++ b/python-flask-mfa-example/app.py @@ -1,20 +1,21 @@ import os +from typing import Any, assert_never, cast from flask import Flask, session, redirect, render_template, request, url_for, jsonify -import json import workos from flask_lucide import Lucide # Flask Setup -DEBUG = False app = Flask(__name__) app.secret_key = os.getenv("APP_SECRET_KEY") lucide = Lucide(app) # WorkOS Setup - -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.project_id = os.getenv("WORKOS_CLIENT_ID") -workos.base_api_url = "http://localhost:7000/" if DEBUG else workos.base_api_url +base_api_url = os.getenv("WORKOS_BASE_API_URL") +workos_client = workos.WorkOSClient( + api_key=os.getenv("WORKOS_API_KEY"), + client_id=os.getenv("WORKOS_CLIENT_ID"), + base_url=base_api_url, +) @app.route("/") @@ -41,30 +42,34 @@ def enroll_factor_details(): def enroll_sms_factor(): factor_type = request.form.get("type") phone_number = request.form.get("phone_number") + if not factor_type in ("sms", "totp"): + return "Invalid factor type" - new_factor = workos.client.mfa.enroll_factor( + new_factor = workos_client.mfa.enroll_factor( type=factor_type, phone_number=phone_number ) - session["factor_list"].append(new_factor) + session["factor_list"].append(new_factor.dict()) session.modified = True return redirect("/") @app.route("/enroll_totp_factor", methods=["POST"]) def enroll_totp_factor(): - data = request.get_json() + data = cast(Any, request.get_json()) type = data["type"] issuer = data["issuer"] user = data["user"] - new_factor = workos.client.mfa.enroll_factor( + new_factor = workos_client.mfa.enroll_factor( type=type, totp_issuer=issuer, totp_user=user ) - session["factor_list"].append(new_factor) + if new_factor.type == "totp": + session["current_factor_qr"] = new_factor.totp.qr_code + session["factor_list"].append(new_factor.dict()) session.modified = True - return jsonify(new_factor["totp"]["qr_code"]) + return jsonify(new_factor.dict()) @app.route("/factor_detail") @@ -90,22 +95,25 @@ def factor_detail(): @app.route("/challenge_factor", methods=["POST"]) def challenge_factor(): - if session["current_factor_type"] == "sms": + factor_type = session["current_factor_type"] + + if factor_type == "sms": message = request.form["sms_message"] session["sms_message"] = message - challenge = workos.client.mfa.challenge_factor( + challenge = workos_client.mfa.challenge_factor( authentication_factor_id=session["current_factor"], sms_template=message, ) - - if session["current_factor_type"] == "totp": + elif factor_type == "totp": authentication_factor_id = session["current_factor"] - challenge = workos.client.mfa.challenge_factor( + challenge = workos_client.mfa.challenge_factor( authentication_factor_id=authentication_factor_id, ) + else: + assert_never(factor_type) - session["challenge_id"] = challenge["id"] + session["challenge_id"] = challenge.id session.modified = True return render_template("challenge_factor.html") @@ -120,15 +128,15 @@ def buildCode(code_values): code = buildCode(request.form) challenge_id = session["challenge_id"] - verify_factor = workos.client.mfa.verify_factor( + verify_factor = workos_client.mfa.verify_challenge( authentication_challenge_id=challenge_id, code=code, ) return render_template( "challenge_success.html", - challenge=verify_factor["challenge"], - valid=verify_factor["valid"], + challenge=verify_factor.challenge, + valid=verify_factor.valid, type=session["current_factor_type"], ) diff --git a/python-flask-mfa-example/requirements.txt b/python-flask-mfa-example/requirements.txt index 92472f7..9d54cc4 100644 --- a/python-flask-mfa-example/requirements.txt +++ b/python-flask-mfa-example/requirements.txt @@ -1,5 +1,6 @@ -Flask==2.0.0 -workos>=1.23.3 +Flask==2.0.3 +workos>=5.0.0 urllib3==1.26.7 +Werkzeug==2.0.1 python-dotenv flask-lucide==0.2.0 \ No newline at end of file diff --git a/python-flask-sso-example/app.py b/python-flask-sso-example/app.py index 0481b83..2738611 100644 --- a/python-flask-sso-example/app.py +++ b/python-flask-sso-example/app.py @@ -1,24 +1,24 @@ import json import os -from urllib.parse import urlparse, parse_qs from flask import Flask, session, redirect, render_template, request, url_for import workos # Flask Setup -DEBUG = False app = Flask(__name__) app.secret_key = os.getenv("APP_SECRET_KEY") +base_api_url = os.getenv("WORKOS_BASE_API_URL") # WorkOS Setup - -workos.api_key = os.getenv("WORKOS_API_KEY") -workos.project_id = os.getenv("WORKOS_CLIENT_ID") -workos.base_api_url = "http://localhost:7000/" if DEBUG else workos.base_api_url +workos_client = workos.WorkOSClient( + api_key=os.getenv("WORKOS_API_KEY"), + client_id=os.getenv("WORKOS_CLIENT_ID"), + base_url=base_api_url, +) # Enter Organization ID here -CUSTOMER_ORGANIZATION_ID = "" +CUSTOMER_ORGANIZATION_ID = "" # Use org_test_idp for testing def to_pretty_json(value): @@ -44,15 +44,24 @@ def login(): def auth(): login_type = request.form.get("login_method") - - params = {"redirect_uri": url_for("auth_callback", _external=True), "state": {}} - - if login_type == "saml": - params["organization"] = CUSTOMER_ORGANIZATION_ID - else: - params["provider"] = login_type - - authorization_url = workos.client.sso.get_authorization_url(**params) + if login_type not in ( + "saml", + "GoogleOAuth", + "MicrosoftOAuth", + ): + return redirect("/") + + redirect_uri = url_for("auth_callback", _external=True) + + authorization_url = ( + workos_client.sso.get_authorization_url( + redirect_uri=redirect_uri, organization_id=CUSTOMER_ORGANIZATION_ID + ) + if login_type == "saml" + else workos_client.sso.get_authorization_url( + redirect_uri=redirect_uri, provider=login_type + ) + ) return redirect(authorization_url) @@ -61,11 +70,13 @@ def auth(): def auth_callback(): code = request.args.get("code") - profile = workos.client.sso.get_profile_and_token(code) - p_profile = profile.to_dict() - session["first_name"] = p_profile["profile"]["first_name"] - session["raw_profile"] = p_profile["profile"] - session["session_id"] = p_profile["profile"]["id"] + # Why do I always get an error that the target does not belong to the target organization? + if code is None: + return redirect("/") + profile = workos_client.sso.get_profile_and_token(code).profile + session["first_name"] = profile.first_name + session["raw_profile"] = profile.dict() + session["session_id"] = profile.id return redirect("/") diff --git a/python-flask-sso-example/requirements.txt b/python-flask-sso-example/requirements.txt index 41c8843..1f413cb 100644 --- a/python-flask-sso-example/requirements.txt +++ b/python-flask-sso-example/requirements.txt @@ -1,4 +1,5 @@ -Flask==2.0.0 -workos>=1.23.3 +Flask==2.0.3 +workos>=5.0.0 urllib3==1.26.7 +Werkzeug==2.0.1 python-dotenv \ No newline at end of file