Dev (#2434)
* 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:
parent
d60cda3f0b
commit
45180a8cd6
8 changed files with 194 additions and 32 deletions
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
86
api/chalicelib/core/spot.py
Normal file
86
api/chalicelib/core/spot.py
Normal 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)
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
63
api/routers/subs/spot.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue