* 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

* refactor(chalice): upgraded dependencies
refactor(chalice): merged spot-login logic with regular-login
This commit is contained in:
Kraiem Taha Yassine 2024-07-31 18:37:30 +02:00 committed by GitHub
parent 01af0ccfda
commit 8136d83025
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 119 additions and 49 deletions

View file

@ -14,7 +14,7 @@ from starlette.responses import StreamingResponse
from chalicelib.utils import helper
from chalicelib.utils import pg_client
from crons import core_crons, core_dynamic_crons
from routers import core, core_dynamic, additional_routes
from routers import core, core_dynamic
from routers.subs import insights, metrics, v1_api, health, usability_tests, spot
loglevel = config("LOGLEVEL", default=logging.WARNING)
@ -127,12 +127,3 @@ app.include_router(usability_tests.app_apikey)
app.include_router(spot.public_app)
app.include_router(spot.app)
app.include_router(spot.app_apikey)
app.include_router(additional_routes.app)
# @app.get('/private/shutdown', tags=["private"])
# async def stop_server():
# logging.info("Requested shutdown")
# await shutdown()
# import os, signal
# os.kill(1, signal.SIGTERM)

View file

@ -3,10 +3,11 @@ import secrets
from decouple import config
from fastapi import BackgroundTasks
from pydantic import BaseModel
import schemas
from chalicelib.core import authorizers, metadata, projects
from chalicelib.core import tenants, assist
from chalicelib.core import tenants, assist, spot
from chalicelib.utils import email_helper, smtp
from chalicelib.utils import helper
from chalicelib.utils import pg_client
@ -555,20 +556,40 @@ def refresh_auth_exists(user_id, jwt_jti=None):
return r is not None
def change_jwt_iat_jti(user_id):
class ChangeJwt(BaseModel):
jwt_iat: int
jwt_refresh_jti: int
jwt_refresh_iat: int
spot_jwt_iat: int | None = None
spot_jwt_refresh_jti: int | None = None
spot_jwt_refresh_iat: int | None = None
def change_jwt_iat_jti(user_id, include_spot: bool = False):
sub_query = ""
sub_result = ""
if include_spot:
sub_query = """,spot_jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
spot_jwt_refresh_jti = 0,
spot_jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')"""
sub_result = """,EXTRACT (epoch FROM spot_jwt_iat)::BIGINT AS spot_jwt_iat,
spot_jwt_refresh_jti,
EXTRACT (epoch FROM spot_jwt_refresh_iat)::BIGINT AS spot_jwt_refresh_iat"""
with pg_client.PostgresClient() as cur:
query = cur.mogrify(f"""UPDATE public.users
SET jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
jwt_refresh_jti = 0,
jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')
jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')
{sub_query}
WHERE user_id = %(user_id)s
RETURNING EXTRACT (epoch FROM jwt_iat)::BIGINT AS jwt_iat,
jwt_refresh_jti,
EXTRACT (epoch FROM jwt_refresh_iat)::BIGINT AS jwt_refresh_iat;""",
EXTRACT (epoch FROM jwt_refresh_iat)::BIGINT AS jwt_refresh_iat
{sub_result};""",
{"user_id": user_id})
cur.execute(query)
row = cur.fetchone()
return row.get("jwt_iat"), row.get("jwt_refresh_jti"), row.get("jwt_refresh_iat")
return ChangeJwt(**row)
def refresh_jwt_iat_jti(user_id):
@ -586,7 +607,7 @@ def refresh_jwt_iat_jti(user_id):
return row.get("jwt_iat"), row.get("jwt_refresh_jti"), row.get("jwt_refresh_iat")
def authenticate(email, password, for_change_password=False) -> dict | bool | None:
def authenticate(email, password, for_change_password=False, include_spot=False) -> dict | bool | None:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT
@ -611,17 +632,29 @@ def authenticate(email, password, for_change_password=False) -> dict | bool | No
if for_change_password:
return True
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_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
j_r = change_jwt_iat_jti(user_id=r['userId'], include_spot=include_spot)
response = {
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=j_r.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),
iat=j_r.jwt_refresh_iat, aud=AUDIENCE,
jwt_jti=j_r.jwt_refresh_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
}
if include_spot:
response = {**response,
"spotJwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'],
iat=j_r.spot_jwt_iat, aud=spot.AUDIENCE),
"spotRefreshToken": authorizers.generate_jwt_refresh(user_id=r['userId'],
tenant_id=r['tenantId'],
iat=j_r.spot_jwt_refresh_iat,
aud=spot.AUDIENCE,
jwt_jti=j_r.spot_jwt_refresh_jti),
"spotRefreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
}
return response
return None

View file

@ -1,7 +1,7 @@
# Keep this version to not have conflicts between requests and boto3
urllib3==1.26.16
requests==2.32.3
boto3==1.34.147
boto3==1.34.151
pyjwt==2.8.0
psycopg2-binary==2.9.9
psycopg[pool,binary]==3.2.1

View file

@ -1,7 +1,7 @@
# Keep this version to not have conflicts between requests and boto3
urllib3==1.26.16
requests==2.32.3
boto3==1.34.147
boto3==1.34.151
pyjwt==2.8.0
psycopg2-binary==2.9.9
psycopg[pool,binary]==3.2.1

View file

@ -1,4 +0,0 @@
# this file will be overwritten by the managed saas
from routers.base import get_routers
public_app, app, app_apikey = get_routers()

View file

@ -46,14 +46,14 @@ if not tenants.tenants_exists_sync(use_pool=False):
@public_app.post('/login', tags=["authentication"])
def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)):
def login_user(response: JSONResponse, spot: Optional[bool] = False, data: schemas.UserLoginSchema = Body(...)):
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid captcha."
)
r = users.authenticate(data.email, data.password.get_secret_value())
r = users.authenticate(email=data.email, password=data.password.get_secret_value(), include_spot=spot)
if r is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -74,9 +74,17 @@ def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
"user": r
}
}
if spot:
content["spotJwt"] = r.pop("spotJwt")
spot_refresh_token = r.pop("spotRefreshToken")
spot_refresh_token_max_age = r.pop("spotRefreshTokenMaxAge")
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/refresh",
max_age=refresh_token_max_age, secure=True, httponly=True)
if spot:
response.set_cookie(key="spotRefreshToken", value=spot_refresh_token, path="/api/spot/refresh",
max_age=spot_refresh_token_max_age, secure=True, httponly=True)
return response
@ -84,6 +92,7 @@ def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
def logout_user(response: Response, context: schemas.CurrentContext = Depends(OR_context)):
users.logout(user_id=context.user_id)
response.delete_cookie(key="refreshToken", path="/api/refresh")
response.delete_cookie(key="spotRefreshToken", path="/api/spot/refresh")
return {"data": "success"}

View file

@ -43,7 +43,7 @@ def login_spot(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
}
}
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=refresh_token, path=COOKIE_PATH,
response.set_cookie(key="spotRefreshToken", value=refresh_token, path=COOKIE_PATH,
max_age=refresh_token_max_age, secure=True, httponly=True)
return response
@ -51,7 +51,7 @@ def login_spot(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
@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")
response.delete_cookie(key="spotRefreshToken", path="/api/refresh")
return {"data": "success"}
@ -60,7 +60,7 @@ def refresh_spot_login(context: schemas.CurrentContext = Depends(OR_context)):
r = spot.refresh(user_id=context.user_id)
content = {"jwt": r.get("jwt")}
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=r.get("refreshToken"), path=COOKIE_PATH,
response.set_cookie(key="spotRefreshToken", value=r.get("refreshToken"), path=COOKIE_PATH,
max_age=r.pop("refreshTokenMaxAge"), secure=True, httponly=True)
return response

View file

@ -4,11 +4,12 @@ import secrets
from decouple import config
from fastapi import BackgroundTasks, HTTPException
from pydantic import BaseModel
from starlette import status
import schemas
from chalicelib.core import authorizers, metadata, projects
from chalicelib.core import roles
from chalicelib.core import roles, spot
from chalicelib.core import tenants, assist
from chalicelib.utils import email_helper, smtp
from chalicelib.utils import helper
@ -643,20 +644,40 @@ def refresh_auth_exists(user_id, tenant_id, jwt_jti=None):
return r is not None
def change_jwt_iat_jti(user_id):
class ChangeJwt(BaseModel):
jwt_iat: int
jwt_refresh_jti: int
jwt_refresh_iat: int
spot_jwt_iat: int | None = None
spot_jwt_refresh_jti: int | None = None
spot_jwt_refresh_iat: int | None = None
def change_jwt_iat_jti(user_id, include_spot: bool = False):
sub_query = ""
sub_result = ""
if include_spot:
sub_query = """,spot_jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
spot_jwt_refresh_jti = 0,
spot_jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')"""
sub_result = """,EXTRACT (epoch FROM spot_jwt_iat)::BIGINT AS spot_jwt_iat,
spot_jwt_refresh_jti,
EXTRACT (epoch FROM spot_jwt_refresh_iat)::BIGINT AS spot_jwt_refresh_iat"""
with pg_client.PostgresClient() as cur:
query = cur.mogrify(f"""UPDATE public.users
SET jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
jwt_refresh_jti = 0,
jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')
jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')
{sub_query}
WHERE user_id = %(user_id)s
RETURNING EXTRACT (epoch FROM jwt_iat)::BIGINT AS jwt_iat,
jwt_refresh_jti,
EXTRACT (epoch FROM jwt_refresh_iat)::BIGINT AS jwt_refresh_iat;""",
EXTRACT (epoch FROM jwt_refresh_iat)::BIGINT AS jwt_refresh_iat
{sub_result};""",
{"user_id": user_id})
cur.execute(query)
row = cur.fetchone()
return row.get("jwt_iat"), row.get("jwt_refresh_jti"), row.get("jwt_refresh_iat")
return ChangeJwt(**row)
def refresh_jwt_iat_jti(user_id):
@ -674,7 +695,7 @@ def refresh_jwt_iat_jti(user_id):
return row.get("jwt_iat"), row.get("jwt_refresh_jti"), row.get("jwt_refresh_iat")
def authenticate(email, password, for_change_password=False) -> dict | bool | None:
def authenticate(email, password, for_change_password=False, include_spot=False) -> dict | bool | None:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT
@ -724,18 +745,29 @@ def authenticate(email, password, for_change_password=False) -> dict | bool | No
elif config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
return {"errors": ["must sign-in with SSO, enforced by admin"]}
jwt_iat, jwt_r_jti, jwt_r_iat = change_jwt_iat_jti(user_id=r['userId'])
return {
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
j_r = change_jwt_iat_jti(user_id=r['userId'], include_spot=include_spot)
response = {
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=j_r.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),
iat=j_r.jwt_refresh_iat, aud=AUDIENCE,
jwt_jti=j_r.jwt_refresh_jti),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
}
if include_spot:
response = {**response,
"spotJwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'],
iat=j_r.spot_jwt_iat, aud=spot.AUDIENCE),
"spotRefreshToken": authorizers.generate_jwt_refresh(user_id=r['userId'],
tenant_id=r['tenantId'],
iat=j_r.spot_jwt_refresh_iat,
aud=spot.AUDIENCE,
jwt_jti=j_r.spot_jwt_refresh_jti),
"spotRefreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
}
return response
if config("enforce_SSO", cast=bool, default=False) and helper.is_saml2_available():
return {"errors": ["must sign-in with SSO, enforced by admin"]}
return None

View file

@ -1,7 +1,7 @@
# Keep this version to not have conflicts between requests and boto3
urllib3==1.26.16
requests==2.32.3
boto3==1.34.147
boto3==1.34.151
pyjwt==2.8.0
psycopg2-binary==2.9.9
psycopg[pool,binary]==3.2.1

View file

@ -1,7 +1,7 @@
# Keep this version to not have conflicts between requests and boto3
urllib3==1.26.16
requests==2.32.3
boto3==1.34.147
boto3==1.34.151
pyjwt==2.8.0
psycopg2-binary==2.9.9
psycopg[pool,binary]==3.2.1

View file

@ -1,7 +1,7 @@
# Keep this version to not have conflicts between requests and boto3
urllib3==1.26.16
requests==2.32.3
boto3==1.34.147
boto3==1.34.151
pyjwt==2.8.0
psycopg2-binary==2.9.9
psycopg[pool,binary]==3.2.1

View file

@ -52,14 +52,14 @@ if config("MULTI_TENANTS", cast=bool, default=False) or not tenants.tenants_exis
@public_app.post('/login', tags=["authentication"])
def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)):
def login_user(response: JSONResponse, spot: Optional[bool] = False, data: schemas.UserLoginSchema = Body(...)):
if helper.allow_captcha() and not captcha.is_valid(data.g_recaptcha_response):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid captcha."
)
r = users.authenticate(data.email, data.password.get_secret_value())
r = users.authenticate(email=data.email, password=data.password.get_secret_value(), include_spot=spot)
if r is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@ -80,9 +80,17 @@ def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
"user": r
}
}
if spot:
content["spotJwt"] = r.pop("spotJwt")
spot_refresh_token = r.pop("spotRefreshToken")
spot_refresh_token_max_age = r.pop("spotRefreshTokenMaxAge")
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/refresh",
max_age=refresh_token_max_age, secure=True, httponly=True)
if spot:
response.set_cookie(key="spotRefreshToken", value=spot_refresh_token, path="/api/spot/refresh",
max_age=spot_refresh_token_max_age, secure=True, httponly=True)
return response
@ -90,6 +98,7 @@ def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
def logout_user(response: Response, context: schemas.CurrentContext = Depends(OR_context)):
users.logout(user_id=context.user_id)
response.delete_cookie(key="refreshToken", path="/api/refresh")
response.delete_cookie(key="spotRefreshToken", path="/api/spot/refresh")
return {"data": "success"}