Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,5 @@ dmypy.json

# Pyre type checker
.pyre/

.vscode/
127 changes: 127 additions & 0 deletions descope/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,18 @@ def _compose_signup_url(method: DeliveryMethod) -> str:
def _compose_verify_code_url(method: DeliveryMethod) -> str:
return AuthClient._compose_url(EndpointsV1.verifyCodeAuthPath, method)

@staticmethod
def _compose_signin_magiclink_url(method: DeliveryMethod) -> str:
return AuthClient._compose_url(EndpointsV1.signInAuthMagicLinkPath, method)

@staticmethod
def _compose_signup_magiclink_url(method: DeliveryMethod) -> str:
return AuthClient._compose_url(EndpointsV1.signUpAuthMagicLinkPath, method)

@staticmethod
def _compose_verify_magiclink_url() -> str:
return EndpointsV1.verifyMagicLinkAuthPath

@staticmethod
def _compose_refresh_token_url() -> str:
return EndpointsV1.refreshTokenPath
Expand Down Expand Up @@ -331,6 +343,121 @@ def verify_code(
claims, tokens = self._validate_and_load_tokens(session_token, refresh_token)
return (claims, tokens)

def sign_up_magiclink(
self, method: DeliveryMethod, identifier: str, uri: str, user: User = None
) -> None:
"""
Sign up a new user by magic link

Args:
method (DeliveryMethod): The Magic Link method you would like to verify the code
sent to you (by the same delivery method)

identifier (str): The identifier based on the chosen delivery method,
For email it should be the email address.
For phone it should be the phone number you would like to get the link
For whatsapp it should be the phone number you would like to get the link

uri (str): The base URI that should contain the magic link code

Raise:
AuthException: for any case sign up by magic link operation failed
"""

if not self._verify_delivery_method(method, identifier):
raise AuthException(
500,
"identifier failure",
f"Identifier {identifier} is not valid by delivery method {method}",
)

body = {self._get_identifier_name_by_method(method): identifier, "URI": uri}

if user is not None:
body["user"] = user.get_data()

requestUri = AuthClient._compose_signup_magiclink_url(method)
response = requests.post(
f"{DEFAULT_BASE_URI}{requestUri}",
headers=self._get_default_headers(),
data=json.dumps(body),
)
if not response.ok:
raise AuthException(response.status_code, "", response.reason)

def sign_in_magiclink(
self, method: DeliveryMethod, identifier: str, uri: str
) -> None:
"""
Sign in a user by magiclink

Args:
method (DeliveryMethod): The Magic Link method you would like to verify the link
sent to you (by the same delivery method)

identifier (str): The identifier based on the chosen delivery method,
For email it should be the email address.
For phone it should be the phone number you would like to get the link
For whatsapp it should be the phone number you would like to get the link

uri (str): The base URI that should contain the magic link code

Raise:
AuthException: for any case sign up by otp operation failed
"""

if not self._verify_delivery_method(method, identifier):
raise AuthException(
500,
"identifier failure",
f"Identifier {identifier} is not valid by delivery method {method}",
)

body = {self._get_identifier_name_by_method(method): identifier, "URI": uri}

requestUri = AuthClient._compose_signin_magiclink_url(method)
response = requests.post(
f"{DEFAULT_BASE_URI}{requestUri}",
headers=self._get_default_headers(),
data=json.dumps(body),
)
if not response.ok:
raise AuthException(response.status_code, "", response.text)

def verify_magiclink(
self, code: str
) -> Tuple[dict, dict]: # Tuple(dict of claims, dict of tokens)
"""Verify magiclink

Args:
code (str): The authorization code you get by the delivery method during signup/signin

Return value (Tuple[dict, dict]):
Return two dicts where the first contains the jwt claims data and
second contains the existing signed token (or the new signed
token in case the old one expired) and refreshed session token

Raise:
AuthException: for any case code is not valid or tokens verification failed
"""

body = {"token": code}

uri = AuthClient._compose_verify_magiclink_url()
response = requests.post(
f"{DEFAULT_BASE_URI}{uri}",
headers=self._get_default_headers(),
data=json.dumps(body),
)
if not response.ok:
raise AuthException(response.status_code, "", response.reason)

session_token = response.cookies.get(SESSION_COOKIE_NAME)
refresh_token = response.cookies.get(REFRESH_SESSION_COOKIE_NAME)

claims, tokens = self._validate_and_load_tokens(session_token, refresh_token)
return (claims, tokens)

def refresh_token(self, signed_token: str, signed_refresh_token: str) -> str:
cookies = {
SESSION_COOKIE_NAME: signed_token,
Expand Down
3 changes: 3 additions & 0 deletions descope/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class EndpointsV1:
signInAuthOTPPath = "/v1/auth/signin/otp"
signUpAuthOTPPath = "/v1/auth/signup/otp"
verifyCodeAuthPath = "/v1/auth/code/verify"
signInAuthMagicLinkPath = "/v1/auth/signin/magiclink"
signUpAuthMagicLinkPath = "/v1/auth/signup/magiclink"
verifyMagicLinkAuthPath = "/v1/auth/magiclink/verify"
publicKeyPath = "/v1/keys"
refreshTokenPath = "/v1/refresh"
logoutPath = "/v1/logoutall"
Expand Down
Loading