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
10 changes: 5 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ exclude: 'docs/'

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.0.1
rev: v4.3.0
hooks:
- id: check-yaml
- id: debug-statements
Expand All @@ -14,21 +14,21 @@ repos:
- id: isort
args: ["--profile", "black"]
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 22.6.0
hooks:
- id: black
language_version: python3
- repo: https://github.com/asottile/pyupgrade
rev: v2.31.1
rev: v2.37.3
hooks:
- id: pyupgrade
args: [--py37-plus]
- repo: https://gitlab.com/pycqa/flake8
rev: 4.0.1
rev: 3.9.2
hooks:
- id: flake8
- repo: https://github.com/dhatim/python-license-check
rev: master
rev: 0.7.2
hooks:
- id: liccheck
language: system
Expand Down
2 changes: 2 additions & 0 deletions descope/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from descope.common import (
REFRESH_SESSION_COOKIE_NAME,
REFRESH_SESSION_TOKEN_NAME,
SESSION_COOKIE_NAME,
SESSION_TOKEN_NAME,
DeliveryMethod,
)
from descope.descope_client import DescopeClient
Expand Down
97 changes: 45 additions & 52 deletions descope/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
from jwt.exceptions import ExpiredSignatureError

from descope.common import (
COOKIE_DATA_NAME,
DEFAULT_BASE_URL,
PHONE_REGEX,
SESSION_COOKIE_NAME,
REFRESH_SESSION_TOKEN_NAME,
SESSION_TOKEN_NAME,
DeliveryMethod,
EndpointsV1,
)
Expand Down Expand Up @@ -264,54 +266,48 @@ def _fetch_public_keys(self) -> None:
# just continue to the next key
pass

def _extractToken(self, jwt) -> dict:
if not jwt:
return None
token_claims = self._validate_and_load_tokens(jwt, None)
token_claims["projectId"] = token_claims.pop(
"iss"
) # replace the key name from iss->projectId
token_claims["userId"] = token_claims.pop(
"sub"
) # replace the key name from sub->userId
return token_claims

def _generate_auth_info(self, response_body, cookie) -> dict:
tokens = {}
rt = response_body.get("refreshJwt", "")
rtoken = self._extractToken(rt)
if rtoken is not None:
tokens[rtoken["drn"]] = rtoken
stoken = self._extractToken(response_body.get("sessionJwt", ""))
if stoken is not None:
tokens[stoken["drn"]] = stoken

if cookie:
token_claims = self._validate_and_load_tokens(cookie, None)
token_claims["projectId"] = token_claims.pop(
"iss"
) # replace the key name from iss->projectId
token_claims["userId"] = token_claims.pop(
"sub"
) # replace the key name from sub->userId
tokens[token_claims["drn"]] = token_claims

# collect all cookie attributed from response
tokens["cookieDomain"] = response_body.get("cookieDomain", "")
tokens["cookiePath"] = response_body.get("cookiePath", "/")
tokens["cookieMaxAge"] = response_body.get("cookieMaxAge", 0)
tokens["cookieExpiration"] = response_body.get("cookieExpiration", 0)

return tokens

def _generate_jwt_response(self, response_body, cookie) -> dict:
tokens = self._generate_auth_info(response_body, cookie)
jwt_response = {
"error": response_body.get("error", ""),
"jwts": tokens,
"user": response_body.get("user", ""),
"firstSeen": response_body.get("firstSeen", True),
def _generate_auth_info(self, response_body: dict, refresh_cookie: str) -> dict:
jwt_response = {}
st_jwt = response_body.get("sessionJwt", "")
if st_jwt:
jwt_response[SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
st_jwt, None
)
rt_jwt = response_body.get("refreshJwt", "")
if rt_jwt:
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
rt_jwt, None
)

if refresh_cookie:
jwt_response[REFRESH_SESSION_TOKEN_NAME] = self._validate_and_load_tokens(
refresh_cookie, None
)

jwt_response[COOKIE_DATA_NAME] = {
"exp": response_body.get("cookieExpiration", 0),
"maxAge": response_body.get("cookieMaxAge", 0),
"domain": response_body.get("cookieDomain", ""),
"path": response_body.get("cookiePath", "/"),
}

return jwt_response

def generate_jwt_response(self, response_body: dict, refresh_cookie: str) -> dict:
jwt_response = self._generate_auth_info(response_body, refresh_cookie)

