From 02bc6809825bc2ee68d62151aeaefff8e03174a9 Mon Sep 17 00:00:00 2001 From: Robin COuret Date: Thu, 5 Mar 2026 17:30:39 +0100 Subject: [PATCH] add auth --- server/.env.example | 4 +- server/requirements.txt | 2 + .../app/__pycache__/database.cpython-311.pyc | Bin 1242 -> 1241 bytes .../__pycache__/faker_seed.cpython-311.pyc | Bin 3395 -> 3205 bytes .../src/app/__pycache__/main.cpython-311.pyc | Bin 1575 -> 1639 bytes server/src/app/api/v1/__init__.py | 4 +- .../v1/__pycache__/__init__.cpython-311.pyc | Bin 555 -> 667 bytes .../v1/__pycache__/knowledges.cpython-311.pyc | Bin 3192 -> 3248 bytes .../api/v1/__pycache__/users.cpython-311.pyc | Bin 0 -> 2611 bytes server/src/app/api/v1/knowledges.py | 1 + server/src/app/api/v1/users.py | 36 ++++++++ .../crud_knowledges.cpython-311.pyc | Bin 3445 -> 3432 bytes .../__pycache__/crud_metrics.cpython-311.pyc | Bin 2741 -> 2741 bytes .../__pycache__/crud_user.cpython-311.pyc | Bin 0 -> 3355 bytes server/src/app/crud/crud_knowledges.py | 4 +- server/src/app/crud/crud_user.py | 38 +++++++++ server/src/app/database.py | 2 +- server/src/app/faker_seed.py | 5 +- server/src/app/main.py | 1 + .../__pycache__/knowledge.cpython-311.pyc | Bin 1011 -> 1011 bytes .../models/__pycache__/metric.cpython-311.pyc | Bin 1041 -> 1041 bytes .../__pycache__/question.cpython-311.pyc | Bin 1204 -> 1204 bytes .../models/__pycache__/user.cpython-311.pyc | Bin 0 -> 732 bytes server/src/app/models/knowledge.py | 1 - server/src/app/models/metric.py | 2 - server/src/app/models/question.py | 1 - server/src/app/models/user.py | 6 ++ .../services/__pycache__/auth.cpython-311.pyc | Bin 0 -> 4463 bytes .../services/__pycache__/user.cpython-311.pyc | Bin 0 -> 4744 bytes server/src/app/services/auth.py | 80 ++++++++++++++++++ 30 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 server/src/app/api/v1/__pycache__/users.cpython-311.pyc create mode 100644 server/src/app/api/v1/users.py create mode 100644 server/src/app/crud/__pycache__/crud_user.cpython-311.pyc create mode 100644 server/src/app/crud/crud_user.py create mode 100644 server/src/app/models/__pycache__/user.cpython-311.pyc create mode 100644 server/src/app/models/user.py create mode 100644 server/src/app/services/__pycache__/auth.cpython-311.pyc create mode 100644 server/src/app/services/__pycache__/user.cpython-311.pyc create mode 100644 server/src/app/services/auth.py diff --git a/server/.env.example b/server/.env.example index 925fbad..373fcc5 100644 --- a/server/.env.example +++ b/server/.env.example @@ -1,4 +1,6 @@ DATABASE_URI="sqlite:///database.db" LANGUAGE_MODEL_API="http://localhost:8080/v1" MODEL_NAME="SmolLM3-Q4_K_M.gguf" -ORIGIN="http://localhost:5173" \ No newline at end of file +ORIGIN="http://localhost:5173" +SECRET_SIGN="xxxx" #generate secure random secret key: openssl rand -hex 32 +ACCESS_TOKEN_EXPIRE_MINUTES=10080 \ No newline at end of file diff --git a/server/requirements.txt b/server/requirements.txt index b151ac7..b750282 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -3,5 +3,7 @@ sqlmodel==0.0.32 python-dotenv==1.2.1 openai==2.21.0 spacy==3.8.11 +PyJWT>=2.11.0 +argon2-cffi>=25.1.0 # python -m spacy download en_core_web_sm # python -m spacy download fr_core_news_sm \ No newline at end of file diff --git a/server/src/app/__pycache__/database.cpython-311.pyc b/server/src/app/__pycache__/database.cpython-311.pyc index b0470e3603301d69c5fcd76d72fd5025478177d3..abff50086015c38d4f0b5ff256ece565e13d9bec 100644 GIT binary patch delta 86 zcmcb`d6ScOIWI340}xDIzcO>hMBaJoY^hvp*q1RgFsue*2#DfN;b>uq;z{8IlDsL5 i!3>&Q6E8(?)?+MW=26{|bAi+QBB%EiPVdR9S@Z$0=N88R delta 88 zcmcb~d5e>GIWI340}z;hoR+zKBJVtP_EfGl9Lty)7*+!@1VnMCaJDc+@uYA8N!}F3 kU2;CQ>k6mWs)hfxmoR^o20SG$htjtuI$SYYN0_03*NMT4}%wfo7jACR2v6*t1a+#x;!EELn zmR!~-Rxq0-hb@;qiXF^m&Ed%9jN$~d*>bpYxudv&Z1xt0D4rCKU#O}M__4caY<%=9+1rsQLx#9(VCr+cXA1bfdDJe zoFX<5!44ugCU4=mI{3g27B{;nP6~2^i@WF` zh@)=)12-PGIJ-HCIGG$3y+JU1dA#?&d_TxZ^?3qcV_XLEJYJo(PA_nEz@@kZ6i~>4 zfCNItOS~jBq6wYo!XSn)i76~%A~!%E*jEn09i{;=i(NA4P_AB(U8FJ7=; zVEtj-d57|akCrQup%SJ7OMxA)p(*IZ98IIV5GmJ<&(S2r@hz$iJZ01++<+)ld`7~j xy(Buc&sj5?mlDd`y_4{!K?qYYn-n7jN6G&p1&yQ-wMGgylfNTgahtl@>kGjDp{D=< diff --git a/server/src/app/__pycache__/main.cpython-311.pyc b/server/src/app/__pycache__/main.cpython-311.pyc index a7d461f790755e8ba8dee2b22ead38cc4a9aa113..cfdbf97dfaebe993272cda868fd064fd5806f0b2 100644 GIT binary patch delta 468 zcmZ3^^PGowIWI340}x!Ay)tv-L|#e8vWe<>^$aOoIfA)DQ9_IiDcmVMIl{RjQ6fM# zLke$>Xs%e47$XA{Ln_NMpvKi8-C)4Tkjj|Cm&%sLl)~SFCKn|RRL_7s>A|G7!O9*gBYnIs7isxGi6D^lq`^jv5@H$K~z0aGO04@j8U>cOXQH%!Pu!B zsj?|TDZ=TDAiPFo87t6ntPGRe8TlARCr@K+;m~Bf#S&VaS~OXTsfbZvaxaq`t1wWv z#N-=H#*D&~d6+%}+_qDb_0mYAWIZD!s*=Sddx72Vx1q z)TAY5rxwK*r>3S9@dG7_Kt>h`Om=5+l@kFmg@D8_4jZ6Rr8%i~MS4IkD9DQ2C$C~z TEOdiGln5gO z6GJM?GN9tsARS=9$dJmI!kfyL#+1U>f+iOw3RKUQD!PV$86!{+5JNzeSgP0paTpJo zP8EWvtcM8#jc3Y|fbkYc!dS?3iU6ANQmInuj8W1+OJtDM!Pu!BsnRKeDMIOtAiPF+ z87t6nKnwvu4>JWbGiZuTUc}fkS&ylRk$>_+CO1|gptShpH%!KiLX#DkgBWEe7c=kI zWKJx|EaC-9-4ZG;O4ds(D9}qw%uX$eFHTKODdGc46oE7r@lP&gag`GWF$ICdFAkgB e{FKt1RJ$S_AQu!e#if&Puq>Y3%X*CkqznLB3PuC~ diff --git a/server/src/app/api/v1/__init__.py b/server/src/app/api/v1/__init__.py index 8204fd4..d01ee60 100644 --- a/server/src/app/api/v1/__init__.py +++ b/server/src/app/api/v1/__init__.py @@ -2,7 +2,9 @@ from fastapi import APIRouter from .knowledges import router as knowledge_router from .metrics import router as metric_router +from .users import router as user_router router = APIRouter(prefix="/v1") router.include_router(knowledge_router) -router.include_router(metric_router) \ No newline at end of file +router.include_router(metric_router) +router.include_router(user_router) \ No newline at end of file diff --git a/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc b/server/src/app/api/v1/__pycache__/__init__.cpython-311.pyc index 8c3a1b41b3eca31c024500b36d29e78ef339ec19..b0c6a39ec0309e84d9a56e833dc8d24c0b00a2e0 100644 GIT binary patch delta 173 zcmZ3@GMkleIWI340}$BrtjM%voX97^cxIxyCRYkm3Udy7E^`#eL_b+Z&WSmiKw_Gj zR4VfVPLOhRkjgP}jRzyw#IIgFtfj@NMa8$cLDXb(#xyP=pkhWKE?zWwIisD>4F-)1 ksA%$2Mr-*S3~bjKL@zOjPRPEn=>iND2>>+%03T{9C;$Ke delta 84 zcmbQux|)S=IWI340}#jy^ku$fn8+u=IBlZ3=EN9jM)rvnnm}Ta+Qd&5j2x4-8NDWV hGRAQU02MO=adFM$kBoMc9ht1TFEFS8Q4t?d6aast6sG_H diff --git a/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc b/server/src/app/api/v1/__pycache__/knowledges.cpython-311.pyc index e6bf0930efdd2cee72e46662b47f7bfa681cf944..f1ca81d029d6cb152bbd727d525dd6f8f3df0d8b 100644 GIT binary patch delta 1198 zcmZ`%&1(}u6rcS}Hk*%b^PMI!jbdGc3M%Nq3L=Q~pa(sK1*vIg8UxuR&TbA0(jE$) z%iPsdwbVnU|AAL86%@)|Jb4g2cr2cLGn-i3)M4``^XC12zxOc*g`HyZZ6XmvuvV>i zolImSS&*6=b61)Y!U${KsB1We8*^f=>6mWZiMt6WA@1~U(oH#2uH{(b9O+KGX(ufq z84(3X+tPbuQ$lDPe&KR5B=aWAK1k_H2*-}zVUuVpGLEmSn-IJ`h9D;gm^{bWlbFOh zIu>0RM}tE0m_mvaL1Ah_fn!8g*G|M>i>{ zon;4F9eC!WMfO}d*PtUZlVi(Z{*@1OOu7V+b+|aoUg|u9P5tX>E&|d#b(-Wx=O)3u z+NcutDw1oQ0gtDFsqq!|Xlfan~5qA)z3IPC~(CH<2Y@?ChOIg+!4W z5v@c;kt!$(g{AbNqTmP690fHUR76zF?AbhQuXMA!bKf_QZ?->kQOUe>-6Vn4-+bHp zX&q)NbmQ=9W5c&S+jl(2PkKo|<)tWLgqiKM?|Kt)s5YUljCm#0J{oU+6&eQzIA zLQdg}msjA0qbYflPOTB<{Qr+7xxS;Z)PZpf;L{<6c+bBHl8v1vsRdm63vG9qCIWYfbk1WR99EgkXV_6aA~ z7FPhUA}}~uUDhQPBRFb-Imbh z180F6vg^!KQ@(PRia0_wski}@Q6%L@XECK>UBi1?{%}^dvA4()ogQcq8Qo403LJT) z@A6PhCDL1aVWfj!iK~QC<)09Nm8cGX*?0X8>$Um1xD7sh7eNh%{9UJ%p1n?1H8qdl D^lQOS diff --git a/server/src/app/api/v1/__pycache__/users.cpython-311.pyc b/server/src/app/api/v1/__pycache__/users.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7584b8b108a89564a716c36cd70d2cab1be8257a GIT binary patch literal 2611 zcma)7&2Jk;6rcU@+MA7IC${s^q`{Pg)+n{<$Ds&PC5_WGrA@1n5V{v@>z%|dySABK z_aivMv=#I}sE@r6QZ*70;twbi2gGSD`CzRSDH1}0o2$a5C*G_-;w?omo_YJ`&6{~| z-uu1rw|;*Qg7)QiPqG;gLch{UyLns8i|-kP9w8N}jD~bZW*Ew|8mn_Mr}Hwedt{F; z$buv1G_UTHeYz-%&YahJbV-&R-lO&Ee%bHvg4U-8WF_v-`lfF6=VdRPwY z5jnyj7GYlXtus5~7WnWKMzhI-c<}L1d)EC`d)%6;gcT$wGJnUhx)!?>L%>NWgX5gX?DJkjm2d+8ij2qx5+nbon1HB*5J zzhXE-DNQigPZjCZV+Yrt`H1VLm3824ek_yx#^hyje7 zUF#)uvoiyp1tq6#0r#{s+hj_NF>bbN1vE@wOA$Udp{ANmWmHz>vdj*@#le0U&32D5 z{cRp0)l*^)tb(@SM$IN`1`f1Ktm@tIx&48>i%T2hi@9_j!kaduG&DIy$I(2Aw&S)D zTwPr~PE*?sxh=F3$k|de`>AwZ#kQbgQ^{(!H-i-wVr+9p@dgcaGpaY5S!eBDSC)F5 z_@~Uf1swB{KJeJ~wf(StZ7&E-0HKkyBNTwC)9;>0tt`#1EMK^CZBd?25_(tC4ajA#KT3{r5H=1aEcwE zPXJasj{smJ2y!A(La!3@IEv&=Dwv{#UKfJryg{n-;26{*z$e-ah%Gc1NgTv)z%VWX zX@OxZwtcKRcB(dZ${KvNm#^$efy$ZPFDz-HDlOEcg>rH)G+r^Qp@~{(qI{`-5YeUB zxGg|UWOM6;hQ#)+DNt*LETQ2g)T6GF2(}jE28OI>&AWuYD@3FleNcUvzO#g;+YSN3 zUVIPAgpN}3eaI3>>QoiN#v_9Cs5EIhJt`Un$R3r@{8%g>1|twDH0 zoBtMkA#a#Y+1UO(-EuNzq%&B@HV5xIybva70*nTeyh8~El!LsNz7N33qK40s_d!M> zX?z2;!7&U|M`M<|@1tq!zj_^AvR>EgXxwu5eKcXYdmY6scdw&@_5b?5IPh?!GE@;Q z@kmuXQWKAqg?+~R_2|9Pho4lL!5TBzU`1w@sk~=Ro_*$h=KWFpNwg*xYoiw%h%WqG zY<|1xa}KdXovn>Q)IVtjC$@7|aJm|tt_7#dern{ePH~YTi0JUpsuhfne=`(8D9`Ee!#&KF8bDDg)9;gXO6*5UL0McS#(vlg7UR D?8A*H literal 0 HcmV?d00001 diff --git a/server/src/app/api/v1/knowledges.py b/server/src/app/api/v1/knowledges.py index d0b0056..eb7d4d5 100644 --- a/server/src/app/api/v1/knowledges.py +++ b/server/src/app/api/v1/knowledges.py @@ -1,3 +1,4 @@ +from typing import Annotated from fastapi import APIRouter from src.app.models.knowledge import Knowledge diff --git a/server/src/app/api/v1/users.py b/server/src/app/api/v1/users.py new file mode 100644 index 0000000..d5e6252 --- /dev/null +++ b/server/src/app/api/v1/users.py @@ -0,0 +1,36 @@ +from typing import Annotated + +from fastapi import Depends, APIRouter, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer +from datetime import timedelta + +from src.app.models.user import User +from src.app.crud.crud_user import create_user +from src.app.services.auth import get_current_user, authenticate_user, create_access_token, hash_password, Token + +router = APIRouter(tags=["users"]) + +@router.post("/token") +async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> Token: + user = authenticate_user(form_data.username, form_data.password) + if not user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect username or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + access_token = create_access_token(data={"sub": user.username}) + return Token(access_token=access_token, token_type="bearer") + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/v1/token") + +@router.get("/user") +async def user(current_user: Annotated[str, Depends(get_current_user)]): + return current_user + +@router.post("/user") +async def create(username, password): + hashed_password = await hash_password(password) + user = User(username = username, hashed_password = hashed_password) + created_user = create_user(user) + return created_user \ No newline at end of file diff --git a/server/src/app/crud/__pycache__/crud_knowledges.cpython-311.pyc b/server/src/app/crud/__pycache__/crud_knowledges.cpython-311.pyc index 0fe5fe6c613acb8407b5df13923d4cd6f44baf60..955b230a1eb00f774290f187efce10c7aa346009 100644 GIT binary patch delta 306 zcmew=^+JkwIWI340}vQ~Se~h~k=KQ-z7oi5V7SXEFjcHmzDNE810$>D4EGx%($l>s zc`xN$VRKQ$;);kx2j>T7pbS_VOnd|>`~o5nV%S(ee*C~D{D52J0*gp7|KuxdW=sK$ zlNlLxH(RoEGEQb@kz{0<9LG`4CJl61@zu$@IWI340}x!{?aSP>k=KQ-z81)9V7SXEFjcHmzDNE80}HF=4EGNl45BjA zy(f7uKNQN-ekh(!nI2Tq_ISOJ*$2vYh5L?Fblv3~scflc@W7Xy#zg~?~wteAor zCo?kYZZ>7-WSqQ$OJ#BbM?RYz&|SrMCST>)Ie7sm>*PL8iOCI|oZ`hoK=G-LR!%a^ zhh$is6xa_bGXlxU0jy%1Z*kf(GHPs=;C{mB!p11NK==a#kU}SfI2e@|7=K^@QX7CO SfYc5M56ng)L?%1%Y5)NGdsE#2 diff --git a/server/src/app/crud/__pycache__/crud_metrics.cpython-311.pyc b/server/src/app/crud/__pycache__/crud_metrics.cpython-311.pyc index ba6d79b00e546a3479d334e0168d69e6d40d2557..ec39e233d95fa4491577c23017e32aa7b151b79b 100644 GIT binary patch delta 20 acmdlgx>b~WIWI340}w0|UcQlg6&Cb~WIWI340}$Mv-@B1}6&C3f?3 z!jW>|kVZ%t1d64KDhEhQxp3&ASMwLx$WpD9kSg`k-cr#6;?$X4XYH&_RZD$|XJ=<- z-`DskATOo2f?!sZ4k8&^pGB!BU<=R z99oksWzGK0{3~^nJZp!-ZK2&SRgiu+2b=M zFm>8C*q^(;Psu8+(5)W-ZX6*MvNx6zh{O^4+BR68yH-06cD29t_XxWQf7>wrb9WAu zl!Hge$3(Cj(oOj?xj`wpOep+S$Q*e_FKIrG)8M#bOU~)K<a>sJT<;7eEw(}cC5qv+ZOX>Z@Y9c127 z$-d~?=VBD*_vlPEE!Wd5JE_#CDM(AjMU`_UV=zI7fRAAaGJP;grst$|GR z#$R{)sgSR19`^RIDgl8#{qK$gtV*6pxgu{N0)1^8EL~Pt*^mUXIl=?bg{F2H6LV*htkVP90p(7wn0I6h@ZD7Sp(G->+utf!XNn_(-3{|fg zrbFBq+~U*XDF_d76SecNL|jwLo{ z9?aBkHDg*UrmZXO(Xq|sgXI0x!_>MT*_%L&9D(djAbUNwuD7do*z1b|_F`)nMGR*8 zD4RM<6}X(_m%Q?iLSt)NQ?~1xgRZbc@U?t56Q$t^stFS9wT>`&k`&}-;@9INx z;3ke94lEfyZ#!`~+2Jru0y0GsE?t9z1?DnuatN+0>uyDwfPfD%=z9+|liX6Ci zd*k-w6I%zH5v>)`)>(UUrZIM=rq%@8cS_b~IXEMQr2-#ZNG zHg8$6k2Wp=ZWt(gvxI##9B{DF;Q%xEXoDW7u=Mc3)6lb1So}Ji1Wkhn!J^3yB3KUW zbB@5z$gLScC)6n$li>LXe&PuT4;G2RSU4w@s@YV>Tk3d29rvDH5Q+r8Y)2pz3524@ z*7bU{4jJzc?Nkk4c3Q4av+Pl&K1)Gf(!!Q1zAl!QV0BI=#W!Z{hXdOz9N1=UhnU6m z;_O&-*8|^3?rIL~m|*Oan2mXq@IV zV8e5>!1S~uQ%c)pvN7oYw?1Q=9BJ(R-(fx)t?D}jE?yD#(O6ZWHRcuI8bw=2z-_d) c-+ul_*}lB^#J+e%n9&3V!sWe!ABBbe19dci1ONa4 literal 0 HcmV?d00001 diff --git a/server/src/app/crud/crud_knowledges.py b/server/src/app/crud/crud_knowledges.py index 1b64de5..e6182a9 100644 --- a/server/src/app/crud/crud_knowledges.py +++ b/server/src/app/crud/crud_knowledges.py @@ -20,9 +20,6 @@ def read_knowledges(): def read_knowledge(knowledge_id: int): with Session(engine) as session: - #statement = select(Knowledge).where(Knowledge.id == knowledge_id) - #results = session.exec(statement) - #knowledge = results.first() knowledge = session.get(Knowledge, knowledge_id) return knowledge @@ -36,6 +33,7 @@ def update_knowledge(knowledge_id: int, content: str, uri: str): session.add(knowledge) session.commit() session.refresh(knowledge) + return knowledge def delete_knowledge(knowledge_id: int): with Session(engine) as session: diff --git a/server/src/app/crud/crud_user.py b/server/src/app/crud/crud_user.py new file mode 100644 index 0000000..028f6ce --- /dev/null +++ b/server/src/app/crud/crud_user.py @@ -0,0 +1,38 @@ +from sqlmodel import Session, select + +from src.app.models.user import User +from src.app.database import engine + +def create_user(user: User): + with Session(engine) as session: + session.add(user) + session.commit() + session.refresh(user) + return user + +def get_user(username: str): + with Session(engine) as session: + statement = select(User).where(User.username == username) + results = session.exec(statement) + result = results.first() + return result + +def read_users(): + with Session(engine) as session: + statement = select(User) + results = session.exec(statement) + users = results.all() + return users + +def update_user(user_id: int, username: str, hash: str): + with Session(engine) as session: + user = session.get(User, user_id) + user.username = username if username else user.username + user.hash = hash if hash else user.hash + return user + +def delete_user(user_id: int): + with Session(engine) as session: + user = session.get(User, user_id) + session.delete(user) + session.commit() \ No newline at end of file diff --git a/server/src/app/database.py b/server/src/app/database.py index 086df93..31406b5 100644 --- a/server/src/app/database.py +++ b/server/src/app/database.py @@ -7,7 +7,7 @@ load_dotenv() database_uri=os.environ.get("DATABASE_URI") connect_args = {"check_same_thread": False} -engine = create_engine(database_uri, echo=True, connect_args=connect_args) +engine = create_engine(database_uri, echo=False, connect_args=connect_args) def create_db_and_tables(): SQLModel.metadata.create_all(engine) diff --git a/server/src/app/faker_seed.py b/server/src/app/faker_seed.py index 5a31d65..bd6ae02 100644 --- a/server/src/app/faker_seed.py +++ b/server/src/app/faker_seed.py @@ -1,7 +1,7 @@ from src.app.models.knowledge import Knowledge -from src.app.crud.crud_knowledges import create_knowledge, read_knowledges, read_knowledge, update_knowledge, delete_knowledge +from src.app.crud.crud_knowledges import create_knowledge from src.app.models.question import Question -from src.app.crud.crud_questions import create_question, read_questions, read_question +from src.app.crud.crud_questions import create_question from src.app.models.metric import Metric from src.app.crud.crud_metrics import create_metric @@ -37,3 +37,4 @@ def faker(): create_metric(metric3) create_metric(metric4) create_metric(metric5) + diff --git a/server/src/app/main.py b/server/src/app/main.py index 9b8cf94..cb0d9f9 100644 --- a/server/src/app/main.py +++ b/server/src/app/main.py @@ -11,6 +11,7 @@ from src.app.database import create_db_and_tables from src.app.models.question import Question from src.app.models.knowledge import Knowledge from src.app.models.metric import Metric +from src.app.models.user import User from .api import router diff --git a/server/src/app/models/__pycache__/knowledge.cpython-311.pyc b/server/src/app/models/__pycache__/knowledge.cpython-311.pyc index 728ab7f235cbb2189bf64213a5d3cdb8c95ce5b0..5de219b8fd31cc4091c50869a763748b3d43f8d6 100644 GIT binary patch delta 39 tcmey&{+XS3IWI340}#{;EYI}b$Xn0E$h^6iNrsV;W%34QvB?LRjRDZ63PS(@ delta 39 ucmey&{+XS3IWI340}#BP-J6-ck++_Sk!5o)lMEvx+vE+*Vv`Rr8v_98qYFa- diff --git a/server/src/app/models/__pycache__/metric.cpython-311.pyc b/server/src/app/models/__pycache__/metric.cpython-311.pyc index 2b00308bc82f6e01edf9c405e48d0b5e293d2f91..4557d95e695ecb998bccf6b93531d32423657e20 100644 GIT binary patch delta 35 pcmbQpF_D9JIWI340}#{+EYED;$lJ-p$g+7llQknF%jC<(sq?Es)72{!-$ diff --git a/server/src/app/models/__pycache__/question.cpython-311.pyc b/server/src/app/models/__pycache__/question.cpython-311.pyc index f83ea2375e91107e409879809f233f53d0424a8f..b8b9649dc8acd99c24dbdd65509b7922cc2e006e 100644 GIT binary patch delta 40 ucmdnOxrLK&IWI340}#{-EYJMFxRLJ%6C=xJcIIS8MwZEiEMk)zS&RYHSPCxy delta 40 ucmdnOxrLK&IWI340}w2n)0-*4w2|)z6C>+pcIIS8Mz+a?EMk)zS&RYC>Ivxp diff --git a/server/src/app/models/__pycache__/user.cpython-311.pyc b/server/src/app/models/__pycache__/user.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4e0f82c8a252159d8b7433e6a3fe0a6d1b5b2d44 GIT binary patch literal 732 zcmZ`$zi$&U6t>TINfcVD3Qj8g2RFb8R0(vd7&?H2NF->o+<@iQMmgd8QTsv_rXVqd zjjap}OhpwP*!d?Ci6QdJ#1<5$PCVZ=2nl%i-t+hTp8dV&`?$W|Mj*G|pC(^m{bqxe zG%mn00&t8t;#i^-$2fIjCnYh#$UzSfcU~h-gu~r4@5Dvu6Id;=%Nu(rXnwb0K#X@k z5t5tM-s4A|oC|5zo(RdxB+v99DGn}9Rm1iBnT zO~z7THU>-&1g8bl`Y2aCXqgtJHbtpvQ<~;{C@o&2^l->zolyk%lzm-8lB_fvl(H<# z%X(g@Cc0FoXbA=A{7yK?QxU3sKgq%l%W|2p8%!1h7D5*PDb5H=1AG9CyZ@SDm@P zHFd4dDi1(iU0vP_u<=T^FGV--OiLfis*QR?`>T| k6x@PfjOXa~^#8hW3BI;Kf4j-uciYwe51ah?kBW-_7vJ=}m;e9( literal 0 HcmV?d00001 diff --git a/server/src/app/models/knowledge.py b/server/src/app/models/knowledge.py index d98c182..6e28b2c 100644 --- a/server/src/app/models/knowledge.py +++ b/server/src/app/models/knowledge.py @@ -1,5 +1,4 @@ from sqlmodel import Field, SQLModel, Relationship -#TODO : add pydantic validation class Knowledge(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) diff --git a/server/src/app/models/metric.py b/server/src/app/models/metric.py index 75a72ad..6671ffe 100644 --- a/server/src/app/models/metric.py +++ b/server/src/app/models/metric.py @@ -1,8 +1,6 @@ from sqlmodel import Field, SQLModel, Relationship from src.app.models.question import Question -#TODO : add pydantic validation - class Metric(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) diff --git a/server/src/app/models/question.py b/server/src/app/models/question.py index 4da7d02..86b5aeb 100644 --- a/server/src/app/models/question.py +++ b/server/src/app/models/question.py @@ -1,6 +1,5 @@ from sqlmodel import Field, SQLModel, Relationship from src.app.models.knowledge import Knowledge -#TODO : add pydantic validation class Question(SQLModel, table=True): id: int | None = Field(default=None, primary_key=True) diff --git a/server/src/app/models/user.py b/server/src/app/models/user.py new file mode 100644 index 0000000..355d511 --- /dev/null +++ b/server/src/app/models/user.py @@ -0,0 +1,6 @@ +from sqlmodel import Field, SQLModel, Relationship + +class User(SQLModel, table=True): + id: int | None = Field(default=None, primary_key=True) + username: str + hashed_password: str \ No newline at end of file diff --git a/server/src/app/services/__pycache__/auth.cpython-311.pyc b/server/src/app/services/__pycache__/auth.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..63c9bee5dbfc55bf6875768b84246537b5cf8d1e GIT binary patch literal 4463 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 MW0iGJ1T4?}534*I?f?J) literal 0 HcmV?d00001 diff --git a/server/src/app/services/__pycache__/user.cpython-311.pyc b/server/src/app/services/__pycache__/user.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..33ff4952371ecad4b6db9c371a8ad91ac493c7a6 GIT binary patch literal 4744 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 Dc=uL< literal 0 HcmV?d00001 diff --git a/server/src/app/services/auth.py b/server/src/app/services/auth.py new file mode 100644 index 0000000..0466b79 --- /dev/null +++ b/server/src/app/services/auth.py @@ -0,0 +1,80 @@ +import os +from dotenv import load_dotenv + +from datetime import timedelta, datetime, timezone +from typing import Annotated +from pydantic import BaseModel + + +import jwt +from jwt.exceptions import InvalidTokenError +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer + +from argon2 import PasswordHasher + +from src.app.models.user import User +from src.app.crud.crud_user import get_user + + +load_dotenv() +secret_key = os.environ.get("SECRET") +algorithm = "HS256" +access_token_expire_minutes = 10080 +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/v1/token") +password_hasher = PasswordHasher() + +class Token(BaseModel): + access_token: str + token_type: str + +class TokenData(BaseModel): + username: str | None = None + +def authenticate_user(username: str, password: str): + user: User = get_user(username) + if not user: + verify_password(password, user.hashed_password) + return False + if not verify_password(password, user.hashed_password): + return False + return user + +def verify_password(plain_password: str, hashed_password: str) -> bool: + isValidated: bool = False + try: + isValidated = password_hasher.verify(hashed_password, plain_password) + except: + isValidated = False + return isValidated + +def create_access_token(data: dict): + expire = datetime.now(timezone.utc) + timedelta(minutes=access_token_expire_minutes) + to_encode = data.copy() + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, secret_key, algorithm=algorithm) + return encoded_jwt + +async def hash_password(password: str) -> str: + return password_hasher.hash(password) + +async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User: + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, secret_key, algorithm) + username = payload.get("sub") + if username is None: + raise credentials_exception + token_data = TokenData(username=username) + except InvalidTokenError: + raise credentials_exception + user = get_user(username=token_data.username) + if user is None: + raise credentials_exception + return user + +