Skip to content

Commit

Permalink
feat(app):
Browse files Browse the repository at this point in the history
- refresh token add user id verification
  • Loading branch information
MorvanZhou committed May 11, 2024
1 parent 697e56c commit 9f312f5
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/retk/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from cryptography.hazmat.primitives.asymmetric import rsa
from pydantic import Field, DirectoryPath
from pydantic_settings import BaseSettings, SettingsConfigDict

from retk.logger import logger


Expand Down Expand Up @@ -84,7 +85,6 @@ def __init__(self):
self.REFRESH_TOKEN_EXPIRE_DELTA = datetime.timedelta(days=self.JWT_REFRESH_EXPIRED_DAYS)
self.ACCESS_TOKEN_EXPIRE_DELTA = datetime.timedelta(minutes=self.JWT_ACCESS_EXPIRED_MINS)


@lru_cache()
def get_settings() -> Settings:
return Settings()
Expand Down
1 change: 1 addition & 0 deletions src/retk/controllers/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ async def get_new_access_token(
access_token = jwt_encode(
exp_delta=config.get_settings().ACCESS_TOKEN_EXPIRE_DELTA,
data={
"is_access": True,
"uid": au.u.id,
"language": au.language,
},
Expand Down
1 change: 1 addition & 0 deletions src/retk/controllers/schemas/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Settings(BaseModel):
settings: Settings

requestId: str
uid: str
user: User = None


Expand Down
4 changes: 3 additions & 1 deletion src/retk/controllers/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ async def get_user(
max_space = const.USER_TYPE.id2config(au.u.type).max_store_space
return schemas.user.UserInfoResponse(
requestId=au.request_id,
uid=au.u.id,
user=schemas.user.UserInfoResponse.User(
email=_email,
nickname=au.u.nickname,
Expand Down Expand Up @@ -61,6 +62,7 @@ async def patch_user(
u_settings = u["settings"]
return schemas.user.UserInfoResponse(
requestId=au.request_id,
uid=u["id"],
user=schemas.user.UserInfoResponse.User(
email=u["email"],
nickname=u["nickname"],
Expand Down Expand Up @@ -113,7 +115,7 @@ async def update_password(
async def get_notifications(
au: AuthedUser,
) -> schemas.user.NotificationResponse:
notifications = await core.user.get_notifications(uid=au.u.id)
notifications = []
return schemas.user.NotificationResponse(
requestId=au.request_id,
notifications=notifications,
Expand Down
27 changes: 20 additions & 7 deletions src/retk/routes/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def verify_referer(referer: Optional[str] = Header(None)):


async def __process_auth_headers(
is_refresh_token: bool,
refresh_token_id: str,
token: str = Header(alias="Authorization", default=""),
request_id: str = Header(
default="", alias="RequestId", max_length=const.settings.MD_MAX_LENGTH
Expand All @@ -82,11 +82,23 @@ async def __process_auth_headers(
u = None
try:
payload = jwt_decode(token=token)
u, code = await core.user.get(uid=payload["uid"])
if code != const.Code.OK:
err = f"get user failed, code={code}"
is_access = payload.get("is_access")
if is_access is None:
code = const.Code.INVALID_AUTH
err = "invalid token"
elif (is_access and refresh_token_id != "") or (not is_access and refresh_token_id == ""):
code = const.Code.INVALID_AUTH
err = "invalid token"
elif refresh_token_id != "" and payload["uid"] != refresh_token_id:
code = const.Code.INVALID_AUTH
err = "invalid token"
else:
u, code = await core.user.get(uid=payload["uid"])
if code != const.Code.OK:
err = f"get user failed, code={code}"

except jwt.exceptions.ExpiredSignatureError:
code = const.Code.EXPIRED_AUTH if is_refresh_token else const.Code.EXPIRED_ACCESS_TOKEN
code = const.Code.EXPIRED_AUTH if refresh_token_id != "" else const.Code.EXPIRED_ACCESS_TOKEN
err = "auth expired"
except jwt.exceptions.DecodeError:
code = const.Code.INVALID_AUTH
Expand Down Expand Up @@ -114,16 +126,17 @@ async def process_normal_headers(
default="", alias="RequestId", max_length=const.settings.MD_MAX_LENGTH
)
) -> AuthedUser:
return await __process_auth_headers(is_refresh_token=False, token=token, request_id=request_id)
return await __process_auth_headers(refresh_token_id="", token=token, request_id=request_id)


async def process_refresh_token_headers(
token: str = Header(alias="Authorization", default=""),
id_: str = Header(alias="ID", default=""),
request_id: str = Header(
default="", alias="RequestId", max_length=const.settings.MD_MAX_LENGTH
)
) -> AuthedUser:
return await __process_auth_headers(is_refresh_token=True, token=token, request_id=request_id)
return await __process_auth_headers(refresh_token_id=id_, token=token, request_id=request_id)


async def process_no_auth_headers(
Expand Down
18 changes: 11 additions & 7 deletions src/retk/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import jwt
from bson import ObjectId
from markdown import Markdown

from retk import config, const, regex
from retk.logger import logger
from retk.models import tps
Expand Down Expand Up @@ -463,17 +464,20 @@ async def get_latest_version() -> Tuple[Tuple[int, int, int], const.Code]:

def get_token(uid: str, language: str) -> Tuple[str, str]:
settings = config.get_settings()
data = {
"uid": uid,
"language": language,
}

access_token = jwt_encode(
exp_delta=settings.ACCESS_TOKEN_EXPIRE_DELTA,
data=data,
data={
"is_access": True,
"uid": uid,
"language": language,
},
)
refresh_token = jwt_encode(
exp_delta=settings.REFRESH_TOKEN_EXPIRE_DELTA,
data=data,
data={
"is_access": False,
"uid": uid,
"language": language,
},
)
return access_token, refresh_token
45 changes: 42 additions & 3 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,20 @@ async def asyncSetUp(self) -> None:
})
self.assertEqual(200, resp.status_code)
rj = resp.json()
self.assertEqual(818, len(rj["accessToken"]))
self.assertEqual(840, len(rj["accessToken"]))
self.assertTrue(rj["accessToken"].startswith("Bearer "))
self.refresh_token = rj["refreshToken"]
self.default_headers = {
"Authorization": rj["accessToken"],
"RequestId": "xxx",
}
resp = self.client.get(
"/api/users",
headers=self.default_headers
)
self.assertEqual(200, resp.status_code, msg=resp.json())
rj = resp.json()
self.uid = rj["uid"]

async def asyncTearDown(self) -> None:
await client.drop()
Expand All @@ -138,6 +145,35 @@ def check_ok_response(self, resp: Response, status_code: int = 200, rid="xxx") -
self.assertEqual(rid, rj["requestId"])
return rj

async def test_access_refresh_token(self):
resp = self.client.put(
"/api/account/login",
json={
"email": const.DEFAULT_USER["email"],
"password": "",
})
rj = resp.json()
access_token = rj["accessToken"]
refresh_token = rj["refreshToken"]

resp = self.client.get(
"/api/users",
headers={
"Authorization": access_token,
"RequestId": "xxx"
}
)
self.assertEqual(200, resp.status_code)

resp = self.client.get(
"/api/users",
headers={
"Authorization": refresh_token,
"RequestId": "xxx"
}
)
self.error_check(resp, 401, const.Code.INVALID_AUTH)

async def test_access_token_expire(self):
aed = config.get_settings().ACCESS_TOKEN_EXPIRE_DELTA
red = config.get_settings().REFRESH_TOKEN_EXPIRE_DELTA
Expand Down Expand Up @@ -172,7 +208,8 @@ async def test_access_token_expire(self):
"/api/account/access-token",
headers={
"Authorization": refresh_token,
"RequestId": "xxx"
"RequestId": "xxx",
"ID": self.uid,
}
)
self.error_check(resp, 401, const.Code.EXPIRED_AUTH)
Expand Down Expand Up @@ -206,7 +243,8 @@ async def test_access_token_expire(self):
"/api/account/access-token",
headers={
"Authorization": refresh_token,
"RequestId": "xxx"
"RequestId": "xxx",
"ID": self.uid,
}
)
rj = self.check_ok_response(resp, 200)
Expand All @@ -222,6 +260,7 @@ async def test_access_token_expire(self):
}
)
self.check_ok_response(resp, 200)
self.assertEqual(self.uid, resp.json()["uid"])

async def test_add_user_update_password(self):
config.get_settings().ONE_USER = False
Expand Down

0 comments on commit 9f312f5

Please sign in to comment.