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 @@
+ {{event|tojson_pretty}} ++ {% endfor %} +
No more events
+ {{group|tojson_pretty}} ++
Name | -ID | +Name | +ID | +View Group |
---|---|---|---|---|
{{group['name']}} | -{{group['id']}} |
- {{group['id']}} |
+ {{ + lucide.icon('settings-2', stroke_width=1) }} | + + {% endfor %}
No Groups Found
Organization | -ID | -View Settings | +Organization | +ID | +View Settings |
---|---|---|---|---|---|
{{ i['name'] }} | -{{ i['id'] }} | -{{ lucide.icon('settings-2', stroke_width=1) }} | -
+ {{user|tojson_pretty}} ++
Name | -Name | +View User | ||
---|---|---|---|---|
{{user['first_name']}} {{user['last_name']}} | -{{user['username']}} |
- {{user['username']}} |
+ {{ + lucide.icon('settings-2', stroke_width=1) }} | + + {% endfor %}
No Users Found
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: