Skip to content

Commit

Permalink
Merge pull request #16 from fga-eps-mds/develop
Browse files Browse the repository at this point in the history
Release 01
  • Loading branch information
joao15victor08 authored Nov 22, 2023
2 parents 81de761 + 4196a0d commit b1bbbae
Show file tree
Hide file tree
Showing 38 changed files with 1,975 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
SECRET=
ALGORITHM=
ACCESS_TOKEN_EXPIRE_MINUTES=
REFRESH_TOKEN_EXPIRE_DAYS=

MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_FROM=
MAIL_PORT=
MAIL_SERVER=

CLIENT_ID=
CLIENT_SECRET=
FACEBOOK_CLIENT_ID=
FACEBOOK_CLIENT_SECRET=

POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_HOST=
POSTGRES_DB=
POSTGRES_PORT=
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
__pycache__
venv
*.db
.env
build
unb_tv.egg-info
data/postgress
.pytest_cache
data
.coverage
13 changes: 13 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM python:3.10.9-slim-buster

WORKDIR /app

COPY requirements.txt /app/requirements.txt

RUN pip3 install --no-cache-dir -r /app/requirements.txt

COPY . /app/

WORKDIR src

CMD [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
1 change: 1 addition & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
web: python src/main.py ${PORT}
48 changes: 48 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
version: "3.8"

services:
app:
build: .
ports:
- 8000:8000
volumes:
- .:/app/
env_file:
- ./.env
environment:
- POSTGRES_HOST=db
depends_on:
db:
condition: service_healthy
restart: always
networks:
- backend_users

db:
image: postgres
volumes:
- postgres_data:/var/lib/postgresql/data/
expose:
- 5432
environment:
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_DB=${POSTGRES_DB}
env_file:
- ./.env
restart: always
networks:
- backend_users
healthcheck:
test:
["CMD-SHELL","pg_isready -d ${POSTGRES_DB} -U ${POSTGRES_USER}",]
interval: 10s
timeout: 5s
retries: 5

volumes:
postgres_data:

networks:
backend_users:
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[tool.pytest.ini_options]
addopts = ["--import-mode=importlib"]
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
filterwarnings=ignore::DeprecationWarning
75 changes: 75 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
aiosmtplib==2.0.2
annotated-types==0.6.0
anyio==3.7.1
astroid==3.0.1
bcrypt==4.0.1
blinker==1.6.3
certifi==2023.7.22
cffi==1.16.0
charset-normalizer==3.3.0
click==8.1.7
coverage==7.3.2
cryptography==41.0.4
DateTime==5.2
deprecation==2.1.0
dill==0.3.7
dnspython==2.4.2
docker==6.1.3
ecdsa==0.18.0
email-validator==2.0.0.post2
fastapi==0.104.0
fastapi-filter==1.0.0
fastapi-mail==1.4.1
fastapi-sso==0.7.2
greenlet==3.0.0
h11==0.14.0
httpcore==0.18.0
httptools==0.6.1
httpx==0.25.0
idna==3.4
iniconfig==2.0.0
isort==5.12.0
Jinja2==3.1.2
MarkupSafe==2.1.3
mccabe==0.7.0
oauthlib==3.2.2
packaging==23.2
passlib==1.7.4
platformdirs==3.11.0
pluggy==1.3.0
psycopg2-binary==2.9.9
pyasn1==0.5.0
pycparser==2.21
pydantic==2.4.2
pydantic-settings==2.0.3
pydantic_core==2.10.1
pylint==3.0.2
PyMySQL==1.1.0
pytest==7.4.3
pytest-asyncio==0.21.1
pytest-env==1.1.1
pytest-html==4.1.1
pytest-metadata==3.0.0
pytest-mock==3.12.0
python-dotenv==1.0.0
python-jose==3.3.0
python-multipart==0.0.6
pytz==2023.3.post1
PyYAML==6.0.1
requests==2.31.0
rsa==4.9
six==1.16.0
sniffio==1.3.0
SQLAlchemy==2.0.22
starlette==0.27.0
testcontainers==3.7.1
tomlkit==0.12.2
typing_extensions==4.8.0
urllib3==2.0.7
uvicorn==0.23.2
uvloop==0.18.0
watchfiles==0.21.0
websocket-client==1.6.4
websockets==11.0.3
wrapt==1.15.0
zope.interface==6.1
1 change: 1 addition & 0 deletions runtime.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
python-3.10.12
Empty file added src/__init__.py
Empty file.
Empty file added src/constants/__init__.py
Empty file.
16 changes: 16 additions & 0 deletions src/constants/errorMessages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
EMAIL_ALREADY_REGISTERED = "Email já foi registrado."
USER_NOT_FOUND = "Usuário não encontrado."
INVALID_PASSWORD = "Senha inválida."
PASSWORD_NO_MATCH = "As senhas não conferem."
TOKEN_EXPIRED = "TOKEN HAS EXPIRED"
INVALID_TOKEN = "TOKEN IS INVALID"
MISSING_ENV_VALUES = "SOME ENVIRONMENT VALUES WERE NOT DEFINED"
INVALID_CONNECTION = "INVALID CONNECTION"
INVALID_CODE = "O código informado é inválido."
ACCOUNT_IS_NOT_ACTIVE = "A sua conta ainda não foi ativada."
ACCOUNT_ALREADY_ACTIVE = "A sua conta já foi ativada."
ERROR_SENDING_EMAIL = "Ocorreu um erro ao enviar o email."
NO_RESET_PASSWORD_CODE = "Código de reinicialização de senha não foi informado."
INVALID_RESET_PASSWORD_CODE = "Código de reinicialização de senha está inválido."
INVALID_REQUEST = "Requisição inválida."
NO_PERMISSION = "NO PERMISSION"
Empty file added src/controller/__init__.py
Empty file.
146 changes: 146 additions & 0 deletions src/controller/authController.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import os
from fastapi import APIRouter, HTTPException, Response, status, Depends
from utils import security, enumeration, send_mail
from database import get_db
from sqlalchemy.orm import Session
from datetime import datetime, timedelta
from constants import errorMessages
from starlette.responses import JSONResponse

from domain import userSchema, authSchema
from repository import userRepository
import secrets

auth = APIRouter(
prefix="/auth"
)

@auth.get("/vinculo", response_model=authSchema.Connections)
def get_connection():
connections = [member.value for member in enumeration.UserConnection]
return JSONResponse(status_code=200, content=connections)

@auth.post('/register')
async def register(data: authSchema.UserCreate, db: Session = Depends(get_db)):
# Verifica se connection é valido
if (not enumeration.UserConnection.has_value(data.connection)):
raise HTTPException(status_code=400, detail=errorMessages.INVALID_CONNECTION)

# Validação de senha
is_valid = security.validate_password(data.password.strip())
if (not is_valid):
raise HTTPException(status_code=400, detail=errorMessages.INVALID_PASSWORD)

# Verifica se já existe um usuário com o email informado
user = userRepository.get_user_by_email(db, data.email)
if user:
raise HTTPException(status_code=400, detail=errorMessages.EMAIL_ALREADY_REGISTERED)

hashed_password = security.get_password_hash(data.password)

activation_code = security.generate_six_digit_number_code()

userRepository.create_user(db, name=data.name, connection=data.connection, email=data.email, password=hashed_password, activation_code=activation_code)

await send_mail.send_verification_code(email=data.email, code=activation_code)

return JSONResponse(status_code=201, content={ "status": "success" })

@auth.post("/login", response_model=authSchema.Token)
async def login(data: authSchema.UserLogin, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

password_match = security.verify_password(data.password, user.password)
if not password_match:
raise HTTPException(status_code=404, detail=errorMessages.PASSWORD_NO_MATCH)

if not user.is_active:
raise HTTPException(status_code=401, detail=errorMessages.ACCOUNT_IS_NOT_ACTIVE)

access_token = security.create_access_token(data={ "id": user.id, "email": user.email, "role": user.role })
refresh_token = security.create_refresh_token(data={ "id": user.id })

return JSONResponse(status_code=200, content={ "access_token": access_token, "refresh_token": refresh_token, "token_type": "bearer" })

@auth.post("/refresh", response_model=authSchema.RefreshTokenResponse)
def refresh_token(token: dict = Depends(security.verify_token)):
access_token=security.create_access_token(token)
return JSONResponse(status_code=200, content={ "access_token": access_token, "token_type": "bearer" })

@auth.post('/resend-code')
async def send_new_code(data: authSchema.SendNewCode, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

if user.is_active:
return JSONResponse(status_code=400, content={ "status": "error", "message": errorMessages.ACCOUNT_ALREADY_ACTIVE })

res = await send_mail.send_verification_code(email=data.email, code=user.activation_code)
return JSONResponse(status_code=201, content={ "status": "success" })

@auth.patch('/activate-account')
async def validate_account(data: authSchema.AccountValidation, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

if user.is_active:
return JSONResponse(status_code=200, content={ "status": "error", "message": errorMessages.ACCOUNT_ALREADY_ACTIVE })

if user.activation_code != data.code:
raise HTTPException(status_code=404, detail=errorMessages.INVALID_CODE)

userRepository.activate_account(db, user)
return JSONResponse(status_code=200, content={ "status": "success" })

@auth.post('/reset-password/request')
async def request_password_(data: authSchema.ResetPasswordRequest, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

if not user.is_active:
raise HTTPException(status_code=404, detail=errorMessages.ACCOUNT_IS_NOT_ACTIVE)

code = security.generate_six_digit_number_code()

userRepository.set_user_reset_pass_code(db, user, code)
await send_mail.send_reset_password_code(data.email, code)
return JSONResponse(status_code=200, content={ "status": "success" })

@auth.post('/reset-password/verify')
async def verify_reset_code(data: authSchema.ResetPasswordVerify, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

if not user.password_reset_code:
raise HTTPException(status_code=404, detail=errorMessages.NO_RESET_PASSWORD_CODE)

if user.password_reset_code != data.code:
raise HTTPException(status_code=400, detail=errorMessages.INVALID_RESET_PASSWORD_CODE)

return JSONResponse(status_code=200, content={ "status": "success" })

@auth.patch('/reset-password/change', response_model=userSchema.User)
async def update_user_password(data: authSchema.ResetPasswordUpdate, db: Session = Depends(get_db)):
user = userRepository.get_user_by_email(db, data.email)
if not user:
raise HTTPException(status_code=404, detail=errorMessages.USER_NOT_FOUND)

if data.password and not security.validate_password(data.password):
raise HTTPException(status_code=400, detail=errorMessages.INVALID_PASSWORD)

if not user.password_reset_code:
raise HTTPException(status_code=401, detail=errorMessages.INVALID_REQUEST)

if data.code != user.password_reset_code:
raise HTTPException(status_code=400, detail=errorMessages.INVALID_RESET_PASSWORD_CODE)

hashed_password = security.get_password_hash(data.password)
updated_user = userRepository.update_password(db, user, hashed_password)

return updated_user
Loading

0 comments on commit b1bbbae

Please sign in to comment.