Skip to content

Commit ece7613

Browse files
authored
Add magic link support to python (#3)
* Add magic link support to python 1. Implement SDK 2. Implement simple sample app 3. implement decorators, while doing that 1. While doing that fixed all decorators, so they will get the client as a parameter, and we will not initiate it in the decorator file 2. Removed hard coded project ids from code 3. Removed public key const from code 4. Added tests 5. Renamed files of samples, so it will be clear what they are handling 6. Added to gitignore file the .vscode dir * Fix linting
1 parent 1258096 commit ece7613

File tree

9 files changed

+790
-157
lines changed

9 files changed

+790
-157
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,5 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
.vscode/

descope/auth.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,18 @@ def _compose_signup_url(method: DeliveryMethod) -> str:
184184
def _compose_verify_code_url(method: DeliveryMethod) -> str:
185185
return AuthClient._compose_url(EndpointsV1.verifyCodeAuthPath, method)
186186

187+
@staticmethod
188+
def _compose_signin_magiclink_url(method: DeliveryMethod) -> str:
189+
return AuthClient._compose_url(EndpointsV1.signInAuthMagicLinkPath, method)
190+
191+
@staticmethod
192+
def _compose_signup_magiclink_url(method: DeliveryMethod) -> str:
193+
return AuthClient._compose_url(EndpointsV1.signUpAuthMagicLinkPath, method)
194+
195+
@staticmethod
196+
def _compose_verify_magiclink_url() -> str:
197+
return EndpointsV1.verifyMagicLinkAuthPath
198+
187199
@staticmethod
188200
def _compose_refresh_token_url() -> str:
189201
return EndpointsV1.refreshTokenPath
@@ -331,6 +343,121 @@ def verify_code(
331343
claims, tokens = self._validate_and_load_tokens(session_token, refresh_token)
332344
return (claims, tokens)
333345

346+
def sign_up_magiclink(
347+
self, method: DeliveryMethod, identifier: str, uri: str, user: User = None
348+
) -> None:
349+
"""
350+
Sign up a new user by magic link
351+
352+
Args:
353+
method (DeliveryMethod): The Magic Link method you would like to verify the code
354+
sent to you (by the same delivery method)
355+
356+
identifier (str): The identifier based on the chosen delivery method,
357+
For email it should be the email address.
358+
For phone it should be the phone number you would like to get the link
359+
For whatsapp it should be the phone number you would like to get the link
360+
361+
uri (str): The base URI that should contain the magic link code
362+
363+
Raise:
364+
AuthException: for any case sign up by magic link operation failed
365+
"""
366+
367+
if not self._verify_delivery_method(method, identifier):
368+
raise AuthException(
369+
500,
370+
"identifier failure",
371+
f"Identifier {identifier} is not valid by delivery method {method}",
372+
)
373+
374+
body = {self._get_identifier_name_by_method(method): identifier, "URI": uri}
375+
376+
if user is not None:
377+
body["user"] = user.get_data()
378+
379+
requestUri = AuthClient._compose_signup_magiclink_url(method)
380+
response = requests.post(
381+
f"{DEFAULT_BASE_URI}{requestUri}",
382+
headers=self._get_default_headers(),
383+
data=json.dumps(body),
384+
)
385+
if not response.ok:
386+
raise AuthException(response.status_code, "", response.reason)
387+
388+
def sign_in_magiclink(
389+
self, method: DeliveryMethod, identifier: str, uri: str
390+
) -> None:
391+
"""
392+
Sign in a user by magiclink
393+
394+
Args:
395+
method (DeliveryMethod): The Magic Link method you would like to verify the link
396+
sent to you (by the same delivery method)
397+
398+
identifier (str): The identifier based on the chosen delivery method,
399+
For email it should be the email address.
400+
For phone it should be the phone number you would like to get the link
401+
For whatsapp it should be the phone number you would like to get the link
402+
403+
uri (str): The base URI that should contain the magic link code
404+
405+
Raise:
406+
AuthException: for any case sign up by otp operation failed
407+
"""
408+
409+
if not self._verify_delivery_method(method, identifier):
410+
raise AuthException(
411+
500,
412+
"identifier failure",
413+
f"Identifier {identifier} is not valid by delivery method {method}",
414+
)
415+
416+
body = {self._get_identifier_name_by_method(method): identifier, "URI": uri}
417+
418+
requestUri = AuthClient._compose_signin_magiclink_url(method)
419+
response = requests.post(
420+
f"{DEFAULT_BASE_URI}{requestUri}",
421+
headers=self._get_default_headers(),
422+
data=json.dumps(body),
423+
)
424+
if not response.ok:
425+
raise AuthException(response.status_code, "", response.text)
426+
427+
def verify_magiclink(
428+
self, code: str
429+
) -> Tuple[dict, dict]: # Tuple(dict of claims, dict of tokens)
430+
"""Verify magiclink
431+
432+
Args:
433+
code (str): The authorization code you get by the delivery method during signup/signin
434+
435+
Return value (Tuple[dict, dict]):
436+
Return two dicts where the first contains the jwt claims data and
437+
second contains the existing signed token (or the new signed
438+
token in case the old one expired) and refreshed session token
439+
440+
Raise:
441+
AuthException: for any case code is not valid or tokens verification failed
442+
"""
443+
444+
body = {"token": code}
445+
446+
uri = AuthClient._compose_verify_magiclink_url()
447+
response = requests.post(
448+
f"{DEFAULT_BASE_URI}{uri}",
449+
headers=self._get_default_headers(),
450+
data=json.dumps(body),
451+
)
452+
if not response.ok:
453+
raise AuthException(response.status_code, "", response.reason)
454+
455+
session_token = response.cookies.get(SESSION_COOKIE_NAME)
456+
refresh_token = response.cookies.get(REFRESH_SESSION_COOKIE_NAME)
457+
458+
claims, tokens = self._validate_and_load_tokens(session_token, refresh_token)
459+
return (claims, tokens)
460+
334461
def refresh_token(self, signed_token: str, signed_refresh_token: str) -> str:
335462
cookies = {
336463
SESSION_COOKIE_NAME: signed_token,

descope/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ class EndpointsV1:
1313
signInAuthOTPPath = "/v1/auth/signin/otp"
1414
signUpAuthOTPPath = "/v1/auth/signup/otp"
1515
verifyCodeAuthPath = "/v1/auth/code/verify"
16+
signInAuthMagicLinkPath = "/v1/auth/signin/magiclink"
17+
signUpAuthMagicLinkPath = "/v1/auth/signup/magiclink"
18+
verifyMagicLinkAuthPath = "/v1/auth/magiclink/verify"
1619
publicKeyPath = "/v1/keys"
1720
refreshTokenPath = "/v1/refresh"
1821
logoutPath = "/v1/logoutall"

0 commit comments

Comments
 (0)