Dev (#2440)
* 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:
parent
af3e1e077f
commit
a9a89a479d
6 changed files with 100 additions and 48 deletions
|
|
@ -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.")
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -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)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue