Revert "feat(chalice): refresh token"

This reverts commit 12a0af0862.
This commit is contained in:
Taha Yassine Kraiem 2023-09-21 15:52:04 +01:00
parent 12a0af0862
commit 6802b216b7
6 changed files with 69 additions and 221 deletions

View file

@ -1,4 +1,3 @@
import datetime
from typing import Optional
from fastapi import Request
@ -6,20 +5,8 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from starlette import status
from starlette.exceptions import HTTPException
import schemas
from chalicelib.core import authorizers, users
def _get_current_auth_context(request: Request, jwt_payload: dict) -> schemas.CurrentContext:
user = users.get(user_id=jwt_payload.get("userId", -1), tenant_id=jwt_payload.get("tenantId", -1))
if user is None:
print("JWTAuth: User not found.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
request.state.authorizer_identity = "jwt"
request.state.currentContext = schemas.CurrentContext(tenant_id=jwt_payload.get("tenantId", -1),
user_id=jwt_payload.get("userId", -1),
email=user["email"])
return request.state.currentContext
import schemas
class JWTAuth(HTTPBearer):
@ -27,60 +14,40 @@ class JWTAuth(HTTPBearer):
super(JWTAuth, self).__init__(auto_error=auto_error)
async def __call__(self, request: Request) -> Optional[schemas.CurrentContext]:
if request.url.path == "/refresh":
refresh_token = request.cookies.get("refreshToken")
jwt_payload = authorizers.jwt_refresh_authorizer(scheme="Bearer", token=refresh_token)
if jwt_payload is None or jwt_payload.get("jti") is None:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
auth_exists = users.auth_exists(user_id=jwt_payload.get("userId", -1),
tenant_id=jwt_payload.get("tenantId", -1),
jwt_iat=jwt_payload.get("iat", 100),
jwt_aud=jwt_payload.get("aud", ""),
jwt_jti=jwt_payload["jti"])
if not auth_exists:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired 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 authentication scheme.")
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
auth_exists = jwt_payload is not None \
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
tenant_id=jwt_payload.get("tenantId", -1),
jwt_iat=jwt_payload.get("iat", 100),
jwt_aud=jwt_payload.get("aud", ""))
if jwt_payload is None \
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
or not auth_exists:
if jwt_payload is not None:
print(jwt_payload)
if jwt_payload.get("iat") is None:
print("JWTAuth: iat is None")
if jwt_payload.get("aud") is None:
print("JWTAuth: aud is None")
if not auth_exists:
print("JWTAuth: not users.auth_exists")
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 authentication scheme.")
old_jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials,
leeway=datetime.timedelta(days=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 token or expired token.")
return _get_current_auth_context(request=request, jwt_payload=jwt_payload)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
user = users.get(user_id=jwt_payload.get("userId", -1), tenant_id=jwt_payload.get("tenantId", -1))
if user is None:
print("JWTAuth: User not found.")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="User not found.")
jwt_payload["authorizer_identity"] = "jwt"
request.state.authorizer_identity = "jwt"
request.state.currentContext = schemas.CurrentContext(tenantId=jwt_payload.get("tenantId", -1),
userId=jwt_payload.get("userId", -1),
email=user["email"])
return request.state.currentContext
else:
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 authentication scheme.")
jwt_payload = authorizers.jwt_authorizer(scheme=credentials.scheme, token=credentials.credentials)
auth_exists = jwt_payload is not None \
and users.auth_exists(user_id=jwt_payload.get("userId", -1),
tenant_id=jwt_payload.get("tenantId", -1),
jwt_iat=jwt_payload.get("iat", 100),
jwt_aud=jwt_payload.get("aud", ""))
if jwt_payload is None \
or jwt_payload.get("iat") is None or jwt_payload.get("aud") is None \
or not auth_exists:
if jwt_payload is not None:
print(jwt_payload)
if jwt_payload.get("iat") is None:
print("JWTAuth: iat is None")
if jwt_payload.get("aud") is None:
print("JWTAuth: aud is None")
if not auth_exists:
print("JWTAuth: not users.auth_exists")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token or expired token.")
return _get_current_auth_context(request=request, jwt_payload=jwt_payload)
print("JWTAuth: Invalid authorization code.")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authorization code.")
print("JWTAuth: Invalid authorization code.")
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid authorization code.")

View file

@ -6,7 +6,7 @@ from chalicelib.core import tenants
from chalicelib.core import users
def jwt_authorizer(scheme: str, token: str, leeway=0):
def jwt_authorizer(scheme: str, token: str):
if scheme.lower() != "bearer":
return None
try:
@ -14,8 +14,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()}"],
leeway=leeway
audience=[f"front:{helper.get_stage_name()}"]
)
except jwt.ExpiredSignatureError:
print("! JWT Expired signature")
@ -27,26 +26,6 @@ def jwt_authorizer(scheme: str, token: str, leeway=0):
return payload
def jwt_refresh_authorizer(scheme: str, token: str):
if scheme.lower() != "bearer":
return None
try:
payload = jwt.decode(
token,
config("JWT_REFRESH_SECRET"),
algorithms=config("jwt_algorithm"),
audience=[f"front:{helper.get_stage_name()}"]
)
except jwt.ExpiredSignatureError:
print("! JWT-refresh Expired signature")
return None
except BaseException as e:
print("! JWT-refresh Base Exception")
print(e)
return None
return payload
def jwt_context(context):
user = users.get(user_id=context["userId"], tenant_id=context["tenantId"])
if user is None:
@ -62,14 +41,10 @@ def get_jwt_exp(iat):
return iat // 1000 + config("JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000
def get_jwt_refresh_exp(iat):
return iat // 1000 + config("JWT_REFRESH_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000
def generate_jwt(user_id, tenant_id, iat, aud):
def generate_jwt(id, tenant_id, iat, aud):
token = jwt.encode(
payload={
"userId": user_id,
"userId": id,
"tenantId": tenant_id,
"exp": get_jwt_exp(iat),
"iss": config("JWT_ISSUER"),
@ -82,23 +57,6 @@ def generate_jwt(user_id, tenant_id, iat, aud):
return token
def generate_jwt_refresh(user_id, tenant_id, iat, aud, jwt_jti):
token = jwt.encode(
payload={
"userId": user_id,
"tenantId": tenant_id,
"exp": get_jwt_refresh_exp(iat),
"iss": config("JWT_ISSUER"),
"iat": iat // 1000,
"aud": aud,
"jti": jwt_jti
},
key=config("JWT_REFRESH_SECRET"),
algorithm=config("jwt_algorithm")
)
return token
def api_key_authorizer(token):
t = tenants.get_by_api_key(token)
if t is not None:

View file

@ -593,19 +593,16 @@ def get_by_invitation_token(token, pass_token=None):
return helper.dict_to_camel_case(r)
def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud, jwt_jti=None):
extra_condition = ""
if jwt_jti is not None:
extra_condition = " AND jwt_jti = %(jwt_jti)s"
def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud):
with pg_client.PostgresClient() as cur:
cur.execute(
cur.mogrify(f"""SELECT user_id,jwt_iat
FROM public.users
cur.mogrify(f"""SELECT user_id,jwt_iat, changed_at
FROM public.users
INNER JOIN public.basic_authentication USING(user_id)
WHERE user_id = %(userId)s
AND deleted_at IS NULL
{extra_condition}
AND deleted_at IS NULL
LIMIT 1;""",
{"userId": user_id, "jwt_jti": jwt_jti})
{"userId": user_id})
)
r = cur.fetchone()
return r is not None \
@ -613,21 +610,18 @@ def auth_exists(user_id, tenant_id, jwt_iat, jwt_aud, jwt_jti=None):
and abs(jwt_iat - TimeUTC.datetime_to_timestamp(r["jwt_iat"]) // 1000) <= 1
def change_jwt_iat_jti(user_id):
def change_jwt_iat(user_id):
with pg_client.PostgresClient() as cur:
query = cur.mogrify(f"""UPDATE public.users
SET jwt_iat = timezone('utc'::text, now()),
jwt_refresh_jti = coalesce(jwt_refresh_jti,0) + 1,
jwt_refresh_iat = coalesce(jwt_refresh_iat,timezone('utc'::text, now()))
SET jwt_iat = timezone('utc'::text, now())
WHERE user_id = %(user_id)s
RETURNING jwt_iat, jwt_refresh_jti, jwt_refresh_iat;""",
RETURNING jwt_iat;""",
{"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 cur.fetchone().get("jwt_iat")
def authenticate(email, password, for_change_password=False) -> dict | bool | None:
def authenticate(email, password, for_change_password=False) -> dict | None:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT
@ -652,65 +646,17 @@ 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'])
jwt_iat = TimeUTC.datetime_to_timestamp(jwt_iat)
jwt_r_iat = TimeUTC.datetime_to_timestamp(jwt_r_iat)
jwt_iat = change_jwt_iat(r['userId'])
iat = TimeUTC.datetime_to_timestamp(jwt_iat)
return {
"jwt": authorizers.generate_jwt(user_id=r['userId'], tenant_id=r['tenantId'], iat=jwt_iat,
"jwt": authorizers.generate_jwt(r['userId'], r['tenantId'], iat=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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int),
"email": email,
**r
}
return None
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
WHERE user_id = %(user_id)s;""",
{"user_id": user_id})
cur.execute(query)
def refresh(user_id: int) -> dict:
with pg_client.PostgresClient() as cur:
query = cur.mogrify(
f"""SELECT
users.user_id,
1 AS tenant_id,
users.jwt_refresh_jti,
users.jwt_refresh_iat,
FROM public.users
WHERE users.user_id = %(user_id)s
AND deleted_at IS NULL
LIMIT 1;""",
{"user_id": user_id})
cur.execute(query)
r = cur.fetchone()
if r is not None:
r = helper.dict_to_camel_case(r)
jwt_iat, jwt_r_jti, jwt_r_iat = change_jwt_iat_jti(user_id=r['userId'])
jwt_iat = TimeUTC.datetime_to_timestamp(jwt_iat)
jwt_r_iat = TimeUTC.datetime_to_timestamp(jwt_r_iat)
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),
"refreshTokenMaxAge": config("JWT_REFRESH_EXPIRATION", cast=int)
}
return {}
def get_user_role(tenant_id, user_id):
with pg_client.PostgresClient() as cur:
cur.execute(

View file

@ -3,7 +3,7 @@ from typing import Optional, Union
from decouple import config
from fastapi import Body, Depends, BackgroundTasks
from fastapi import HTTPException, status
from starlette.responses import RedirectResponse, FileResponse, JSONResponse, Response
from starlette.responses import RedirectResponse, FileResponse
import schemas
from chalicelib.core import sessions, errors, errors_viewed, errors_favorite, sessions_assignments, heatmaps, \
@ -38,7 +38,7 @@ if not tenants.tenants_exists(use_pool=False):
@public_app.post('/login', tags=["authentication"])
def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)):
def login_user(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,
@ -58,38 +58,21 @@ def login_user(response: JSONResponse, data: schemas.UserLoginSchema = Body(...)
)
r["smtp"] = smtp.has_smtp()
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,
max_age=refresh_token_max_age, secure=True, httponly=True)
return response
return content
@app.get('/logout', tags=["login"])
def logout_user(response: Response, context: schemas.CurrentContext = Depends(OR_context)):
users.logout(user_id=context.user_id)
response.delete_cookie(key="refreshToken")
@app.get('/logout', tags=["login", "logout"])
def logout_user(context: schemas.CurrentContext = Depends(OR_context)):
return {"data": "success"}
@app.get('/refresh', tags=["login"])
def refresh_login(context: schemas.CurrentContext = Depends(OR_context)):
r = users.refresh(user_id=context.user_id)
content = {"jwt": r.get("jwt")}
response = JSONResponse(content=content)
response.set_cookie(key="refreshToken", value=r.get("refreshToken"),
max_age=r.pop("refreshTokenMaxAge"),
secure=True, httponly=True)
return response
@app.get('/account', tags=['accounts'])
def get_account(context: schemas.CurrentContext = Depends(OR_context)):
r = users.get(tenant_id=context.tenant_id, user_id=context.user_id)

View file

@ -67,10 +67,6 @@ CREATE TABLE IF NOT EXISTS public.sessions_feature_flags
condition_id integer NULL REFERENCES feature_flags_conditions (condition_id) ON DELETE SET NULL
);
ALTER TABLE IF EXISTS public.users
ADD COLUMN IF NOT EXISTS jwt_refresh_jti integer NULL DEFAULT NULL,
ADD COLUMN IF NOT EXISTS jwt_refresh_iat timestamp without time zone NULL DEFAULT NULL;
COMMIT;
\elif :is_next

View file

@ -129,18 +129,16 @@ $$
CREATE TABLE 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
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,
data jsonb NOT NULL DEFAULT '{}'::jsonb,
weekly_report boolean NOT NULL DEFAULT TRUE
);
CREATE TABLE basic_authentication