8000 updated · wpcodevo/python_fastapi@9a22cc1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9a22cc1

Browse files
committed< 8000 div class="Box-sc-g0xbh4-0 LoadingSkeleton-sc-695d630a-0 irPhWZ irithh d-none d-sm-flex ml-1" width="60px">
updated
1 parent 232e4fe commit 9a22cc1

File tree

8 files changed

+544
-4
lines changed

8 files changed

+544
-4
lines changed

.env

+7
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,13 @@ JWT_ALGORITHM=RS256
1111

1212
CLIENT_ORIGIN=http://localhost:3000
1313

14+
VERIFICATION_SECRET=my-email-verification-secret
15+
16+
EMAIL_HOST=smtp.mailtrap.io
17+
EMAIL_PORT=587
18+
EMAIL_USERNAME=4aeca0c9318dd2
19+
EMAIL_PASSWORD=a987a0e0eac00d
20+
EMAIL_FROM=admin@admin.com
1421

1522
JWT_PRIVATE_KEY=LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlCT2dJQkFBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVmlQWlJyVFpjd3l4RVhVRGpNaFZuCi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUUpBYm5LaENOQ0dOSFZGaHJPQ0RCU0IKdmZ2ckRWUzVpZXAwd2h2SGlBUEdjeWV6bjd0U2RweUZ0NEU0QTNXT3VQOXhqenNjTFZyb1pzRmVMUWlqT1JhUwp3UUloQU84MWl2b21iVGhjRkltTFZPbU16Vk52TGxWTW02WE5iS3B4bGh4TlpUTmhBaUVBbWRISlpGM3haWFE0Cm15QnNCeEhLQ3JqOTF6bVFxU0E4bHUvT1ZNTDNSak1DSVFEbDJxOUdtN0lMbS85b0EyaCtXdnZabGxZUlJPR3oKT21lV2lEclR5MUxaUVFJZ2ZGYUlaUWxMU0tkWjJvdXF4MHdwOWVEejBEWklLVzVWaSt6czdMZHRDdUVDSUVGYwo3d21VZ3pPblpzbnU1clBsTDJjZldLTGhFbWwrUVFzOCtkMFBGdXlnCi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0t
1623
JWT_PUBLIC_KEY=LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZ3d0RRWUpLb1pJaHZjTkFRRUJCUUFEU3dBd1NBSkJBSSs3QnZUS0FWdHVQYzEzbEFkVk94TlVmcWxzMm1SVgppUFpSclRaY3d5eEVYVURqTWhWbi9KVHRsd3h2a281T0pBQ1k3dVE0T09wODdiM3NOU3ZNd2xNQ0F3RUFBUT09Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQ==

app/config.py

+8
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ class Settings(BaseSettings):
1717

1818
CLIENT_ORIGIN: str
1919

20+
VERIFICATION_SECRET: str
21+
22+
EMAIL_HOST: str
23+
EMAIL_PORT: int
24+
EMAIL_USERNAME: str
25+
EMAIL_PASSWORD: str
26+
EMAIL_FROM: EmailStr
27+
2028
class Config:
2129
env_file = './.env'
2230

app/email.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from typing import List
2+
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig
3+
from pydantic import EmailStr, BaseModel
4+
from . import models
5+
from .config import settings
6+
from jinja2 import Environment, select_autoescape, PackageLoader
7+
8+
9+
env = Environment(
10+
loader=PackageLoader('app', 'templates'),
11+
autoescape=select_autoescape(['html', 'xml'])
12+
)
13+
14+
15+
class EmailSchema(BaseModel):
16+
email: List[EmailStr]
17+
18+
19+
class Email:
20+
def __init__(self, user: models.User, url: str, email: List[EmailStr]):
21+
self.name = user.name
22+
self.sender = 'Codevo <admin@admin.com>'
23+
self.email = email
24+
self.url = url
25+
pass
26+
27+
async def sendMail(self, subject, template):
28+
# Define the config
29+
conf = ConnectionConfig(
30+
MAIL_USERNAME=settings.EMAIL_USERNAME,
31+
MAIL_PASSWORD=settings.EMAIL_PASSWORD,
32+
MAIL_FROM=settings.EMAIL_FROM,
33+
MAIL_PORT=settings.EMAIL_PORT,
34+
MAIL_SERVER=settings.EMAIL_HOST,
35+
MAIL_TLS=True,
36+
MAIL_SSL=False,
37+
USE_CREDENTIALS=True,
38+
VALIDATE_CERTS=True
39+
)
40+
# Generate the HTML template base on the template name
41+
template = env.get_template(f'{template}.html')
42+
43+
html = template.render(
44+
url=self.url,
45+
first_name=self.name,
46+
subject=subject
47+
)
48+
49+
# Define the message options
50+
message = MessageSchema(
51+
subject=subject,
52+
recipients=self.email,
53+
body=html,
54+
subtype="html"
55+
)
56+
57+
# Send the email
58+
fm = FastMail(conf)
59+
await fm.send_message(message)
60+
61+
async def sendVerificationCode(self):
62+
await self.sendMail('Your verification code (Valid for 10min)', 'verification')

