Skip to content

Commit

Permalink
feat: ✨ add endpoint to contact experts via email
Browse files Browse the repository at this point in the history
  • Loading branch information
thomashbrnrd committed Jul 24, 2024
1 parent fe4071a commit 7df3893
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 6 deletions.
1 change: 1 addition & 0 deletions backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ user-agents==2.2.0
boto3==1.28.39
autodynatrace==2.0.0
PyJWT==2.8.0
cryptography==42.0.8
# ML
basegun-ml==1.0.1
# Dev
Expand Down
11 changes: 11 additions & 0 deletions backend/src/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from smtplib import SMTP

import boto3
from fastapi.security import OpenIdConnect
from gelfformatter import GelfFormatter
from jwt import PyJWKClient

CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))

Expand Down Expand Up @@ -124,3 +126,12 @@ def get_base_logs(user_agent, user_id: str) -> dict:

# Emails
SMTPClient = SMTP(os.environ["EMAIL_HOST"], os.environ["EMAIL_PORT"])

# Authentication
JWKS_CLIENT = None
PUBLIC_KEY = None
OAUTH2_SCHEME = None
if os.environ.get("JWKS_URL", None):
JWKS_CLIENT = PyJWKClient(os.environ["JWKS_URL"])
PUBLIC_KEY = JWKS_CLIENT.get_signing_key(os.environ["KID"]).key
OAUTH2_SCHEME = OpenIdConnect(openIdConnectUrl=os.environ["OPENIDCONNECT_URL"])
20 changes: 20 additions & 0 deletions backend/src/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from pydantic import BaseModel


class EmailData(BaseModel):
firstname: str
lastname: str
nigend: str
service: str | None
phone: str
email: str
seizure: str
una_or_procedure_number: str
gun_type: str
gun_length: int | None
gun_barrel_length: int | None
markings_description: str | None
right_picture: str
left_picture: str
markings_pictures: str
magazine_picture: str | None
36 changes: 34 additions & 2 deletions backend/src/router.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
import os
import time
from typing import Union
from typing import Annotated, Union
from uuid import uuid4

from basegun_ml.classification import get_typology
Expand All @@ -10,6 +10,7 @@
APIRouter,
BackgroundTasks,
Cookie,
Depends,
File,
Form,
HTTPException,
Expand All @@ -21,7 +22,8 @@
from user_agents import parse

from .config import APP_VERSION, S3_PREFIX, TYPOLOGIES_MEASURED, get_base_logs
from .utils import upload_image
from .models import EmailData
from .utils import get_current_user, send_mail, upload_image

router = APIRouter(prefix="/api")

