* refactor(chalice): upgraded dependencies

* refactor(chalice): upgraded dependencies
feat(chalice): support heatmaps

* fix(chalice): fixed Math-operators validation
refactor(chalice): search for sessions that have events for heatmaps

* refactor(chalice): search for sessions that have at least 1 location event for heatmaps

* refactor(chalice): upgraded dependencies

* refactor(chalice): upgraded dependencies
feat(chalice): support heatmaps

* fix(chalice): fixed Math-operators validation
refactor(chalice): search for sessions that have events for heatmaps

* refactor(chalice): search for sessions that have at least 1 location event for heatmaps

* refactor(chalice): upgraded dependencies
refactor(crons): upgraded dependencies
refactor(alerts): upgraded dependencies

* feat(chalice): get top 10 values for autocomplete CH

* refactor(chalice): cleaned code
refactor(chalice): upgraded dependencies
refactor(alerts): upgraded dependencies
refactor(crons): upgraded dependencies

* feat(chalice): autocomplete return top 10 with stats

* fix(chalice): fixed autocomplete top 10 meta-filters

* feat(chalice): spot login/logout/refresh

* feat(chalice): spot only allow authorized endpoints
feat(chalice): spot get Slack channels
This commit is contained in:
Kraiem Taha Yassine 2024-07-30 14:39:15 +02:00 committed by GitHub
parent af3e1e077f
commit a9a89a479d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 100 additions and 48 deletions

View file

@ -9,7 +9,7 @@ from starlette import status
from starlette.exceptions import HTTPException
import schemas
from chalicelib.core import authorizers, users
from chalicelib.core import authorizers, users, spot
logger = logging.getLogger(__name__)
@ -67,6 +67,42 @@ class JWTAuth(HTTPBearer):
return _get_current_auth_context(request=request, jwt_payload=jwt_payload)
elif request.url.path in ["/spot/refresh", "/spot/api/refresh"]:
if "refreshToken" not in request.cookies:
logger.warning("Missing sopt-refreshToken cookie.")
jwt_payload = None
else:
jwt_payload = authorizers.jwt_refresh_authorizer(scheme="Bearer", token=request.cookies["refreshToken"])
if jwt_payload is None or jwt_payload.get("jti") is None:
logger.warning("Null spot-refreshToken's payload, or null JTI.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid spot-refresh-token or expired refresh-token.")
auth_exists = spot.refresh_auth_exists(user_id=jwt_payload.get("userId", -1),
jwt_jti=jwt_payload["jti"])
if not auth_exists:
logger.warning("spot-refreshToken's user not found.")
logger.warning(jwt_payload)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid spot-refresh-token or expired refresh-token.")
credentials: HTTPAuthorizationCredentials = await super(JWTAuth, self).__call__(request)
if credentials:
if not credentials.scheme == "Bearer":
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid spot-authentication scheme.")
old_jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials,
leeway=datetime.timedelta(
days=config("JWT_LEEWAY_DAYS", cast=int, default=3)
))
if old_jwt_payload is None \
or old_jwt_payload.get("userId") is None \
or old_jwt_payload.get("userId") != jwt_payload.get("userId"):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid spot-token or expired token.")
return _get_current_auth_context(request=request, jwt_payload=jwt_payload)
else:
credentials: HTTPAuthorizationCredentials = await super(JWTAuth, self).__call__(request)
if credentials:
@ -91,6 +127,11 @@ class JWTAuth(HTTPBearer):
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
if jwt_payload.get("aud", "").startswith("spot") and not request.url.path.startswith("/spot"):
# Allow access to spot endpoints only
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Unauthorized access (spot).")
return _get_current_auth_context(request=request, jwt_payload=jwt_payload)
logger.warning("Invalid authorization code.")

View file

