Skip to content

Commit 8b56c8f

Browse files
committed
readme
1 parent d9ea2bc commit 8b56c8f

File tree

3 files changed

+349
-0
lines changed

3 files changed

+349
-0
lines changed

README.md

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
## Contents
2+
- [Intro](#intro)
3+
- [Flowchart](#flowchart)
4+
- [Models](#models)
5+
- [User](#user)
6+
- [Device](#device)
7+
- [Backup Tokens](#backup-tokens)
8+
- [Endpoints](#endpoints)
9+
- [Auth](#auth)
10+
- [Two Factor Authentication](#two-factor-authentication)
11+
- [Users](#users)
12+
- [Tasks](#tasks)
13+
- [Crud](#crud)
14+
- [Core](#core)
15+
## Run
16+
- [Run from docker-compose](#run-from-docker-compose)
17+
18+
## Todo
19+
- [Todos](#todos)
20+
21+
22+
__________
23+
__________
24+
### **[Intro](#intro)**
25+
The application is an authentication microservice, developed with FastAPI framework, for login access via JWT token and possibility to enable two factor authentication (TFA).
26+
27+
**Two Factor Authentication**
28+
29+
TFA can be enabled choosing by two kind of devices:
30+
* email (OTP token will be send via email)
31+
* code generator (on activation a qr code is send to the user that, since than, will have the OTP token in any authenticator app like Microsoft or Google Authenticator apps)
32+
33+
It is only available the TOTP version of OTP validation protocol, based on timestamp and a private key for each user.
34+
35+
**Backup Tokens**
36+
37+
On TFA activation, independently from the chosen device, an email will be sent to the user with 5 backup tokens that can be used in case qr code was lost or it's somehow not possible to receive the token, these backup tokens are consumed every time one is used.
38+
39+
**Email service**
40+
41+
Email sending is faked by printing on stout the sent email text. It is anyway handled by a Celery consumer listening on a RabbitMQ message broker.
42+
43+
**Services**
44+
45+
The app run inside Docker, orchestrated by docker-compose. Database is Postgres, RabbitMq is used as message broker for Celery tasks and Redis is used as a backend for saving tasks results. It is also available a Flower instance to check task status via web dashboard.
46+
47+
| service | container name | listening port |
48+
|---|---|---|
49+
| **FastAPI** | fastapi_2fa | 5555 |
50+
| **PostgresDB** | fastapi_2fa-db | 5454 |
51+
| **Redis** | fastapi_2fa-cache | 6389 |
52+
| **RabbitMQ** | fastapi_2fa-rabbitmq | 15672 |
53+
| **Celery worker** | fastapi_2fa-celery | |
54+
| **Flower** | fastapi_2fa-flower | 5557 |
55+
56+
57+
58+
59+
### **[Flowchart](#flowchart)**
60+
61+
<img src="https://raw.githubusercontent.com/dfm88/fastapi-two-factor-authentication/main/docs/two_factor_flowchart.drawio.png" alt="image" style="width:600px;"/>
62+
63+
64+
### **[Models](#models)**
65+
66+
User model has a One to One relationship with Device model. Device model has a One to Many relationship with BackupTokens model.
67+
68+
#### [User](#user)
69+
70+
* `full_name` - user name
71+
* `email` - used for login
72+
* `hashed_password` - user hashed password
73+
* `tfa_enabled` - boolean field to determine if user has TFA enabled
74+
75+
#### [Device](#device)
76+
77+
* `user_id` - associated user
78+
* `key` - here is saved the encrypted version of the user OTP key
79+
* `device_type` - string value to determine if TFA device is of type `email` or `code_generator`
80+
81+
#### [Backup Tokens](#backup-tokens)
82+
83+
* `device_id` - associated device
84+
* `token` - random TOTP backup token
85+
86+
### **[Endpoints](#endpoints)**
87+
88+
Endpoints can be easily tested via FastAPI Swagger on route `/docs`. here are proposed the `curl` version to easily set JWT tokens
89+
90+
#### [Auth](#auth)
91+
92+
* `/api/v1/auth/signup`
93+
94+
**POST** - Creates a new user.
95+
96+
`device` key is required only if `tfa_enabled` is `true`;
97+
98+
`device_type` is one between `email` or `code_generator`
99+
100+
**body**
101+
102+
```json
103+
{
104+
"email": "[email protected]",
105+
"tfa_enabled": true,
106+
"full_name": "string",
107+
"password": "123456",
108+
"device": {
109+
"device_type": "email"
110+
}
111+
}
112+
```
113+
114+
**curl** (the `--output` option will save on your filesystem a valid qr-code image only if device type is `code_generator`)
115+
116+
```shell
117+
curl -X 'POST' \
118+
'http://localhost:5555/api/v1/auth/signup' \
119+
-H 'accept: application/json' \
120+
-H 'Content-Type: application/json' \
121+
-d '{
122+
"email": "[email protected]",
123+
"tfa_enabled": true,
124+
"full_name": "string",
125+
"password": "123456",
126+
"device": {
127+
"device_type": "email"
128+
}
129+
}' --output my_qrcode.png
130+
```
131+
132+
* `/api/v1/auth/login`
133+
134+
**POST** - Authenticates new user
135+
136+
**form data**
137+
138+
```
139+
Email
140+
Password
141+
```
142+
143+
**curl**
144+
145+
```shell
146+
curl -X 'POST' \
147+
'http://localhost:5555/api/v1/auth/login' \
148+
-H 'accept: application/json' \
149+
-H 'Content-Type: application/x-www-form-urlencoded' \
150+
-d 'grant_type=&username={{ USERNAME }}&password={{ PASSWORD }}&scope=&client_id=&client_secret='
151+
```
152+
153+
* `/api/v1/auth/test-token`
154+
155+
**GET** - Test authenticated endpoint to check if user is authenticated, JWT token must be set in `Authorization` header
156+
157+
**curl**
158+
159+
```shell
160+
curl -X 'GET' \
161+
'http://localhost:5555/api/v1/auth/test-token' \
162+
-H 'accept: application/json' \
163+
-H 'Authorization: Bearer {{ MY_ACCESS_JWT_TOKEN }}'
164+
```
165+
166+
* `/api/v1/auth/refresh`
167+
168+
**POST** - given a `REFRESH_JWT_TOKEN` returns a new `ACCESS_JWT_TOKEN`
169+
170+
**body**
171+
172+
```json
173+
{
174+
"refresh_token": "{{ MY_JWT_REFRESH_TOKEN }}"
175+
}
176+
```
177+
178+
**curl**
179+
180+
```shell
181+
curl -X 'POST' \
182+
'http://localhost:5555/api/v1/auth/refresh' \
183+
-H 'accept: application/json' \
184+
-H 'Content-Type: application/json' \
185+
-d '{
186+
"refresh_token": "{{ MY_JWT_REFRESH_TOKEN }}"
187+
}'
188+
```
189+
190+
191+
192+
#### [Two Factor Authentication](#two-factor-authentication)
193+
194+
* `/api/v1/tfa/login_tfa?tfa_token=`
195+
196+
**POST** - it's the second step after login for users with TFA enabled, it is necessary to have the TOTP token and to have the temporary access token in `Authorization` header returned by `login` endpoint for users with TFA enabled
197+
198+
**curl**
199+
200+
```shell
201+
curl -X 'POST' \
202+
'http://localhost:5555/api/v1/tfa/login_tfa?tfa_token={{ MY_TOTP_TOKEN }}' \
203+
-H 'accept: application/json' \
204+
-H 'Authorization: Bearer {{ MY_PRE_TFA_JWT_ACCESS_TOKEN }}' \
205+
-d ''
206+
```
207+
208+
* `/api/v1/tfa/recover_tfa?tfa_backup_token=`
209+
210+
**POST** - it's the second step after login for users with TFA enabled that can't receive/recover their TOTP token, so they can use one of the backup tokens sent in signup step. It is necessary to have the temporary access token in `Authorization` header returned by `login` endpoint for users with TFA enabled
211+
212+
**curl**
213+
214+
```shell
215+
curl -X 'POST' \
216+
'http://localhost:5555/api/v1/tfa/recover_tfa?tfa_backup_token={{ MY_BACKUP_TOTP_TOKEN }}' \
217+
-H 'accept: application/json' \
218+
-H 'Authorization: Bearer {{ MY_PRE_TFA_JWT_ACCESS_TOKEN }}' \
219+
-d ''
220+
```
221+
222+
* `/api/v1/tfa/get_my_qrcode`
223+
224+
**GET** - authenticated endpoint to receive again its own qr code (only for user with `code_generator` device)
225+
226+
**curl**
227+
228+
```shell
229+
curl -X 'GET' \
230+
'http://localhost:5555/api/v1/tfa/get_my_qrcode' \
231+
-H 'accept: application/json' \
232+
-H 'Authorization: Bearer {{ MY_JWT_ACCESS_TOKEN }}' --output my_recovered_qr_code.png
233+
```
234+
235+
* `/api/v1/tfa/enable_tfa`
236+
237+
**PUT** - enables TFA for authenticated users that didn't enable it on signup step. `device_type` is one between `email` or `code_generator`
238+
239+
**body**
240+
241+
```json
242+
{
243+
"device_type": "code_generator"
244+
}
245+
```
246+
247+
**curl** (the `--output` option will save on your filesystem a valid qr-code image only if device type is `code_generator`)
248+
249+
```shell
250+
curl -X 'PUT' \
251+
'http://localhost:5555/api/v1/tfa/enable_tfa' \
252+
-H 'accept: application/json' \
253+
-H 'Authorization: Bearer {{ MY_JWT_ACCESS_TOKEN }}' \
254+
-H 'Content-Type: application/json' \
255+
-d '{
256+
"device_type": "code_generator"
257+
}' --output my_new_qr_code.png
258+
```
259+
260+
261+
#### [Users](#users)
262+
263+
* `/api/v1/users/users`
264+
265+
**GET** - test non-authenticated endpoint to get all users in DB
266+
267+
**curl**
268+
269+
```shell
270+
curl -X 'GET' \
271+
'http://localhost:5555/api/v1/users/users' \
272+
-H 'accept: application/json'
273+
```
274+
275+
#### [Tasks](#tasks)
276+
277+
* `/api/v1/tasks/test-celery`
278+
279+
**GET** - endpoint to test celery send mail function
280+
281+
**curl**
282+
283+
```shell
284+
curl -X 'GET' \
285+
'http://localhost:5555/api/v1/tasks/test-celery' \
286+
-H 'accept: application/json'
287+
```
288+
289+
* `/api/v1/tasks/taskstatus?task_id=`
290+
291+
**GET** - endpoint to retrieve task status. `?task_id` is returned by ``/api/v1/tasks/test-celery` endpoint
292+
293+
**curl**
294+
295+
```shell
296+
curl -X 'GET' \
297+
'http://localhost:5555/api/v1/tasks/taskstatus?task_id={{ TASK_ID }}' \
298+
-H 'accept: application/json'
299+
```
300+
301+
### **[Crud](#crud)**
302+
The modules inside this package are responsible to query the DB
303+
304+
### **[Core](#core)**
305+
The modules inside this package are responsible to handle the core business logic of the application
306+
307+
__________
308+
__________
309+
310+
## [Run from docker-compose](#run-from-docker-compose)
311+
312+
To run all the services, from the application root run:
313+
314+
```shell
315+
docker-compose -f docker/docker-compose.yaml up
316+
```
317+
318+
The FastAPI server is exposed on port `5555`, FastAPI swagger is available at http://localhost:5555/docs#/
319+
320+
---
321+
322+
To follow only FastAPI logs, from another terminal, run:
323+
```shell
324+
docker logs --tail 200 -f fastapi_2fa
325+
```
326+
327+
---
328+
329+
To run tests, from another terminal, run:
330+
```shell
331+
docker exec fastapi_2fa pytest --cov-report term --cov=fastapi_2fa tests/
332+
```
333+
334+
__________
335+
__________
336+
337+
## [Todos](#todos)
338+
339+
* reset / recover password
340+
* encrypt backup tokens
341+
* throttling for failed login
342+
* complete tests
343+
* logging

docker/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ COPY --from=build /${IMAGE_NAME}/venv /${IMAGE_NAME}/venv/
7171
# add venv to PATH
7272
ENV PATH /${IMAGE_NAME}/venv/bin:$PATH
7373

74+
RUN apt update && apt install -y vim
75+
7476
# copying codebase
7577
WORKDIR /${IMAGE_NAME}
7678
COPY . ./

docker/docker-compose.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services:
99
image: fastapi_2fa
1010
container_name: fastapi_2fa
1111
entrypoint: ./scripts/entrypoint.sh
12+
restart: always
1213
# stdin_open: true
1314
# tty: true
1415
ports:
@@ -56,12 +57,14 @@ services:
5657
image: rabbitmq:3-management
5758
env_file:
5859
- ../env/.env
60+
restart: always
5961
# environment:
6062
# - SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://admin:admin@fastapi_2fa-db:5432/postgres
6163
# - CELERY_BROKER_URL=amqp://admin:admin@fastapi_2fa-rabbitmq:5672/
6264
# - result_backend=redis://fastapi_2fa-cache:6379/0
6365
ports:
6466
- "5672:5672"
67+
- "15672:15672"
6568

6669
celery_worker:
6770
container_name: fastapi_2fa-celery
@@ -72,6 +75,7 @@ services:
7275
entrypoint: ./scripts/start_celery_worker.sh
7376
env_file:
7477
- ../env/.env
78+
restart: always
7579
# environment:
7680
# - SQLALCHEMY_DATABASE_URI=postgresql+asyncpg://admin:admin@fastapi_2fa-db:5432/postgres
7781
# - CELERY_BROKER_URL=amqp://admin:admin@fastapi_2fa-rabbitmq:5672/

0 commit comments

Comments
 (0)