* 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(DB): support spot login/refresh
This commit is contained in:
Kraiem Taha Yassine 2024-07-26 12:13:23 +02:00 committed by GitHub
parent d60cda3f0b
commit 45180a8cd6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 194 additions and 32 deletions

View file

@ -15,7 +15,7 @@ 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.subs import insights, metrics, v1_api, health, usability_tests
from routers.subs import insights, metrics, v1_api, health, usability_tests, spot
loglevel = config("LOGLEVEL", default=logging.WARNING)
print(f">Loglevel set to: {loglevel}")
@ -124,6 +124,10 @@ app.include_router(usability_tests.public_app)
app.include_router(usability_tests.app)
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"])

View file

@ -19,7 +19,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()}"],
audience=[f"front:{helper.get_stage_name()}",f"spot:{helper.get_stage_name()}"],
leeway=leeway
)
except jwt.ExpiredSignatureError:
@ -63,7 +63,7 @@ def jwt_context(context):
}
def generate_jwt(user_id, tenant_id, iat, aud):
def generate_spot_jwt(user_id, tenant_id, iat, aud):
token = jwt.encode(
payload={
"userId": user_id,
@ -79,7 +79,7 @@ def generate_jwt(user_id, tenant_id, iat, aud):
return token
def generate_jwt_refresh(user_id, tenant_id, iat, aud, jwt_jti):
def generate_spot_jwt_refresh(user_id, tenant_id, iat, aud, jwt_jti):
token = jwt.encode(
payload={
"userId": user_id,

View file

@ -0,0 +1,86 @@
from decouple import config
from chalicelib.core import authorizers, users
from chalicelib.utils import helper
from chalicelib.utils import pg_client
def change_spot_jwt_iat_jti(user_id):
with pg_client.PostgresClient() as cur:
query = cur.mogrify(f"""UPDATE public.users
SET spot_jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
spot_jwt_refresh_jti = 0,
spot_jwt_refresh_iat = timezone('utc'::text, now()-INTERVAL '10s')
WHERE user_id = %(user_id)s
RETURNING 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;""",
{"user_id": user_id})
cur.execute(query)
row = cur.fetchone()
return row.get("spot_jwt_iat"), row.get("spot_jwt_refresh_jti"), row.get("spot_jwt_refresh_iat")
def refresh_spot_jwt_iat_jti(user_id):
with pg_client.PostgresClient() as cur:
query = cur.mogrify(f"""UPDATE public.users
SET spot_jwt_iat = timezone('utc'::text, now()-INTERVAL '10s'),
spot_jwt_refresh_jti = spot_jwt_refresh_jti + 1
WHERE user_id = %(user_id)s
RETURNING EXTRACT (epoch FROM spot_jwt_iat)::BIGINT AS spot_jwt_iat,
spot_jwt_refresh_jti,
EXTRACT (epoch FROM spot_jwt_refresh_jti)::BIGINT AS spot_jwt_refresh_jti;""",
{"user_id": user_id})
cur.execute(query)
row = cur.fetchone()
return row.get("spot_jwt_iat"), row.get("spot_jwt_refresh_jti"), row.get("spot_jwt_refresh_jti")
def authenticate(email, password) -> dict | None:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT
users.user_id,
1 AS tenant_id,
users.name,
users.email
FROM public.users INNER JOIN public.basic_authentication USING(user_id)
WHERE users.email = %(email)s
AND basic_authentication.password = crypt(%(password)s, basic_authentication.password)
AND basic_authentication.user_id = (SELECT su.user_id FROM public.users AS su WHERE su.email=%(email)s AND su.deleted_at IS NULL LIMIT 1)
LIMIT 1;""",
{"email": email, "password": password})
cur.execute(query)
r = cur.fetchone()
if r is not 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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
}
return None
def logout(user_id: int):
users.logout(user_id=user_id)
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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int) - (spot_jwt_iat - spot_jwt_r_iat)
}

View file

@ -611,11 +611,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_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
aud=f"front:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_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_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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
@ -627,7 +627,8 @@ def logout(user_id: int):
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
"""UPDATE public.users
SET jwt_iat = NULL, jwt_refresh_jti = NULL, jwt_refresh_iat = NULL
SET jwt_iat = NULL, jwt_refresh_jti = NULL, jwt_refresh_iat = NULL,
spot_jwt_iat = NULL, spot_jwt_refresh_jti = NULL, spot_jwt_refresh_iat = NULL
WHERE user_id = %(user_id)s;""",
{"user_id": user_id})
cur.execute(query)
@ -636,11 +637,11 @@ 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_jwt(user_id=user_id, tenant_id=tenant_id, iat=jwt_iat,
aud=f"front:{helper.get_stage_name()}"),
"refreshToken": authorizers.generate_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_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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int) - (jwt_iat - jwt_r_iat)
}

View file

@ -6,11 +6,11 @@ from auth.auth_project import ProjectAuthorizer
from or_dependencies import ORRoute
def get_routers(extra_dependencies=[]) -> (APIRouter, APIRouter, APIRouter):
public_app = APIRouter(route_class=ORRoute)
def get_routers(prefix="", extra_dependencies=[], tags=[]) -> (APIRouter, APIRouter, APIRouter):
public_app = APIRouter(route_class=ORRoute, prefix=prefix, tags=tags)
app = APIRouter(dependencies=[Depends(JWTAuth()), Depends(ProjectAuthorizer("projectId"))] + extra_dependencies,
route_class=ORRoute)
route_class=ORRoute, prefix=prefix, tags=tags)
app_apikey = APIRouter(
dependencies=[Depends(APIKeyAuth()), Depends(ProjectAuthorizer("projectKey"))] + extra_dependencies,
route_class=ORRoute)
route_class=ORRoute, prefix=prefix, tags=tags)
return public_app, app, app_apikey

63
api/routers/subs/spot.py Normal file
View file

@ -0,0 +1,63 @@
from fastapi import Body, Depends
from fastapi import HTTPException, status
from starlette.responses import JSONResponse, Response
import schemas
from chalicelib.core import spot
from chalicelib.utils import captcha
from chalicelib.utils import helper
from or_dependencies import OR_context
from routers.base import get_routers
public_app, app, app_apikey = get_routers(prefix="/spot", tags=["spot"])
@public_app.post('/login', tags=["authentication"])
def login_spot(response: JSONResponse, 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 = spot.authenticate(data.email, data.password.get_secret_value())
if r is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="You've entered invalid Email or Password."
)
if "errors" in r:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail=r["errors"][0]
)
refresh_token = r.pop("refreshToken")
refresh_token_max_age = r.pop("refreshTokenMaxAge")
content = {
'jwt': r.pop('jwt'),
'data': {
"user": r
}
}
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=refresh_token, path="/api/spot/refresh",
max_age=refresh_token_max_age, secure=True, httponly=True)
return response
@app.get('/logout', tags=["login"])
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"])
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="/api/refresh",
max_age=r.pop("refreshTokenMaxAge"), secure=True, httponly=True)
return response

View file

@ -22,6 +22,11 @@ ALTER TABLE IF EXISTS events.clicks
ALTER COLUMN normalized_x SET DATA TYPE decimal,
ALTER COLUMN normalized_y SET DATA TYPE decimal;
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS spot_jwt_iat timestamp without time zone NULL DEFAULT NULL,
ADD COLUMN IF NOT EXISTS spot_jwt_refresh_jti integer NULL DEFAULT NULL,
ADD COLUMN IF NOT EXISTS spot_jwt_refresh_iat timestamp without time zone NULL DEFAULT NULL;
COMMIT;
\elif :is_next

View file

@ -110,19 +110,22 @@ CREATE TYPE user_role AS ENUM ('owner', 'admin', 'member');
CREATE TABLE public.users
(
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE,
role user_role NOT NULL DEFAULT 'member',
name text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
deleted_at timestamp without time zone NULL DEFAULT NULL,
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
jwt_iat timestamp without time zone NULL DEFAULT NULL,
jwt_refresh_jti integer NULL DEFAULT NULL,
jwt_refresh_iat timestamp without time zone NULL DEFAULT NULL,
data jsonb NOT NULL DEFAULT '{}'::jsonb,
weekly_report boolean NOT NULL DEFAULT TRUE,
settings jsonb DEFAULT NULL
user_id integer generated BY DEFAULT AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE,
role user_role NOT NULL DEFAULT 'member',
name text NOT NULL,
created_at timestamp without time zone NOT NULL DEFAULT (now() at time zone 'utc'),
deleted_at timestamp without time zone NULL DEFAULT NULL,
api_key text UNIQUE DEFAULT generate_api_key(20) NOT NULL,
jwt_iat timestamp without time zone NULL DEFAULT NULL,
jwt_refresh_jti integer NULL DEFAULT NULL,
jwt_refresh_iat timestamp without time zone NULL DEFAULT NULL,
spot_jwt_iat timestamp without time zone NULL DEFAULT NULL,
spot_jwt_refresh_jti integer NULL DEFAULT NULL,
spot_jwt_refresh_iat timestamp without time zone NULL DEFAULT NULL,
data jsonb NOT NULL DEFAULT '{}'::jsonb,
weekly_report boolean NOT NULL DEFAULT TRUE,
settings jsonb DEFAULT NULL
);
CREATE TABLE public.basic_authentication