@ -2,16 +2,20 @@ import logging
import jwt
from decouple import config
from typing import Optional
from chalicelib.core import tenants
from chalicelib.core import users
from chalicelib.utils import helper
from chalicelib.core import users, spot
from chalicelib.utils.TimeUTC import TimeUTC
logger = logging.getLogger(__name__)
def jwt_authorizer(scheme: str, token: str, leeway=0):
def get_supported_audience():
return [users.AUDIENCE, spot.AUDIENCE]
def jwt_authorizer(scheme: str, token: str, leeway=0) -> Optional[dict]:
if scheme.lower() != "bearer":
return None
try:
@ -19,7 +23,7 @@ def jwt_authorizer(scheme: str, token: str, leeway=0):
token,
config("jwt_secret"),
algorithms=config("jwt_algorithm"),
audience=[f"front:{helper.get_stage_name()}",f"spot:{helper.get_stage_name()}"],
audience=get_supported_audience(),
leeway=leeway
)
except jwt.ExpiredSignatureError:
@ -40,7 +44,7 @@ def jwt_refresh_authorizer(scheme: str, token: str):
token,
config("JWT_REFRESH_SECRET"),
algorithms=config("jwt_algorithm"),
audience=[f"front:{helper.get_stage_name()}"]
audience=get_supported_audience()
)
except jwt.ExpiredSignatureError:
logger.debug("! JWT-refresh Expired signature")
@ -52,18 +56,7 @@ def jwt_refresh_authorizer(scheme: str, token: str):
return payload
def jwt_context(context):
user = users.get(user_id=context["userId"], tenant_id=context["tenantId"])
if user is None:
return None
return {
"tenantId": context["tenantId"],
"userId": context["userId"],
**user
}
def generate_spot_jwt(user_id, tenant_id, iat, aud):
def generate_jwt(user_id, tenant_id, iat, aud):
token = jwt.encode(
payload={
"userId": user_id,
@ -79,7 +72,7 @@ def generate_spot_jwt(user_id, tenant_id, iat, aud):
return token
def generate_spot_jwt_refresh(user_id, tenant_id, iat, aud, jwt_jti):
def generate_jwt_refresh(user_id, tenant_id, iat, aud, jwt_jti):
token = jwt.encode(
payload={
"userId": user_id,

View file

@ -4,6 +4,8 @@ from chalicelib.core import authorizers, users
from chalicelib.utils import helper
from chalicelib.utils import pg_client
AUDIENCE = "spot:OpenReplay"
def change_spot_jwt_iat_jti(user_id):
with pg_client.PostgresClient() as cur:
@ -58,11 +60,11 @@ def authenticate(email, password) -> dict | None:
r = helper.dict_to_camel_case(r)
spot_jwt_iat, spot_jwt_r_jti, spot_jwt_r_iat = change_spot_jwt_iat_jti(user_id=r['userId'])
return {
"jwt": authorizers.generate_spot_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=spot_jwt_iat,
aud=f"spot:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_spot_jwt_refresh(user_id=r['userId'], tenant_id=r['tenantId'],
iat=spot_jwt_r_iat, aud=f"spot:{helper.get_stage_name()}",
jwt_jti=spot_jwt_r_jti),
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=spot_jwt_iat,
aud=AUDIENCE),
"refreshToken": authorizers.generate_jwt_refresh(user_id=r['userId'], tenant_id=r['tenantId'],
iat=spot_jwt_r_iat, aud=AUDIENCE,
jwt_jti=spot_jwt_r_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
@ -77,10 +79,24 @@ def logout(user_id: int):
def refresh(user_id: int, tenant_id: int = -1) -> dict:
spot_jwt_iat, spot_jwt_r_jti, spot_jwt_r_iat = refresh_spot_jwt_iat_jti(user_id=user_id)
return {
"jwt": authorizers.generate_spot_jwt(user_id=user_id, tenant_id=tenant_id, iat=spot_jwt_iat,
aud=f"spot:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_spot_jwt_refresh(user_id=user_id, tenant_id=tenant_id, iat=spot_jwt_r_iat,
aud=f"spot:{helper.get_stage_name()}",
jwt_jti=spot_jwt_r_jti),
"jwt": authorizers.generate_jwt(user_id=user_id, tenant_id=tenant_id, iat=spot_jwt_iat,
aud=AUDIENCE),
"refreshToken": authorizers.generate_jwt_refresh(user_id=user_id, tenant_id=tenant_id, iat=spot_jwt_r_iat,
aud=AUDIENCE, jwt_jti=spot_jwt_r_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int) - (spot_jwt_iat - spot_jwt_r_iat)
}
def refresh_auth_exists(user_id, jwt_jti=None):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify(f"""SELECT user_id
FROM public.users
WHERE user_id = %(userId)s
AND deleted_at IS NULL
AND spot_jwt_refresh_jti = %(jwt_jti)s
LIMIT 1;""",
{"userId": user_id, "jwt_jti": jwt_jti})
)
r = cur.fetchone()
return r is not None

View file

@ -12,6 +12,8 @@ from chalicelib.utils import helper
from chalicelib.utils import pg_client
from chalicelib.utils.TimeUTC import TimeUTC
AUDIENCE = "front:OpenReplay"
def __generate_invitation_token():
return secrets.token_urlsafe(64)
@ -611,11 +613,11 @@ def authenticate(email, password, for_change_password=False) -> dict | bool | No
r = helper.dict_to_camel_case(r)
jwt_iat, jwt_r_jti, jwt_r_iat = change_jwt_iat_jti(user_id=r['userId'])
return {
"jwt": authorizers.generate_spot_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
aud=f"front:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_spot_jwt_refresh(user_id=r['userId'], tenant_id=r['tenantId'],
iat=jwt_r_iat, aud=f"front:{helper.get_stage_name()}",
jwt_jti=jwt_r_jti),
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
aud=AUDIENCE),
"refreshToken": authorizers.generate_jwt_refresh(user_id=r['userId'], tenant_id=r['tenantId'],
iat=jwt_r_iat, aud=AUDIENCE,
jwt_jti=jwt_r_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
@ -637,11 +639,10 @@ def logout(user_id: int):
def refresh(user_id: int, tenant_id: int = -1) -> dict:
jwt_iat, jwt_r_jti, jwt_r_iat = refresh_jwt_iat_jti(user_id=user_id)
return {
"jwt": authorizers.generate_spot_jwt(user_id=user_id, tenant_id=tenant_id, iat=jwt_iat,
aud=f"front:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_spot_jwt_refresh(user_id=user_id, tenant_id=tenant_id, iat=jwt_r_iat,
aud=f"front:{helper.get_stage_name()}",
jwt_jti=jwt_r_jti),
"jwt": authorizers.generate_jwt(user_id=user_id, tenant_id=tenant_id, iat=jwt_iat,
aud=AUDIENCE),
"refreshToken": authorizers.generate_jwt_refresh(user_id=user_id, tenant_id=tenant_id, iat=jwt_r_iat,
aud=AUDIENCE, jwt_jti=jwt_r_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int) - (jwt_iat - jwt_r_iat)
}

View file

@ -11,10 +11,6 @@ import schemas
from chalicelib.utils.TimeUTC import TimeUTC
def get_stage_name():
return "OpenReplay"
def random_string(length=36):
return "".join(random.choices(string.hexdigits, k=length))

View file

@ -3,7 +3,7 @@ from fastapi import HTTPException, status
from starlette.responses import JSONResponse, Response
import schemas
from chalicelib.core import spot
from chalicelib.core import spot, webhook
from chalicelib.utils import captcha
from chalicelib.utils import helper
from or_dependencies import OR_context
@ -12,7 +12,7 @@ from routers.base import get_routers
public_app, app, app_apikey = get_routers(prefix="/spot", tags=["spot"])
@public_app.post('/login', tags=["authentication"])
@public_app.post('/login')
def login_spot(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)):
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
raise HTTPException(
@ -46,14 +46,14 @@ def login_spot(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
return response
@app.get('/logout', tags=["login"])
@app.get('/logout')
def logout_spot(response: Response, context: schemas.CurrentContext = Depends(OR_context)):
spot.logout(user_id=context.user_id)
response.delete_cookie(key="refreshToken", path="/api/refresh")
return {"data": "success"}
@app.get('/refresh', tags=["login"])
@app.get('/refresh')
def refresh_spot_login(context: schemas.CurrentContext = Depends(OR_context)):
r = spot.refresh(user_id=context.user_id)
content = {"jwt": r.get("jwt")}
@ -61,3 +61,8 @@ def refresh_spot_login(context: schemas.CurrentContext = Depends(OR_context)):
response.set_cookie(key="refreshToken", value=r.get("refreshToken"), path="/api/refresh",
max_age=r.pop("refreshTokenMaxAge"), secure=True, httponly=True)
return response
@app.get('/integrations/slack/channels', tags=["integrations"])
def get_slack_channels(context: schemas.CurrentContext = Depends(OR_context)):
return {"data": webhook.get_by_type(tenant_id=context.tenant_id, webhook_type=schemas.WebhookType.SLACK)}