Expand Down Expand Up @@ -163,3 +165,33 @@ async def log_identification_dummy(
extras_logging["bg_" + key] = res[key]

logging.info("Identification dummy", extra=extras_logging)


# Currently missing because we don't know if we can send attachements or if target can use S3 link
# Photo face droite : {request.right_picture}
# Photo face gauche : {request.left_picture}
# Photo des marquages : {request.markings_pictures}
# Photo du chargeur : {request.magazine_picture}
@router.post("/expert-contact")
async def expert_contact(
request: EmailData,
current_user: Annotated[dict, Depends(get_current_user)],
):
send_mail(
subject="[Basegun] Demande d'identification",
to="[email protected]",
message=f"""
Nom : {request.lastname}
Prénom : {request.firstname}
NIGEND / matricule : {request.nigend}
Service d'affectation : {request.service}
Téléphone : {request.phone}
Email : {request.email}
Saisie : {request.seizure}
N° de procédure : {request.una_or_procedure_number}
Typologie de l'arme (épaule ou poing) : {request.gun_type}
Longueur de l'arme : {request.gun_length}
Longueur du canon de l'arme : {request.gun_barrel_length}
Précision sur les marquages présents sur l'arme : {request.markings_description}
""",
)
22 changes: 21 additions & 1 deletion backend/src/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
import time
from datetime import datetime
from email.message import EmailMessage
from typing import Annotated

import jwt
from fastapi import Depends, HTTPException, status
from src.config import SMTPClient

from .config import S3, S3_BUCKET_NAME
from .config import OAUTH2_SCHEME, PUBLIC_KEY, S3, S3_BUCKET_NAME


def upload_image(content: bytes, image_key: str):
Expand Down Expand Up @@ -33,3 +36,20 @@ def send_mail(subject: str, to: str, message: str):
msg["To"] = to
msg.set_content(message)
SMTPClient.send_message(msg)


async def get_current_user(token: Annotated[str, Depends(OAUTH2_SCHEME)]):
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
return jwt.decode(
token.split()[1],
PUBLIC_KEY,
algorithms=["RS256"],
audience=["master-realm", "account"],
)
except jwt.InvalidTokenError:
raise credentials_exception
4 changes: 2 additions & 2 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import os
import time
from io import BytesIO

import boto3
import pytest
Expand Down Expand Up @@ -119,6 +118,7 @@ def test_headers(self):
for header_to_add in HEADERS_TO_ADD:
assert header_to_add["name"].lower() in CURRENT_HEADERS


class TestUpload:
def test_revolver_without_card(self):
with open("./tests/revolver.jpg", "rb") as f:
Expand Down Expand Up @@ -150,4 +150,4 @@ def test_semi_auto_without_card(self):
assert response.data["confidence_level"] == "high"
assert response.data["gun_length"] is not None
assert response.data["gun_barrel_length"] is not None
assert response.data["conf_card"] is not None
assert response.data["conf_card"] is not None
22 changes: 22 additions & 0 deletions backend/tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import jwt
import pytest
from fastapi.testclient import TestClient
from src.config import PUBLIC_KEY
from src.main import app

client = TestClient(app)

token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICItWmVZS3ZiMFEwdmJyZ2tEc2I5Rk5YLTU3QkdEMjNxRWhnUE1kdERHNUY4In0.eyJleHAiOjE3MjE3Mzc5ODksImlhdCI6MTcyMTczNzA4OSwiYXV0aF90aW1lIjoxNzIxNzM2ODk4LCJqdGkiOiI0NTkxMzI5Zi02YjIzLTQxYmMtYjU4Yy03ZmM3NjFhYjIzMzgiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgwODAvcmVhbG1zL21hc3RlciIsImF1ZCI6WyJtYXN0ZXItcmVhbG0iLCJhY2NvdW50Il0sInN1YiI6IjQ2YTUzMDM1LWExZGMtNDExOS1hZmYwLTM0NDY5OTJkMzFiOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImJhc2VndW4iLCJzaWQiOiI5M2RlZjg3My1lZTIxLTRhY2YtOTI4Ny03N2UwNzA3OTcxODEiLCJhY3IiOiIwIiwiYWxsb3dlZC1vcmlnaW5zIjpbImh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiY3JlYXRlLXJlYWxtIiwiZGVmYXVsdC1yb2xlcy1tYXN0ZXIiLCJvZmZsaW5lX2FjY2VzcyIsImFkbWluIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJtYXN0ZXItcmVhbG0iOnsicm9sZXMiOlsidmlldy1pZGVudGl0eS1wcm92aWRlcnMiLCJ2aWV3LXJlYWxtIiwibWFuYWdlLWlkZW50aXR5LXByb3ZpZGVycyIsImltcGVyc29uYXRpb24iLCJjcmVhdGUtY2xpZW50IiwibWFuYWdlLXVzZXJzIiwicXVlcnktcmVhbG1zIiwidmlldy1hdXRob3JpemF0aW9uIiwicXVlcnktY2xpZW50cyIsInF1ZXJ5LXVzZXJzIiwibWFuYWdlLWV2ZW50cyIsIm1hbmFnZS1yZWFsbSIsInZpZXctZXZlbnRzIiwidmlldy11c2VycyIsInZpZXctY2xpZW50cyIsIm1hbmFnZS1hdXRob3JpemF0aW9uIiwibWFuYWdlLWNsaWVudHMiLCJxdWVyeS1ncm91cHMiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4ifQ.hU0hJgMQayorct84UK9jiXDWOoBZ2KgmGa-JK0OOvn5Dfq0i_uZEOwzSjNwsvsYjX5NtHRrVBIxIcYzD-6IubUB2eUk7dgbYTqcVyjkWFrjwuv6ieTYvk-OuUg5iCXSe67ZbFuQwvNCg_8ns3JhEAEgHD4mLmhkPDlln4FkK605vAGZ5bDDYuPbaBI3ao4zpFr837r8zP5BGnzsclzk-T9k03pbTZ2aIv3PqlhfBVl2rKM0KYYKL8n3zLvObFMnZSx22-AuTPKKxkv3IrpCX2Zr_pAv-Bb2dw9LfEy_jxKv1i175Awjy3ayLbltvMcRzDTEzZ7YdBiuKoGoYjdoHng"


@pytest.mark.skip(reason="Cannot currently run in CI.")
class TestAuthentication:
def test_jwks(self):
print(
jwt.decode(
token,
PUBLIC_KEY,
algorithms=["RS256"],
audience=["master-realm", "account"],
)
)
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ services:
- AWS_SECRET_ACCESS_KEY=minioadmin
- EMAIL_HOST=mailpit
- EMAIL_PORT=1025
- JWKS_URL=http://keycloak:8080/realms/master/protocol/openid-connect/certs
- OPENIDCONNECT_URL=http://localhost:8080/realms/master/.well-known/openid-configuration
- KID=-ZeYKvb0Q0vbrgkDsb9FNX-57BGD23qEhgPMdtDG5F8
- http_proxy
- https_proxy
- no_proxy
Expand Down
11 changes: 10 additions & 1 deletion frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,16 @@ const routes: RouteRecordRaw[] = [
beforeEnter: (to, from) => {
mgr.getUser().then((user) => {
console.log(user);
if (user === null) mgr.signinRedirect();
if (user === null)
mgr
.signinRedirect()
.then((data) => console.log(data))
.catch((err) => {
console.log(err);
return {
name: "PageNotFound",
};
});
});
},
},
Expand Down

0 comments on commit 7df3893

Please sign in to comment.