Skip to content

Commit

Permalink
Merge pull request #31 from Kosjenka-Reading-App/dev
Browse files Browse the repository at this point in the history
Alpha prototype
  • Loading branch information
vvihorev authored Nov 26, 2023
2 parents bcae962 + d57ad99 commit e2f15ef
Show file tree
Hide file tree
Showing 27 changed files with 1,122 additions and 186 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit=test/*
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
DATABASE_URL="sqlite:///./db.sqlite"
# DATABASE_URL="postgresql://user:password@postgresserver/db"

# AUTH SETTINGS / PASSWORT RESET
# Important: No Backslash at the end!
PASSWORD_RESET_LINK="127.0.0.1"
#In Sec = 20min
JWT_VALID_TIME_ACCESS=1200
#In Sec = one week
JWT_VALID_TIME_REFRESH=604800
#In Sec = 10min
JWT_VALID_TIME_PWD_RESET=600
JWT_SECRET="secret"
JWT_ALGORITHM="HS256"
MAIL_USERNAME="[email protected]"
MAIL_PASSWORD=""
MAIL_PORT=587
MAIL_SERVER="smtp.gmail.com"
MAIL_FROM_NAME="Kosjenka Support"

SUPERADMIN_LOGIN="[email protected]"
SUPERADMIN_PASSWORD="superadmin"
13 changes: 13 additions & 0 deletions .github/workflows/dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,19 @@ jobs:
- name: Crate env file with secrets
run: |
echo "DATABASE_URL=${{ secrets.DEV_DATABASE_URL }}" >> .env
echo "JWT_SECRET=${{ secrets.DEV_JWT_SECRET }}" >> .env
echo "MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}" >> .env
echo "PASSWORD_RESET_LINK=127.0.0.1" >> .env
echo "JWT_VALID_TIME_ACCESS=1200" >> .env
echo "JWT_VALID_TIME_REFRESH=604800" >> .env
echo "JWT_VALID_TIME_PWD_RESET=600" >> .env
echo "JWT_ALGORITHM=HS256" >> .env
echo "[email protected]" >> .env
echo "MAIL_PORT=587" >> .env
echo "MAIL_SERVER=smtp.gmail.com" >> .env
echo "MAIL_FROM_NAME=Kosjenka Support" >> .env
echo "[email protected]" >> .env
echo "SUPERADMIN_PASSWORD={{ secrets.DEV_SUPERADMIN_PASSWORD }}" >> .env
- name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ on:

env:
DATABASE_URL: "sqlite:///./db.sqlite"
PASSWORD_RESET_LINK: "127.0.0.1"
JWT_VALID_TIME_ACCESS: 1200
JWT_VALID_TIME_REFRESH: 604800
JWT_VALID_TIME_PWD_RESET: 600
JWT_SECRET: "secret"
JWT_ALGORITHM: "HS256"
MAIL_USERNAME: "[email protected]"
MAIL_PASSWORD: "secret"
MAIL_PORT: 587
MAIL_SERVER: "smtp.gmail.com"
MAIL_FROM_NAME: "Kosjenka Support"
SUPERADMIN_LOGIN: "[email protected]"
SUPERADMIN_PASSWORD: "superadmin"


jobs:
tests:
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,17 @@ jobs:
- name: Crate env file with secrets
run: |
echo "DATABASE_URL=${{ secrets.PROD_DATABASE_URL }}" >> .env
echo "JWT_SECRET=${{ secrets.DEV_JWT_SECRET }}" >> .env
echo "JWT_SECRET=${{ secrets.PROD_JWT_SECRET }}" >> .env
echo "MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}" >> .env
echo "PASSWORD_RESET_LINK=127.0.0.1" >> .env
echo "JWT_VALID_TIME_ACCESS=1200" >> .env
echo "JWT_VALID_TIME_REFRESH=604800" >> .env
echo "JWT_VALID_TIME_PWD_RESET=600" >> .env
echo "JWT_ALGORITHM=HS256" >> .env
echo "[email protected]" >> .env
echo "MAIL_PORT=587" >> .env
echo "MAIL_SERVER=smtp.gmail.com" >> .env
echo "MAIL_FROM_NAME=Kosjenka Support" >> .env
echo "SUPERADMIN_PASSWORD=${{ secrets.PROD_SUPERADMIN_PASSWORD }}" >> .env
- name: Build and push Docker image
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ __pycache__
venv
db.sqlite
.env
.coverage
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Launch tests from the root directory with
python3 -m pytest
```

To experiment with the ORM, run:
```
ipython3 -i test/sqlalchemy_console.py
```

To see the coverage report:
```
coverage run -m pytest
coverage report
coverage html
```


# Sources

Expand Down
86 changes: 81 additions & 5 deletions auth.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,40 @@
from typing import Type

from sqlalchemy.orm import Session
from fastapi_mail import FastMail, MessageSchema, ConnectionConfig, MessageType
from pydantic import EmailStr
from dotenv import load_dotenv

import models, schemas
import models, schemas, crud
import jwt
import time
import bcrypt
import os

JWT_VALID_TIME_ACCESS = 60 * 20 # 20min
JWT_VALID_TIME_REFRESH = 60 * 60 * 24 * 7 # One week
JWT_SECRET = "C0ddVvlcaL4UuChF8ckFQoVCGbtizyvK"
JWT_ALGORITHM = "HS256"
JWT_VALID_TIME_ACCESS = int(os.environ["JWT_VALID_TIME_ACCESS"]) # 60 * 20 # 20min
JWT_VALID_TIME_REFRESH = int(
os.environ["JWT_VALID_TIME_REFRESH"]
) # 60 * 60 * 24 * 7 # One week
JWT_VALID_TIME_PWD_RESET = int(
os.environ["JWT_VALID_TIME_PWD_RESET"]
) # 60 * 10 # 10min
JWT_SECRET = os.environ["JWT_SECRET"] # "C0ddVvlcaL4UuChF8ckFQoVCGbtizyvK"
JWT_ALGORITHM = os.environ["JWT_ALGORITHM"] # "HS256"

# Mail Config
conf = ConnectionConfig(
MAIL_USERNAME=os.environ["MAIL_USERNAME"], # "[email protected]",
MAIL_PASSWORD=os.environ["MAIL_PASSWORD"], # "qcjb hvps xmlf rtpm",
MAIL_FROM=os.environ["MAIL_USERNAME"], # "[email protected]",
MAIL_PORT=int(os.environ["MAIL_PORT"]), # 587,
MAIL_SERVER=os.environ["MAIL_SERVER"], # "smtp.gmail.com",
MAIL_FROM_NAME=os.environ["MAIL_FROM_NAME"], # "Kosjenka Support",
MAIL_STARTTLS=True,
MAIL_SSL_TLS=False,
USE_CREDENTIALS=True,
VALIDATE_CERTS=False,
TEMPLATE_FOLDER=os.path.join(os.path.dirname(__file__), "html_templates"),
)


def createToken(
Expand Down Expand Up @@ -78,3 +102,55 @@ def generate_refresh_token(old_token: str, decoded_token: Type[schemas.AuthSchem
)
reponse.refresh_token = old_token
return reponse


# Password reset
def get_account_by_email(db: Session, email: EmailStr):
account = db.query(models.Account).filter(models.Account.email == email).first()
return account


async def send_password_reset_mail(account: models.Account):
token = createPasswortResetToken(
email=account.email, valid_time=JWT_VALID_TIME_PWD_RESET
)
link_base = os.environ["PASSWORD_RESET_LINK"]
template_body = {
"user": account.email,
"url": f"{link_base}?token={token}",
"expire_in_minutes": int(JWT_VALID_TIME_PWD_RESET / 60),
}
message = MessageSchema(
subject="Kosjenka - Password Reset",
recipients=[account.email],
template_body=template_body,
subtype=MessageType.html,
)
fm = FastMail(conf)
await fm.send_message(message, template_name="reset_password_email.html")


def createPasswortResetToken(email: EmailStr, valid_time: int):
payload = {
"email": email,
"expires": time.time() + valid_time,
}
token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
return token


def reset_password(db: Session, new_password: str, token: str):
try:
decoded_token = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
if decoded_token["expires"] < time.time():
return "TOKEN_EXPIRED"
account = get_account_by_email(db, decoded_token["email"])
if account is None:
return "EMAIL_NOT_FOUND"
hashed_pw = crud.password_hasher(new_password)
setattr(account, "password", hashed_pw)
db.commit()
db.refresh(account)
return "SUCCESS"
except:
return "ERROR"
3 changes: 3 additions & 0 deletions auth_bearer.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

class JWTBearer(HTTPBearer):
def __init__(self, auto_error: bool = True):
self.auto_error = auto_error
super(JWTBearer, self).__init__(auto_error=auto_error)

async def __call__(self, request: Request):
Expand All @@ -26,5 +27,7 @@ async def __call__(self, request: Request):
status_code=403, detail="Invalid token or expired token."
)
return token
elif not self.auto_error:
return None
else:
raise HTTPException(status_code=403, detail="Invalid authorization code.")
Loading

0 comments on commit e2f15ef

Please sign in to comment.