projectId = jwt_response.get(SESSION_TOKEN_NAME, {}).get(
"iss", None
) or jwt_response.get(REFRESH_SESSION_TOKEN_NAME, {}).get("iss", None)
user_id = jwt_response.get(SESSION_TOKEN_NAME, {}).get(
"sub", None
) or jwt_response.get(REFRESH_SESSION_TOKEN_NAME, {}).get("sub", None)

jwt_response["tenants"] = response_body.get("tenants", {})
jwt_response["projectId"] = projectId
jwt_response["userId"] = user_id
jwt_response["user"] = response_body.get("user", {})
jwt_response["firstSeen"] = response_body.get("firstSeen", True)
return jwt_response

def _get_default_headers(self, pswd: str = None):
Expand Down Expand Up @@ -405,10 +401,7 @@ def _validate_and_load_tokens(self, session_token: str, refresh_token: str) -> d
)

# Refresh token is valid now refresh the session token
auth_info = self._refresh_token(refresh_token)

claims = auth_info[SESSION_COOKIE_NAME]
return claims
return self._refresh_token(refresh_token) # return jwt_response dict

except Exception as e:
raise AuthException(
Expand Down
2 changes: 1 addition & 1 deletion descope/authmethod/exchanger.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def exchange_token(self, code: str) -> dict:
params = Exchanger._compose_exchange_params(code)
response = self._auth.do_get(uri, params, False)
resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down
4 changes: 2 additions & 2 deletions descope/authmethod/magiclink.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def get_session(self, pending_ref: str) -> dict:
response = self._auth.do_post(uri, body)

resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand All @@ -58,7 +58,7 @@ def verify(self, token: str) -> dict:
body = MagicLink._compose_verify_body(token)
response = self._auth.do_post(uri, body)
resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down
2 changes: 1 addition & 1 deletion descope/authmethod/otp.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def verify_code(self, method: DeliveryMethod, identifier: str, code: str) -> dic
response = self._auth.do_post(uri, body)

resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down
2 changes: 1 addition & 1 deletion descope/authmethod/totp.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def sign_in_code(self, identifier: str, code: str) -> dict:
response = self._auth.do_post(uri, body)

resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down
4 changes: 2 additions & 2 deletions descope/authmethod/webauthn.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def sign_up_finish(self, transactionID: str, response: str) -> dict:
response = self._auth.do_post(uri, body)

resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down Expand Up @@ -92,7 +92,7 @@ def sign_in_finish(self, transaction_id: str, response: str) -> dict:
response = self._auth.do_post(uri, body)

resp = response.json()
jwt_response = self._auth._generate_jwt_response(
jwt_response = self._auth.generate_jwt_response(
resp, response.cookies.get(REFRESH_SESSION_COOKIE_NAME, None)
)
return jwt_response
Expand Down
4 changes: 4 additions & 0 deletions descope/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
SESSION_COOKIE_NAME = "DS"
REFRESH_SESSION_COOKIE_NAME = "DSR"

SESSION_TOKEN_NAME = "sessionToken"
REFRESH_SESSION_TOKEN_NAME = "refreshSessionToken"
COOKIE_DATA_NAME = "cookieData"

REDIRECT_LOCATION_COOKIE_NAME = "Location"


Expand Down
16 changes: 12 additions & 4 deletions descope/descope_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from descope.authmethod.saml import SAML # noqa: F401
from descope.authmethod.totp import TOTP # noqa: F401
from descope.authmethod.webauthn import WebauthN # noqa: F401
from descope.common import EndpointsV1
from descope.common import SESSION_TOKEN_NAME, EndpointsV1
from descope.exceptions import ERROR_TYPE_INVALID_ARGUMENT, AuthException


Expand Down Expand Up @@ -73,10 +73,18 @@ def validate_session_request(self, session_token: str, refresh_token: str) -> di
AuthException: for any case token is not valid means session is not
authorized
"""
token_claims = self._auth._validate_and_load_tokens(
res = self._auth._validate_and_load_tokens(
session_token, refresh_token
)
return {token_claims["drn"]: token_claims}
) # return jwt_response dict

# Check if we had to refresh the session token and got a new one
if res.get(SESSION_TOKEN_NAME, None) and session_token != res.get(
SESSION_TOKEN_NAME
).get("jwt"):
return res
else:
# In such case we return only the data related to the session token
return {SESSION_TOKEN_NAME: res}

def logout(self, refresh_token: str) -> requests.Response:
"""
Expand Down
Loading