Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 17ce6e5

Browse filesBrowse files
committed
updated
1 parent 8fefd27 commit 17ce6e5
Copy full SHA for 17ce6e5

File tree

4 files changed

+71
-35
lines changed
Filter options

4 files changed

+71
-35
lines changed
+43Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"""added verification code
2+
3+
Revision ID: 39256113e8e5
4+
Revises: 1c7984990e1d
5+
Create Date: 2022-07-14 08:03:57.507140
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
from sqlalchemy.dialects import postgresql
11+
12+
# revision identifiers, used by Alembic.
13+
revision = '39256113e8e5'
14+
down_revision = '1c7984990e1d'
15+
branch_labels = None
16+
depends_on = None
17+
18+
19+
def upgrade() -> None:
20+
# ### commands auto generated by Alembic - please adjust! ###
21+
op.drop_table('posts')
22+
op.add_column('users', sa.Column('verification_code', sa.String(), nullable=True))
23+
op.create_unique_constraint(None, 'users', ['verification_code'])
24+
# ### end Alembic commands ###
25+
26+
27+
def downgrade() -> None:
28+
# ### commands auto generated by Alembic - please adjust! ###
29+
op.drop_constraint(None, 'users', type_='unique')
30+
op.drop_column('users', 'verification_code')
31+
op.create_table('posts',
32+
sa.Column('id', postgresql.UUID(), server_default=sa.text('uuid_generate_v4()'), autoincrement=False, nullable=False),
33+
sa.Column('user_id', postgresql.UUID(), autoincrement=False, nullable=False),
34+
sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=False),
35+
sa.Column('content', sa.VARCHAR(), autoincrement=False, nullable=False),
36+
sa.Column('category', sa.VARCHAR(), autoincrement=False, nullable=False),
37+
sa.Column('image', sa.VARCHAR(), autoincrement=False, nullable=False),
38+
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False),
39+
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False),
40+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name='posts_user_id_fkey', ondelete='CASCADE'),
41+
sa.PrimaryKeyConstraint('id', name='posts_pkey')
42+
)
43+
# ### end Alembic commands ###

‎app/models.py

Copy file name to clipboardExpand all lines: app/models.py
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from enum import unique
12
import uuid
23
from .database import Base
34
from sqlalchemy import TIMESTAMP, Column, String, Boolean, text
@@ -13,6 +14,7 @@ class User(Base):
1314
password = Column(String, nullable=False)
1415
photo = Column(String, nullable=True)
1516
verified = Column(Boolean, nullable=False, server_default='False')
17+
verification_code = Column(String, nullable=True, unique=True)
1618
role = Column(String, server_default='user', nullable=False)
1719
created_at = Column(TIMESTAMP(timezone=True),
1820
nullable=False, server_default=text("now()"))

‎app/oauth2.py

Copy file name to clipboardExpand all lines: app/oauth2.py
-27Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import base64
2-
from datetime import datetime, timedelta
32
from typing import List
43
from fastapi import Depends, HTTPException, status
54
from fastapi_jwt_auth import AuthJWT
65
from pydantic import BaseModel
7-
from jose import jwt, JWTError
86

97
from . import models
108
from .database import get_db
@@ -65,28 +63,3 @@ def require_user(db: Session = Depends(get_db), Authorize: AuthJWT = Depends()):
6563
raise HTTPException(
6664
status_code=status.HTTP_401_UNAUTHORIZED, detail='Token is invalid or has expired')
6765
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

Copy file name to clipboardExpand all lines: app/routers/auth.py
+26-8Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from datetime import timedelta
2+
import hashlib
3+
from random import randbytes
24
from fastapi import APIRouter, Request, Response, status, Depends, HTTPException
35
from pydantic import EmailStr
46

@@ -19,8 +21,9 @@
1921
@router.post('/register', status_code=status.HTTP_201_CREATED)
2022
async def create_user(payload: schemas.CreateUserSchema, request: Request, db: Session = Depends(get_db)):
2123
# Check if user already exist
22-
user = db.query(models.User).filter(
23-
models.User.email == EmailStr(payload.email.lower())).first()
24+
user_query = db.query(models.User).filter(
25+
models.User.email == EmailStr(payload.email.lower()))
26+
user = user_query.first()
2427
if user:
2528
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
2629
detail='Account already exist')
@@ -40,11 +43,21 @@ async def create_user(payload: schemas.CreateUserSchema, request: Request, db: S
4043
db.refresh(new_user)
4144

4245
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}"
46+
# Send Verification Email
47+
token = randbytes(10)
48+
hashedCode = hashlib.sha256()
49+
hashedCode.update(token)
50+
verification_code = hashedCode.hexdigest()
51+
user_query.update(
52+
{'verification_code': verification_code}, synchronize_session=False)
53+
db.commit()
54+
url = f"{request.url.scheme}://{request.client.host}:{request.url.port}/api/auth/verifyemail/{token.hex()}"
4555
await Email(new_user, url, [payload.email]).sendVerificationCode()
4656
except Exception as error:
4757
print('Error', error)
58+
user_query.update(
59+
{'verification_code': None}, synchronize_session=False)
60+
db.commit()
4861
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
4962
detail='There was an error sending email')
5063
return {'status': 'success', 'message': 'Verification token successfully sent to your email'}
@@ -129,13 +142,18 @@ def logout(response: Response, Authorize: AuthJWT = Depends(), user_id: str = De
129142

130143
@router.get('/verifyemail/{token}')
131144
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)
145+
hashedCode = hashlib.sha256()
146+
hashedCode.update(bytes.fromhex(token))
147+
verification_code = hashedCode.hexdigest()
148+
user_query = db.query(models.User).filter(
149+
models.User.verification_code == verification_code)
150+
db.commit()
134151
user = user_query.first()
135152
if not user:
136153
raise HTTPException(
137-
status_code=status.HTTP_404_NOT_FOUND, detail='User no longer exist')
138-
user_query.update({'verified': True}, synchronize_session=False)
154+
status_code=status.HTTP_403_FORBIDDEN, detail='Email can only be verified once')
155+
user_query.update(
156+
{'verified': True, 'verification_code': None}, synchronize_session=False)
139157
db.commit()
140158
return {
141159
"status": "success",

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.