app/oauth2.py

+27
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import base64
2+
from datetime import datetime, timedelta
23
from typing import List
34
from fastapi import Depends, HTTPException, status
45
from fastapi_jwt_auth import AuthJWT
56
from pydantic import BaseModel
7+
from jose import jwt, JWTError
68

79
from . import models
810
from .database import get_db
@@ -63,3 +65,28 @@ def require_user(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()):
6365
raise HTTPException(
6466
status_code=status.HTTP_401_UNAUTHORIZED, detail='Token is invalid or has expired')
6567
return user_id
68+
69+
70+
def create_verification_token(user_id: str):
71+
expires = datetime.utcnow() + timedelta(minutes=30)
72+
payload = {'user_id': user_id, 'exp': expires}
73+
return jwt.encode(payload, settings.VERIFICATION_SECRET, algorithm='HS256')
74+
75+
76+
def verify_email_token(token: str):
77+
try:
78+
decoded = jwt.decode(
79+
token, settings.VERIFICATION_SECRET, algorithms=['HS256'])
80+
user_id = decoded.get('user_id')
81+
if not user_id:
82+
raise HTTPException(
83+
status_code=status.HTTP_400_BAD_REQUEST, detail='Could not verify user')
84+
except JWTError as e:
85+
error = e.__class__.__name__
86+
print(error)
87+
if error == 'ExpiredSignatureError':
88+
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
89+
detail='Token is invalid or has expired')
90+
raise HTTPException(
91+
status_code=status.HTTP_400_BAD_REQUEST, detail='Could not verify user')
92+
return user_id

app/routers/auth.py

+30-4
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@
88
from ..database import get_db
99
from app.oauth2 import AuthJWT
1010
from ..config import settings
11+
from ..email import Email
1112

1213

1314
router = APIRouter()
1415
ACCESS_TOKEN_EXPIRES_IN = settings.ACCESS_TOKEN_EXPIRES_IN
1516
REFRESH_TOKEN_EXPIRES_IN = settings.REFRESH_TOKEN_EXPIRES_IN
1617

1718

18-
@router.post('/register', status_code=status.HTTP_201_CREATED, response_model=schemas.UserResponse)
19-
async def create_user(payload: schemas.CreateUserSchema, db: Session = Depends(get_db)):
19+
@router.post('/register', status_code=status.HTTP_201_CREATED)
20+
async def create_user(payload: schemas.CreateUserSchema, request: Request, db: Session = Depends(get_db)):
2021
# Check if user already exist
2122
user = db.query(models.User).filter(
2223
models.User.email == EmailStr(payload.email.lower())).first()
@@ -31,13 +32,22 @@ async def create_user(payload: schemas.CreateUserSchema, db: Session = Depends(g
3132
payload.password = utils.hash_password(payload.password)
3233
del payload.passwordConfirm
3334
payload.role = 'user'
34-
payload.verified = True
35+
payload.verified = False
3536
payload.email = EmailStr(payload.email.lower())
3637
new_user = models.User(**payload.dict())
3738
db.add(new_user)
3839
db.commit()
3940
db.refresh(new_user)
40-
return new_user
41+
42+
try:
43+
token = oauth2.create_verification_token(str(new_user.id))
44+
url = f"{request.url.scheme}://{request.client.host}:{request.url.port}/api/auth/verifyemail/{token}"
45+
await Email(new_user, url, [payload.email]).sendVerificationCode()
46+
except Exception as error:
47+
print('Error', error)
48+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
49+
detail='There was an error sending email')
50+
return {'status': 'success', 'message': 'Verification token successfully sent to your email'}
4151

4252

4353
@router.post('/login')
@@ -115,3 +125,19 @@ def logout(response: Response, Authorize: AuthJWT = Depends(), user_id: str = De
115125
response.set_cookie('logged_in', '', -1)
116126

117127
return {'status': 'success'}
128+
129+
130+
@router.get('/verifyemail/{token}')
131+
def verify_me(token: str, db: Session = Depends(get_db)):
132+
id = oauth2.verify_email_token(token)
133+
user_query = db.query(models.User).filter(models.User.id == id)
134+
user = user_query.first()
135+
if not user:
136+
raise HTTPException(
137+
status_code=status.HTTP_404_NOT_FOUND, detail='User no longer exist')
138+
user_query.update({'verified': True}, synchronize_session=False)
139+
db.commit()
140+
return {
141+
"status": "success",
142+
"message": "Account verified successfully"
143+
}

0 commit comments

Comments
 (0)
0