Skip to content

Commit b3671f6

Browse files
committed
Small refactoring, wip async, docs improve
1 parent 1f5b1a9 commit b3671f6

File tree

8 files changed

+113
-49
lines changed

8 files changed

+113
-49
lines changed

docs/docs/developing.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
Install dev dependencies
44
```shell
5-
python -m pip install .[docs] # \[docs\] in zsh
5+
python -m pip install .[docs,test] # \[docs,test\] in zsh
66
```
77

88

@@ -69,11 +69,11 @@ Edit it in `docs/`
6969
mkdocs serve --config-file docs/mkdocs.yml
7070
```
7171

72-
Note: Server will auto-restart for all changed `docs/*` files.
72+
Note: Server will auto-restart for all changed `docs/*` files.
7373
If you want to edit `README.md` or `CONTRIBUTING.md` you should restart server on each change.
7474

7575

76-
### Building (`TODO`)
76+
### Building pkg docs (`TODO`)
7777

7878
Add python backend docs `TODO`
7979
```shell

docs/docs/user_guide/classes.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Classes
2+
3+
This library made in fastapi style, so it can be used as standard security features
4+
5+
6+
## Security classes
7+
8+
#### Credentials
9+
10+
* `JwtAuthorizationCredentials` - universal credentials for access and refresh tokens.
11+
Provide access to subject and unique token identifier (jti)
12+
```python
13+
def foo(credentials: JwtAuthorizationCredentials = Security(access_security)):
14+
return credentials["username"], credentials.jti
15+
```
16+
17+
#### Access tokens
18+
19+
* `JwtAccessBearer` - read access token from bearer header only
20+
* `JwtAccessCookie` - read access token from cookies only
21+
* `JwtAccessBearerCookie` - read access token from both bearer and cookie
22+
23+
#### Refresh tokens
24+
25+
* `JwtRefreshBearer` - read access token from bearer header only
26+
* `JwtRefreshCookie` - read access token from cookies only
27+
* `JwtRefreshBearerCookie` - read access token from both bearer and cookie
28+
29+
30+
### Create
31+
32+
You can create `access_security` / `refresh_security` in multiple ways
33+
```python
34+
# Manually
35+
access_security = JwtAccessBearerCookie(
36+
secret_key="other_secret_key",
37+
auto_error=True,
38+
access_expires_delta=timedelta(hours=1), # custom access token valid timedelta
39+
refresh_expires_delta=timedelta(days=1), # custom access token valid timedelta
40+
)
41+
42+
# Create from another object, copy all params
43+
refresh_security = JwtRefreshBearer.from_other(access_security)
44+
45+
# Create from another object, rewrite some params
46+
other_access_security = JwtAccessCookie.from_other(
47+
access_security,
48+
secret_key='!key!',
49+
auto_error=False
50+
)
51+
```
52+
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
# User guide
1+
# Examples
22

33
This library made in fastapi style, so it can be used as standard security features
44

55

6-
## Example
6+
## Full Example
77

88

99
```python

docs/mkdocs.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ edit_uri: ""
1616

1717
nav:
1818
- Home: index.md
19-
- User Guide: user_guide.md
19+
- User Guide:
20+
- Classes: user_guide/classes.md
21+
- Examples: user_guide/examples.md
2022
- Developing: developing.md
2123
- Release notes: release-notes.md
2224

fastapi_jwt/jwt.py

Lines changed: 45 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from abc import ABC
22
from datetime import datetime, timedelta
33
from typing import Any, Dict, Optional, Set
4-
from uuid import uuid1
4+
from uuid import uuid4
55

66
from fastapi.exceptions import HTTPException
77
from fastapi.param_functions import Security
@@ -83,6 +83,24 @@ def __init__(
8383
self.access_expires_delta = access_expires_delta or timedelta(minutes=15)
8484
self.refresh_expires_delta = refresh_expires_delta or timedelta(days=31)
8585

86+
@classmethod
87+
def from_other(
88+
cls,
89+
other: 'JwtAuthBase',
90+
secret_key: Optional[str] = None,
91+
auto_error: Optional[bool] = None,
92+
algorithm: Optional[str] = None,
93+
access_expires_delta: Optional[timedelta] = None,
94+
refresh_expires_delta: Optional[timedelta] = None,
95+
) -> 'JwtAuthBase':
96+
return cls(
97+
secret_key=secret_key or other.secret_key,
98+
auto_error=auto_error or other.auto_error,
99+
algorithm=algorithm or other.algorithm,
100+
access_expires_delta=access_expires_delta or other.access_expires_delta,
101+
refresh_expires_delta=refresh_expires_delta or other.refresh_expires_delta,
102+
)
103+
86104
def _decode(self, token: str) -> Optional[Dict[str, Any]]:
87105
try:
88106
payload: Dict[str, Any] = jwt.decode(
@@ -124,24 +142,17 @@ def _generate_payload(
124142
"jti": unique_identifier, # uuid
125143
}
126144

127-
def _get_token(
128-
self,
129-
bearer: Optional[HTTPBearer] = None,
130-
cookie: Optional[APIKeyCookie] = None,
131-
) -> Optional[str]:
132-
if bearer:
133-
return str(bearer.credentials) # type: ignore
134-
if cookie:
135-
return str(cookie)
136-
return None
137-
138-
def _get_payload(
145+
async def _get_payload(
139146
self, bearer: Optional[HTTPBearer], cookie: Optional[APIKeyCookie]
140147
) -> Optional[Dict[str, Any]]:
141-
refresh_token = self._get_token(bearer, cookie) # TODO: del function
148+
token: Optional[str] = None
149+
if bearer:
150+
token = str(bearer.credentials) # type: ignore
151+
elif cookie:
152+
token = str(cookie)
142153

143154
# Check token exist
144-
if refresh_token is None:
155+
if not token:
145156
if self.auto_error:
146157
raise HTTPException(
147158
status_code=HTTP_401_UNAUTHORIZED, detail="Credentials are not provided"
@@ -150,7 +161,7 @@ def _get_payload(
150161
return None
151162

152163
# Try to decode jwt token. auto_error on error
153-
payload = self._decode(refresh_token)
164+
payload = self._decode(token)
154165
return payload
155166

156167
def create_access_token(
@@ -160,7 +171,7 @@ def create_access_token(
160171
unique_identifier: Optional[str] = None,
161172
) -> str:
162173
expires_delta = expires_delta or self.access_expires_delta
163-
unique_identifier = unique_identifier or str(uuid1())
174+
unique_identifier = unique_identifier or str(uuid4())
164175
to_encode = self._generate_payload(
165176
subject, expires_delta, unique_identifier, "access"
166177
)
@@ -177,7 +188,7 @@ def create_refresh_token(
177188
unique_identifier: Optional[str] = None,
178189
) -> str:
179190
expires_delta = expires_delta or self.refresh_expires_delta
180-
unique_identifier = unique_identifier or str(uuid1())
191+
unique_identifier = unique_identifier or str(uuid4())
181192
to_encode = self._generate_payload(
182193
subject, expires_delta, unique_identifier, "refresh"
183194
)
@@ -252,12 +263,12 @@ def __init__(
252263
refresh_expires_delta=refresh_expires_delta,
253264
)
254265

255-
def _get_credentials(
266+
async def _get_credentials(
256267
self,
257268
bearer: Optional[JwtAuthBase.JwtAccessBearer],
258269
cookie: Optional[JwtAuthBase.JwtAccessCookie],
259270
) -> Optional[JwtAuthorizationCredentials]:
260-
payload = self._get_payload(bearer, cookie)
271+
payload = await self._get_payload(bearer, cookie)
261272

262273
if payload:
263274
return JwtAuthorizationCredentials(
@@ -284,10 +295,10 @@ def __init__(
284295
refresh_expires_delta=refresh_expires_delta,
285296
)
286297

287-
def __call__(
298+
async def __call__(
288299
self, bearer: JwtAuthBase.JwtAccessBearer = Security(JwtAccess._bearer)
289300
) -> Optional[JwtAuthorizationCredentials]:
290-
return self._get_credentials(bearer=bearer, cookie=None)
301+
return await self._get_credentials(bearer=bearer, cookie=None)
291302

292303

293304
class JwtAccessCookie(JwtAccess):
@@ -308,11 +319,11 @@ def __init__(
308319
refresh_expires_delta=refresh_expires_delta,
309320
)
310321

311-
def __call__(
322+
async def __call__(
312323
self,
313324
cookie: JwtAuthBase.JwtAccessCookie = Security(JwtAccess._cookie),
314325
) -> Optional[JwtAuthorizationCredentials]:
315-
return self._get_credentials(bearer=None, cookie=cookie)
326+
return await self._get_credentials(bearer=None, cookie=cookie)
316327

317328

318329
class JwtAccessBearerCookie(JwtAccess):
@@ -333,12 +344,12 @@ def __init__(
333344
refresh_expires_delta=refresh_expires_delta,
334345
)
335346

336-
def __call__(
347+
async def __call__(
337348
self,
338349
bearer: JwtAuthBase.JwtAccessBearer = Security(JwtAccess._bearer),
339350
cookie: JwtAuthBase.JwtAccessCookie = Security(JwtAccess._cookie),
340351
) -> Optional[JwtAuthorizationCredentials]:
341-
return self._get_credentials(bearer=bearer, cookie=cookie)
352+
return await self._get_credentials(bearer=bearer, cookie=cookie)
342353

343354

344355
class JwtRefresh(JwtAuthBase):
@@ -363,12 +374,12 @@ def __init__(
363374
refresh_expires_delta=refresh_expires_delta,
364375
)
365376

366-
def _get_credentials(
377+
async def _get_credentials(
367378
self,
368379
bearer: Optional[JwtAuthBase.JwtRefreshBearer],
369380
cookie: Optional[JwtAuthBase.JwtRefreshCookie],
370381
) -> Optional[JwtAuthorizationCredentials]:
371-
payload = self._get_payload(bearer, cookie)
382+
payload = await self._get_payload(bearer, cookie)
372383

373384
if payload is None:
374385
return None
@@ -405,10 +416,10 @@ def __init__(
405416
refresh_expires_delta=refresh_expires_delta,
406417
)
407418

408-
def __call__(
419+
async def __call__(
409420
self, bearer: JwtAuthBase.JwtRefreshBearer = Security(JwtRefresh._bearer)
410421
) -> Optional[JwtAuthorizationCredentials]:
411-
return self._get_credentials(bearer=bearer, cookie=None)
422+
return await self._get_credentials(bearer=bearer, cookie=None)
412423

413424

414425
class JwtRefreshCookie(JwtRefresh):
@@ -429,11 +440,11 @@ def __init__(
429440
refresh_expires_delta=refresh_expires_delta,
430441
)
431442

432-
def __call__(
443+
async def __call__(
433444
self,
434445
cookie: JwtAuthBase.JwtRefreshCookie = Security(JwtRefresh._cookie),
435446
) -> Optional[JwtAuthorizationCredentials]:
436-
return self._get_credentials(bearer=None, cookie=cookie)
447+
return await self._get_credentials(bearer=None, cookie=cookie)
437448

438449

439450
class JwtRefreshBearerCookie(JwtRefresh):
@@ -454,9 +465,9 @@ def __init__(
454465
refresh_expires_delta=refresh_expires_delta,
455466
)
456467

457-
def __call__(
468+
async def __call__(
458469
self,
459470
bearer: JwtAuthBase.JwtRefreshBearer = Security(JwtRefresh._bearer),
460471
cookie: JwtAuthBase.JwtRefreshCookie = Security(JwtRefresh._cookie),
461472
) -> Optional[JwtAuthorizationCredentials]:
462-
return self._get_credentials(bearer=bearer, cookie=cookie)
473+
return await self._get_credentials(bearer=bearer, cookie=cookie)

setup.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ allow_untyped_decorators = True
9696
[tool:pytest]
9797
testpaths = tests/
9898
python_files = test_*.py
99-
timeout = 180
10099
addopts = --cov=fastapi_jwt/ --cov-report term-missing
101100

102101
[isort]

tests/test_security_jwt_general.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from uuid import uuid1
2+
from uuid import uuid4
33
from typing import Set
44

55
from fastapi import FastAPI, Security
@@ -11,7 +11,7 @@
1111
app = FastAPI()
1212

1313
access_security = JwtAccessBearer(secret_key="secret_key")
14-
refresh_security = JwtRefreshBearer(secret_key="secret_key")
14+
refresh_security = JwtRefreshBearer.from_other(access_security)
1515

1616

1717
unique_identifiers_database: Set[str] = set()
@@ -20,7 +20,7 @@
2020
@app.post("/auth")
2121
def auth():
2222
subject = {"username": "username", "role": "user"}
23-
unique_identifier = str(uuid1())
23+
unique_identifier = str(uuid4())
2424
unique_identifiers_database.add(unique_identifier)
2525

2626
access_token = access_security.create_access_token(
@@ -33,7 +33,7 @@ def auth():
3333

3434
@app.post("/refresh")
3535
def refresh(credentials: JwtAuthorizationCredentials = Security(refresh_security)):
36-
unique_identifier = str(uuid1())
36+
unique_identifier = str(uuid4())
3737
unique_identifiers_database.add(unique_identifier)
3838

3939
access_token = refresh_security.create_access_token(

tests/test_security_jwt_general_optional.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import datetime
2-
from uuid import uuid1
2+
from uuid import uuid4
33
from typing import Optional, Set
44

55
from fastapi import FastAPI, Security
@@ -11,7 +11,7 @@
1111
app = FastAPI()
1212

1313
access_security = JwtAccessBearer(secret_key="secret_key", auto_error=False)
14-
refresh_security = JwtRefreshBearer(secret_key="secret_key", auto_error=False)
14+
refresh_security = JwtRefreshBearer.from_other(access_security)
1515

1616

1717
unique_identifiers_database: Set[str] = set()
@@ -20,7 +20,7 @@
2020
@app.post("/auth")
2121
def auth():
2222
subject = {"username": "username", "role": "user"}
23-
unique_identifier = str(uuid1())
23+
unique_identifier = str(uuid4())
2424
unique_identifiers_database.add(unique_identifier)
2525

2626
access_token = access_security.create_access_token(
@@ -38,7 +38,7 @@ def refresh(
3838
if credentials is None:
3939
return {"msg": "Create an account first"}
4040

41-
unique_identifier = str(uuid1())
41+
unique_identifier = str(uuid4())
4242
unique_identifiers_database.add(unique_identifier)
4343

4444
access_token = refresh_security.create_access_token(

0 commit comments

Comments
 (0)