Skip to content

Commit 3445755

Browse files
committed
Backend: Día 4
1 parent eb92a3b commit 3445755

File tree

10 files changed

+203
-2
lines changed

10 files changed

+203
-2
lines changed
124 Bytes
Binary file not shown.

Backend/FastAPI/main.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@
88

99
from fastapi import FastAPI
1010
from routers import products, users
11+
from fastapi.staticfiles import StaticFiles
1112

1213
app = FastAPI()
1314

1415
# Routers - Clase en vídeo (08/12/2022): https://www.twitch.tv/videos/1673759045
1516
app.include_router(products.router)
1617
app.include_router(users.router)
1718

19+
# Recursos estáticos - Clase en vídeo (14/12/2022): https://www.twitch.tv/videos/1679022882
20+
app.mount("/static", StaticFiles(directory="static"), name="static")
21+
1822

1923
# Url local: http://127.0.0.1:8000
2024

2.32 KB
Binary file not shown.
3.13 KB
Binary file not shown.
0 Bytes
Binary file not shown.
0 Bytes
Binary file not shown.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# Clase en vídeo (14/12/2022): https://www.twitch.tv/videos/1679022882
2+
3+
### Users API con autorización OAuth2 básica ###
4+
5+
from fastapi import FastAPI, Depends, HTTPException, status
6+
from pydantic import BaseModel
7+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
8+
9+
app = FastAPI()
10+
11+
oauth2 = OAuth2PasswordBearer(tokenUrl="login")
12+
13+
14+
class User(BaseModel):
15+
username: str
16+
full_name: str
17+
email: str
18+
disabled: bool
19+
20+
21+
class UserDB(User):
22+
password: str
23+
24+
25+
users_db = {
26+
"mouredev": {
27+
"username": "mouredev",
28+
"full_name": "Brais Moure",
29+
"email": "braismoure@mourede.com",
30+
"disabled": False,
31+
"password": "123456"
32+
},
33+
"mouredev2": {
34+
"username": "mouredev2",
35+
"full_name": "Brais Moure 2",
36+
"email": "braismoure2@mourede.com",
37+
"disabled": True,
38+
"password": "654321"
39+
}
40+
}
41+
42+
43+
def search_user_db(username: str):
44+
if username in users_db:
45+
return UserDB(**users_db[username])
46+
47+
48+
def search_user(username: str):
49+
if username in users_db:
50+
return User(**users_db[username])
51+
52+
53+
async def current_user(token: str = Depends(oauth2)):
54+
user = search_user(token)
55+
if not user:
56+
raise HTTPException(
57+
status_code=status.HTTP_401_UNAUTHORIZED,
58+
detail="Credenciales de autenticación inválidas",
59+
headers={"WWW-Authenticate": "Bearer"})
60+
61+
if user.disabled:
62+
raise HTTPException(
63+
status_code=status.HTTP_400_BAD_REQUEST,
64+
detail="Usuario inactivo")
65+
66+
return user
67+
68+
69+
@app.post("/login")
70+
async def login(form: OAuth2PasswordRequestForm = Depends()):
71+
user_db = users_db.get(form.username)
72+
if not user_db:
73+
raise HTTPException(
74+
status_code=status.HTTP_400_BAD_REQUEST, detail="El usuario no es correcto")
75+
76+
user = search_user_db(form.username)
77+
if not form.password == user.password:
78+
raise HTTPException(
79+
status_code=status.HTTP_400_BAD_REQUEST, detail="La contraseña no es correcta")
80+
81+
return {"access_token": user.username, "token_type": "bearer"}
82+
83+
84+
@app.get("/users/me")
85+
async def me(user: User = Depends(current_user)):
86+
return user
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Clase en vídeo (14/12/2022): https://www.twitch.tv/videos/1679022882
2+
3+
### Users API con autorización OAuth2 JWT ###
4+
5+
from fastapi import FastAPI, Depends, HTTPException, status
6+
from pydantic import BaseModel
7+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
8+
from jose import jwt, JWTError
9+
from passlib.context import CryptContext
10+
from datetime import datetime, timedelta
11+
12+
ALGORITHM = "HS256"
13+
ACCESS_TOKEN_DURATION = 1
14+
SECRET = "201d573bd7d1344d3a3bfce1550b69102fd11be3db6d379508b6cccc58ea230b"
15+
16+
app = FastAPI()
17+
18+
oauth2 = OAuth2PasswordBearer(tokenUrl="login")
19+
20+
crypt = CryptContext(schemes=["bcrypt"])
21+
22+
23+
class User(BaseModel):
24+
username: str
25+
full_name: str
26+
email: str
27+
disabled: bool
28+
29+
30+
class UserDB(User):
31+
password: str
32+
33+
34+
users_db = {
35+
"mouredev": {
36+
"username": "mouredev",
37+
"full_name": "Brais Moure",
38+
"email": "braismoure@mourede.com",
39+
"disabled": False,
40+
"password": "$2a$12$B2Gq.Dps1WYf2t57eiIKjO4DXC3IUMUXISJF62bSRiFfqMdOI2Xa6"
41+
},
42+
"mouredev2": {
43+
"username": "mouredev2",
44+
"full_name": "Brais Moure 2",
45+
"email": "braismoure2@mourede.com",
46+
"disabled": True,
47+
"password": "$2a$12$SduE7dE.i3/ygwd0Kol8bOFvEABaoOOlC8JsCSr6wpwB4zl5STU4S"
48+
}
49+
}
50+
51+
52+
def search_user_db(username: str):
53+
if username in users_db:
54+
return UserDB(**users_db[username])
55+
56+
57+
def search_user(username: str):
58+
if username in users_db:
59+
return User(**users_db[username])
60+
61+
62+
async def auth_user(token: str = Depends(oauth2)):
63+
64+
exception = HTTPException(
65+
status_code=status.HTTP_401_UNAUTHORIZED,
66+
detail="Credenciales de autenticación inválidas",
67+
headers={"WWW-Authenticate": "Bearer"})
68+
69+
try:
70+
username = jwt.decode(token, SECRET, algorithms=[ALGORITHM]).get("sub")
71+
if username is None:
72+
raise exception
73+
74+
except JWTError:
75+
raise exception
76+
77+
return search_user(username)
78+
79+
80+
async def current_user(user: User = Depends(auth_user)):
81+
if user.disabled:
82+
raise HTTPException(
83+
status_code=status.HTTP_400_BAD_REQUEST,
84+
detail="Usuario inactivo")
85+
86+
return user
87+
88+
89+
@app.post("/login")
90+
async def login(form: OAuth2PasswordRequestForm = Depends()):
91+
user_db = users_db.get(form.username)
92+
if not user_db:
93+
raise HTTPException(
94+
status_code=status.HTTP_400_BAD_REQUEST, detail="El usuario no es correcto")
95+
96+
user = search_user_db(form.username)
97+
98+
if not crypt.verify(form.password, user.password):
99+
raise HTTPException(
100+
status_code=status.HTTP_400_BAD_REQUEST, detail="La contraseña no es correcta")
101+
102+
access_token = {"sub": user.username,
103+
"exp": datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_DURATION)}
104+
105+
return {"access_token": jwt.encode(access_token, SECRET, algorithm=ALGORITHM), "token_type": "bearer"}
106+
107+
108+
@app.get("/users/me")
109+
async def me(user: User = Depends(current_user)):
110+
return user
4.37 MB
Loading

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
> * Base de datos
2121
> * Despliegue en servidor
2222
>
23-
> **🔴 SIGUIENTE CLASE: Miércoles 14 de Diciembre a las 20:00 (hora España)**
23+
> **🔴 SIGUIENTE CLASE: Jueves 22 de Diciembre a las 20:00 (hora España)**
2424
25-
> 🗓 En [Discord](https://discord.gg/mouredev) tienes creado un [evento](https://discord.gg/mouredev?event=1051412181721305158) para que consultes la hora de tu país y añadas un recordatorio.
25+
> 🗓 En [Discord](https://discord.gg/mouredev) tienes creado un [evento](https://discord.gg/mouredev?event=1052999245826883626) para que consultes la hora de tu país y añadas un recordatorio.
2626
>
2727
> Mientras, aprovecha para practicar unos [retos de programación](https://retosdeprogramacion.com/semanales2022) y así ir mejorando poco a poco.
2828
>
@@ -41,6 +41,7 @@ Curso en el que aprenderemos a utilizar Python para backend e implementaremos un
4141
* [Clase 1 - 24/11/2022 - Hola Mundo en FastAPI](https://www.twitch.tv/videos/1661716599)
4242
* [Clase 2 - 01/12/2022 - Operaciones con GET y peticiones HTTP](https://www.twitch.tv/videos/1667582141)
4343
* [Clase 3 - 08/12/2022 - Operaciones con POST, PUT, DELETE, códigos HTTP y Routers](https://www.twitch.tv/videos/1673759045)
44+
* [Clase 4 - 14/12/2022 - Recursos estáticos y Autorización OAuth2](https://www.twitch.tv/videos/1679022882)
4445

4546
### Curso de fundamentos desde cero
4647

0 commit comments

Comments
 (0)