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 @@
-

+
@@ -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 @@
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+ {% if events|length > 0 %}
+
+
+
+
+
+ Event |
+ Time |
+
+
+
+ {% for event in events %}
+
+ {{ event['event'] }} |
+ {{ event['created_at'] }} |
+
+ {% endfor %}
+
+
+
+ {% if after %}
+
+ {% endif %}
+
+
+
+
+ {% for event in events %}
+
+ {{event|tojson_pretty}}
+
+ {% 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 @@
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
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 @@
-
+
-

+
@@ -21,37 +21,40 @@
-
+
-
+
Groups Details
+ class='button button-sm button-outline'>Back
{% if groups['data']|length > 0 %}
-
+
- Name |
- ID |
+ Name |
+ ID |
+ View Group |
- {% for group in groups['data'] %}
-
+ {% for group in groups['data'] %}
+
{{group['name']}} |
- {{group['id']}} |
-
- {% endfor %}
+ {{group['id']}} |
+ {{
+ lucide.icon('settings-2', stroke_width=1) }} |
+
+ {% endfor %}
{% 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 @@
-
+
-

+
@@ -27,39 +27,41 @@
Select a Directory
-
-
+
{% 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 @@
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
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 @@
-
+
-

+
-
+
-
+
-
User Details
+ Directory Users
+ class='button button-sm button-outline'>Back
{% if users['data']|length > 0 %}
-
+
- Name |
- Email |
+ Name |
+ Email |
+ View User |
- {% for user in users['data'] %}
-
+ {% for user in users['data'] %}
+
{{user['first_name']}} {{user['last_name']}} |
- {{user['username']}} |
-
- {% endfor %}
+ {{user['username']}} |
+ {{
+ lucide.icon('settings-2', stroke_width=1) }} |
+
+ {% endfor %}
{% 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 @@
-

+
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