Skip to content

Commit f9c8a4c

Browse files
authored
Async apis (#43)
* async apis * Added httpx as requirement and added to FlaskAuthAsync * FlaskAuthAsync no longer extends FlaskAuth
1 parent 5831724 commit f9c8a4c

File tree

3 files changed

+372
-3
lines changed

3 files changed

+372
-3
lines changed

propelauth_flask/__init__.py

Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import httpx
12
from typing import Any, Dict, List, Optional, cast
23
from flask import g
34
from propelauth_py import (
45
TokenVerificationMetadata,
56
init_base_auth,
7+
init_base_async_auth,
68
SamlIdpMetadata,
79
StepUpMfaGrantType,
810
StepUpMfaVerifyTotpResponse,
@@ -507,6 +509,362 @@ def verify_step_up_grant(self, action_type: str, user_id: str, grant: str) -> bo
507509
return self.auth.verify_step_up_grant(action_type, user_id, grant)
508510

509511

512+
class FlaskAuthAsync():
513+
def __init__(
514+
self,
515+
auth_url: str,
516+
integration_api_key: str,
517+
token_verification_metadata: Optional[TokenVerificationMetadata],
518+
debug_mode: bool,
519+
httpx_client: Optional[httpx.AsyncClient] = None,
520+
):
521+
self.auth_url = auth_url
522+
self.integration_api_key = integration_api_key
523+
self.token_verification_metadata = token_verification_metadata
524+
self.debug_mode = debug_mode
525+
self.httpx_client = httpx_client
526+
self.auth = init_base_async_auth(auth_url, integration_api_key, token_verification_metadata, self.httpx_client)
527+
528+
@property
529+
def require_user(self):
530+
return _get_user_credential_decorator(
531+
self.auth.validate_access_token_and_get_user, True, self.debug_mode
532+
)
533+
534+
@property
535+
def optional_user(self):
536+
return _get_user_credential_decorator(
537+
self.auth.validate_access_token_and_get_user, False, self.debug_mode
538+
)
539+
540+
@property
541+
def require_org_member(self):
542+
return _get_require_org_decorator(
543+
self.auth.validate_access_token_and_get_user_with_org, self.debug_mode
544+
)
545+
546+
@property
547+
def require_org_member_with_minimum_role(self):
548+
return _require_org_member_with_minimum_role_decorator(
549+
self.auth.validate_access_token_and_get_user_with_org_by_minimum_role,
550+
self.debug_mode,
551+
)
552+
553+
@property
554+
def require_org_member_with_exact_role(self):
555+
return _require_org_member_with_exact_role_decorator(
556+
self.auth.validate_access_token_and_get_user_with_org_by_exact_role,
557+
self.debug_mode,
558+
)
559+
560+
@property
561+
def require_org_member_with_permission(self):
562+
return _require_org_member_with_permission_decorator(
563+
self.auth.validate_access_token_and_get_user_with_org_by_permission,
564+
self.debug_mode,
565+
)
566+
567+
@property
568+
def require_org_member_with_all_permissions(self):
569+
return _require_org_member_with_all_permissions_decorator(
570+
self.auth.validate_access_token_and_get_user_with_org_by_all_permissions,
571+
self.debug_mode,
572+
)
573+
574+
def validate_access_token_and_get_user(self, authorization_header: str) -> User:
575+
return self.auth.validate_access_token_and_get_user(
576+
authorization_header=authorization_header
577+
)
578+
579+
async def fetch_user_metadata_by_user_id(self, user_id: str, include_orgs: bool = False):
580+
return await self.auth.fetch_user_metadata_by_user_id(user_id, include_orgs)
581+
582+
async def fetch_user_metadata_by_email(self, email: str, include_orgs: bool = False):
583+
return await self.auth.fetch_user_metadata_by_email(email, include_orgs)
584+
585+
async def fetch_user_metadata_by_username(self, username: str, include_orgs: bool = False):
586+
return await self.auth.fetch_user_metadata_by_username(username, include_orgs)
587+
588+
async def fetch_user_signup_query_params_by_user_id(self, user_id: str):
589+
return await self.auth.fetch_user_signup_query_params_by_user_id(user_id)
590+
591+
async def fetch_batch_user_metadata_by_user_ids(self, user_ids: List[str], include_orgs: bool = False):
592+
return await self.auth.fetch_batch_user_metadata_by_user_ids(user_ids, include_orgs)
593+
594+
async def fetch_batch_user_metadata_by_emails(self, emails: List[str], include_orgs: bool = False):
595+
return await self.auth.fetch_batch_user_metadata_by_emails(emails, include_orgs)
596+
597+
async def fetch_batch_user_metadata_by_usernames(self, usernames: List[str], include_orgs: bool = False):
598+
return await self.auth.fetch_batch_user_metadata_by_usernames(usernames, include_orgs)
599+
600+
async def fetch_org(self, org_id: str):
601+
return await self.auth.fetch_org(org_id)
602+
603+
async def fetch_org_by_query(
604+
self, page_size: int = 10, page_number: int = 0, order_by: OrgQueryOrderBy = OrgQueryOrderBy.CREATED_AT_ASC,
605+
name: Optional[str] = None, legacy_org_id: Optional[str] = None, domain: Optional[str] = None
606+
):
607+
return await self.auth.fetch_org_by_query(page_size, page_number, order_by, name, legacy_org_id, domain)
608+
609+
async def fetch_custom_role_mappings(self):
610+
return await self.auth.fetch_custom_role_mappings()
611+
612+
async def fetch_pending_invites(self, page_number: int = 0, page_size: int = 10, org_id: Optional[str] = None):
613+
return await self.auth.fetch_pending_invites(page_number, page_size, org_id)
614+
615+
async def fetch_users_by_query(
616+
self, page_size: int = 10, page_number: int = 0, order_by: UserQueryOrderBy = UserQueryOrderBy.CREATED_AT_ASC,
617+
email_or_username: Optional[str] = None, include_orgs: bool = False, legacy_user_id: Optional[str] = None
618+
):
619+
return await self.auth.fetch_users_by_query(page_size, page_number, order_by, email_or_username, include_orgs, legacy_user_id)
620+
621+
async def fetch_users_in_org(
622+
self, org_id: str, page_size: int = 10, page_number: int = 0, include_orgs: bool = False, role: Optional[str] = None
623+
):
624+
return await self.auth.fetch_users_in_org(org_id, page_size, page_number, include_orgs, role)
625+
626+
async def create_user(
627+
self, email: str, email_confirmed: bool = False, send_email_to_confirm_email_address: bool = True,
628+
ask_user_to_update_password_on_login: bool = False, password: Optional[str] = None, username: Optional[str] = None,
629+
first_name: Optional[str] = None, last_name: Optional[str] = None, properties: Optional[Dict[str, Any]] = None, ignore_domain_restrictions: bool = False
630+
):
631+
return await self.auth.create_user(
632+
email, email_confirmed, send_email_to_confirm_email_address, ask_user_to_update_password_on_login,
633+
password, username, first_name, last_name, properties, ignore_domain_restrictions
634+
)
635+
636+
async def invite_user_to_org(self, email: str, org_id: str, role: str, additional_roles: List[str] = []):
637+
return await self.auth.invite_user_to_org(email, org_id, role, additional_roles)
638+
639+
async def resend_email_confirmation(self, user_id: str):
640+
return await self.auth.resend_email_confirmation(user_id)
641+
642+
async def logout_all_user_sessions(self, user_id: str):
643+
return await self.auth.logout_all_user_sessions(user_id)
644+
645+
async def update_user_email(self, user_id: str, new_email: str, require_email_confirmation: bool):
646+
return await self.auth.update_user_email(user_id, new_email, require_email_confirmation)
647+
648+
async def update_user_metadata(
649+
self,
650+
user_id: str,
651+
username: Optional[str] = None,
652+
first_name: Optional[str] = None,
653+
last_name: Optional[str] = None,
654+
metadata: Optional[Dict[str, Any]] = None,
655+
properties: Optional[Dict[str, Any]] = None,
656+
picture_url: Optional[str] = None,
657+
update_password_required: Optional[bool] = None,
658+
legacy_user_id: Optional[str] = None,
659+
):
660+
return await self.auth.update_user_metadata(
661+
user_id, username, first_name, last_name, metadata, properties, picture_url, update_password_required, legacy_user_id
662+
)
663+
664+
async def clear_user_password(self, user_id: str):
665+
return await self.auth.clear_user_password(user_id)
666+
667+
async def update_user_password(self, user_id: str, password: str, ask_user_to_update_password_on_login: bool = False):
668+
return await self.auth.update_user_password(user_id, password, ask_user_to_update_password_on_login)
669+
670+
async def create_magic_link(
671+
self,
672+
email: str,
673+
redirect_to_url: Optional[str] = None,
674+
expires_in_hours: Optional[str] = None,
675+
create_new_user_if_one_doesnt_exist: Optional[bool] = None,
676+
user_signup_query_parameters: Optional[Dict[str, Any]] = None,
677+
):
678+
return await self.auth.create_magic_link(
679+
email, redirect_to_url, expires_in_hours, create_new_user_if_one_doesnt_exist, user_signup_query_parameters
680+
)
681+
682+
async def create_access_token(self, user_id: str, duration_in_minutes: int, active_org_id: Optional[str] = None):
683+
return await self.auth.create_access_token(user_id, duration_in_minutes, active_org_id)
684+
685+
async def migrate_user_from_external_source(
686+
self,
687+
email: str,
688+
email_confirmed: bool,
689+
existing_user_id: Optional[str] = None,
690+
existing_password_hash: Optional[str] = None,
691+
existing_mfa_base32_encoded_secret: Optional[str] = None,
692+
ask_user_to_update_password_on_login: bool = False,
693+
enabled: Optional[bool] = None,
694+
first_name: Optional[str] = None,
695+
last_name: Optional[str] = None,
696+
username: Optional[str] = None,
697+
picture_url: Optional[str] = None,
698+
properties: Optional[Dict[str, Any]] = None,
699+
):
700+
return await self.auth.migrate_user_from_external_source(
701+
email, email_confirmed, existing_user_id, existing_password_hash,
702+
existing_mfa_base32_encoded_secret, ask_user_to_update_password_on_login,
703+
enabled, first_name, last_name, username, picture_url, properties
704+
)
705+
706+
async def migrate_user_password(
707+
self,
708+
user_id: str,
709+
password_hash: str,
710+
):
711+
return await self.auth.migrate_user_password(user_id, password_hash)
712+
713+
async def create_org(
714+
self,
715+
name: str,
716+
enable_auto_joining_by_domain: bool = False,
717+
members_must_have_matching_domain: bool = False,
718+
domain: Optional[str] = None,
719+
max_users: Optional[str] = None,
720+
custom_role_mapping_name: Optional[str] = None,
721+
legacy_org_id: Optional[str] = None,
722+
):
723+
return await self.auth.create_org(
724+
name, enable_auto_joining_by_domain, members_must_have_matching_domain,
725+
domain, max_users, custom_role_mapping_name, legacy_org_id
726+
)
727+
728+
async def update_org_metadata(
729+
self,
730+
org_id: str,
731+
name: Optional[str] = None,
732+
can_setup_saml: Optional[bool] = None,
733+
metadata: Optional[Dict[str, Any]] = None,
734+
max_users: Optional[str] = None,
735+
can_join_on_email_domain_match: Optional[bool] = None,
736+
members_must_have_email_domain_match: Optional[bool] = None,
737+
domain: Optional[str] = None,
738+
require_2fa_by: Optional[str] = None,
739+
extra_domains: Optional[List[str]] = None,
740+
):
741+
return await self.auth.update_org_metadata(
742+
org_id, name, can_setup_saml, metadata, max_users,
743+
can_join_on_email_domain_match, members_must_have_email_domain_match, domain, require_2fa_by, extra_domains
744+
)
745+
746+
async def subscribe_org_to_role_mapping(self, org_id: str, custom_role_mapping_name: str):
747+
return await self.auth.subscribe_org_to_role_mapping(org_id, custom_role_mapping_name)
748+
749+
async def delete_org(self, org_id: str):
750+
return await self.auth.delete_org(org_id)
751+
752+
async def revoke_pending_org_invite(self, org_id: str, invitee_email: str):
753+
return await self.auth.revoke_pending_org_invite(org_id, invitee_email)
754+
755+
async def add_user_to_org(self, user_id: str, org_id: str, role: str, additional_roles: List[str] = []):
756+
return await self.auth.add_user_to_org(user_id, org_id, role, additional_roles)
757+
758+
async def remove_user_from_org(self, user_id: str, org_id: str):
759+
return await self.auth.remove_user_from_org(user_id, org_id)
760+
761+
async def change_user_role_in_org(self, user_id: str, org_id: str, role: str, additional_roles: List[str] = []):
762+
return await self.auth.change_user_role_in_org(user_id, org_id, role, additional_roles)
763+
764+
async def delete_user(self, user_id: str):
765+
return await self.auth.delete_user(user_id)
766+
767+
async def disable_user(self, user_id: str):
768+
return await self.auth.disable_user(user_id)
769+
770+
async def enable_user(self, user_id: str):
771+
return await self.auth.enable_user(user_id)
772+
773+
async def disable_user_2fa(self, user_id: str):
774+
return await self.auth.disable_user_2fa(user_id)
775+
776+
async def enable_user_can_create_orgs(self, user_id: str):
777+
return await self.auth.enable_user_can_create_orgs(user_id)
778+
779+
async def disable_user_can_create_orgs(self, user_id: str):
780+
return await self.auth.disable_user_can_create_orgs(user_id)
781+
782+
async def allow_org_to_setup_saml_connection(self, org_id: str):
783+
return await self.auth.allow_org_to_setup_saml_connection(org_id)
784+
785+
async def disallow_org_to_setup_saml_connection(self, org_id: str):
786+
return await self.auth.disallow_org_to_setup_saml_connection(org_id)
787+
788+
async def fetch_api_key(self, api_key_id: str):
789+
return await self.auth.fetch_api_key(api_key_id)
790+
791+
async def fetch_current_api_keys(
792+
self,
793+
org_id: Optional[str] = None,
794+
user_id: Optional[str] = None,
795+
user_email: Optional[str] = None,
796+
page_size: Optional[int] = None,
797+
page_number: Optional[int] = None,
798+
api_key_type: Optional[str] = None,
799+
):
800+
return await self.auth.fetch_current_api_keys(
801+
org_id, user_id, user_email, page_size, page_number, api_key_type
802+
)
803+
804+
async def fetch_archived_api_keys(
805+
self,
806+
org_id: Optional[str] = None,
807+
user_id: Optional[str] = None,
808+
user_email: Optional[str] = None,
809+
page_size: Optional[int] = None,
810+
page_number: Optional[int] = None,
811+
api_key_type: Optional[str] = None,
812+
):
813+
return await self.auth.fetch_archived_api_keys(
814+
org_id, user_id, user_email, page_size, page_number, api_key_type
815+
)
816+
817+
async def create_api_key(
818+
self,
819+
org_id: Optional[str] = None,
820+
user_id: Optional[str] = None,
821+
expires_at_seconds: Optional[str] = None,
822+
metadata: Optional[Dict[str, Any]] = None
823+
):
824+
return await self.auth.create_api_key(org_id, user_id, expires_at_seconds, metadata)
825+
826+
async def update_api_key(self, api_key_id: str, expires_at_seconds: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None):
827+
return await self.auth.update_api_key(api_key_id, expires_at_seconds, metadata)
828+
829+
async def delete_api_key(self, api_key_id: str):
830+
return await self.auth.delete_api_key(api_key_id)
831+
832+
async def validate_personal_api_key(self, api_key_token: str):
833+
return await self.auth.validate_personal_api_key(api_key_token)
834+
835+
async def validate_org_api_key(self, api_key_token: str):
836+
return await self.auth.validate_org_api_key(api_key_token)
837+
838+
async def validate_api_key(self, api_key_token: str):
839+
return await self.auth.validate_api_key(api_key_token)
840+
841+
async def fetch_saml_sp_metadata(self, org_id: str):
842+
return await self.auth.fetch_saml_sp_metadata(org_id)
843+
844+
async def set_saml_idp_metadata(self, org_id: str, saml_idp_metadata: SamlIdpMetadata):
845+
return await self.auth.set_saml_idp_metadata(org_id=org_id, saml_idp_metadata=saml_idp_metadata)
846+
847+
async def saml_go_live(self, org_id: str):
848+
return await self.auth.saml_go_live(org_id)
849+
850+
async def delete_saml_connection(self, org_id: str):
851+
return await self.auth.delete_saml_connection(org_id)
852+
853+
async def verify_step_up_totp_challenge(
854+
self,
855+
action_type: str,
856+
user_id: str,
857+
code: str,
858+
grant_type: StepUpMfaGrantType,
859+
valid_for_seconds: int,
860+
) -> StepUpMfaVerifyTotpResponse:
861+
return await self.auth.verify_step_up_totp_challenge(
862+
action_type, user_id, code, grant_type, valid_for_seconds
863+
)
864+
865+
async def verify_step_up_grant(self, action_type: str, user_id: str, grant: str) -> bool:
866+
return await self.auth.verify_step_up_grant(action_type, user_id, grant)
867+
510868
def init_auth(
511869
auth_url: str,
512870
api_key: str,
@@ -520,3 +878,13 @@ def init_auth(
520878
token_verification_metadata=token_verification_metadata,
521879
debug_mode=debug_mode,
522880
)
881+
882+
def init_auth_async(
883+
auth_url: str,
884+
api_key: str,
885+
token_verification_metadata: Optional[TokenVerificationMetadata] = None,
886+
debug_mode=False,
887+
httpx_client: Optional[httpx.AsyncClient] = None,
888+
) -> FlaskAuthAsync:
889+
"""Fetches metadata required to validate access tokens and returns auth decorators and utilities"""
890+
return FlaskAuthAsync(auth_url=auth_url, integration_api_key=api_key, token_verification_metadata=token_verification_metadata, debug_mode=debug_mode, httpx_client=httpx_client)

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
flask<4
2-
propelauth-py==4.2.6
2+
propelauth-py==4.2.7
33
pytest
44
requests-mock
5+
httpx

0 commit comments

Comments
 (0)