From 7bd590b424651b3b94cbfd4dacc9b97aa1ddfa12 Mon Sep 17 00:00:00 2001 From: Roberta musi Date: Wed, 3 Dec 2025 04:03:16 +0100 Subject: [PATCH] user authentification --- .../app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 170 bytes Backend/app/__pycache__/main.cpython-313.pyc | Bin 0 -> 986 bytes .../api/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 174 bytes .../app/api/__pycache__/auth.cpython-313.pyc | Bin 0 -> 3210 bytes .../app/api/__pycache__/deps.cpython-313.pyc | Bin 0 -> 2245 bytes Backend/app/api/auth.py | 62 ++++++++++++++++++ Backend/app/api/deps.py | 42 ++++++++++++ .../core/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 175 bytes .../core/__pycache__/config.cpython-313.pyc | Bin 0 -> 1174 bytes .../core/__pycache__/security.cpython-313.pyc | Bin 0 -> 1767 bytes Backend/app/core/config.py | 16 +++++ Backend/app/core/security.py | 23 +++++++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 179 bytes .../database/__pycache__/base.cpython-313.pyc | Bin 0 -> 884 bytes .../__pycache__/session.cpython-313.pyc | Bin 0 -> 541 bytes Backend/app/database/base.py | 12 ++++ Backend/app/database/session.py | 8 +++ Backend/app/main.py | 16 +++++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 177 bytes .../models/__pycache__/user.cpython-313.pyc | Bin 0 -> 808 bytes Backend/app/models/user.py | 9 +++ .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 178 bytes .../schemas/__pycache__/token.cpython-313.pyc | Bin 0 -> 815 bytes .../schemas/__pycache__/user.cpython-313.pyc | Bin 0 -> 1350 bytes Backend/app/schemas/token.py | 9 +++ Backend/app/schemas/user.py | 17 +++++ Backend/requirements.txt | 13 +++- Backend/sql_app.db | Bin 0 -> 16384 bytes Backend/test.db | Bin 0 -> 16384 bytes .../__pycache__/__init__.cpython-313.pyc | Bin 0 -> 172 bytes .../test_auth.cpython-313-pytest-9.0.1.pyc | Bin 0 -> 6194 bytes Backend/tests/test_auth.py | 53 +++++++++++++++ 32 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 Backend/app/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/__pycache__/main.cpython-313.pyc create mode 100644 Backend/app/api/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/api/__pycache__/auth.cpython-313.pyc create mode 100644 Backend/app/api/__pycache__/deps.cpython-313.pyc create mode 100644 Backend/app/api/auth.py create mode 100644 Backend/app/api/deps.py create mode 100644 Backend/app/core/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/core/__pycache__/config.cpython-313.pyc create mode 100644 Backend/app/core/__pycache__/security.cpython-313.pyc create mode 100644 Backend/app/core/config.py create mode 100644 Backend/app/core/security.py create mode 100644 Backend/app/database/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/database/__pycache__/base.cpython-313.pyc create mode 100644 Backend/app/database/__pycache__/session.cpython-313.pyc create mode 100644 Backend/app/database/base.py create mode 100644 Backend/app/database/session.py create mode 100644 Backend/app/models/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/models/__pycache__/user.cpython-313.pyc create mode 100644 Backend/app/models/user.py create mode 100644 Backend/app/schemas/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/app/schemas/__pycache__/token.cpython-313.pyc create mode 100644 Backend/app/schemas/__pycache__/user.cpython-313.pyc create mode 100644 Backend/app/schemas/token.py create mode 100644 Backend/app/schemas/user.py create mode 100644 Backend/sql_app.db create mode 100644 Backend/test.db create mode 100644 Backend/tests/__pycache__/__init__.cpython-313.pyc create mode 100644 Backend/tests/__pycache__/test_auth.cpython-313-pytest-9.0.1.pyc create mode 100644 Backend/tests/test_auth.py diff --git a/Backend/app/__pycache__/__init__.cpython-313.pyc b/Backend/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01b4a738c752b17b2db644d49dee260f6d2ad964 GIT binary patch literal 170 zcmey&%ge<81m%6|nIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4O(*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(TN2GG4b)4d6^~g@p=W7 gw>WHa^HWN5QtgUZfhL1&E(S3^GBYwV7BK@^056m)I{*Lx literal 0 HcmV?d00001 diff --git a/Backend/app/__pycache__/main.cpython-313.pyc b/Backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f145e97118933f28ed3557dc93a9a3689691fa8d GIT binary patch literal 986 zcmY*XPiqrF6rb6@N!m?qT1n6p14 zLp|1m=qC{U27ZK|WOE7(1S+1qRdVs@%rY(0k*OB zzjw zQB>lcoE3tI&&kIBvwOUL+jWR8T#*K*NQAIhnO~3MBC6Nf@J``LZewk?xV`dtgULLc z#ciitZ&fhAe~V(mv_8i8gBG9NE}FXqCfD$JX+iITLda-v%EN|RY1Q#ENpn?v6z>fe zXEKDa3oduTR2QVWV6qFQ&oltlBjeEMfK(f#K2?2z*MDfn@uSXorp>1|b0D7rxdRey zkT|~kaq<1)mm8g#rS{B{pIQEf{pt0S&6E4S@b8{N7)Pv literal 0 HcmV?d00001 diff --git a/Backend/app/api/__pycache__/__init__.cpython-313.pyc b/Backend/app/api/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..177422274578c98fc1c55dbf0ff61298ccd8c884 GIT binary patch literal 174 zcmey&%ge<81m%6|nIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4O-*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(TN2GF^L74G4b)4d6^~g k@p=W7w>WHa^HWN5QtgUZfu@6OF9tC_GBYwV7BK@^0RN0E)c^nh literal 0 HcmV?d00001 diff --git a/Backend/app/api/__pycache__/auth.cpython-313.pyc b/Backend/app/api/__pycache__/auth.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a5d7f54fd67f71f7a5ab8a44c595fa4c013aea53 GIT binary patch literal 3210 zcmcIm-ESMm5#RgZkvvkAM2Zq6Cho;H;u47^IhE5WjRQ!g9f=GqgD1jHMT9+(E9o5Z z$m|^>Q$PS22w(?!a34}2eQF96sQnPsk4+GueGT$rS_McPtj1~kk~gXHAJCa4kF*5` zC<=5y-p&CKo2{ALda22=v{kI$#f@c<$J#EC~R=XCfhfso%2o#;ZAWCSK?$ctGq zBQYr>Gdbg9zKp^YE|;?YOn?PAFK2@pm8qQfWkZ=T3v*t{4rC%M!g+soFf+u4I3LIk zXQC_$^BA88vm==}i)*AH9VchI;jq!dXl|-Id1BDjLQD_MiUs*NDTH&=Pb_uWv+x~A zdU#UM2MVelDMaN=L4hgtj&nrTeJ}9dW&&yg162l z)<;tDA276(;7W5=BPF}Rxyy^WYTcpC^`~i#T4mcESXf@ZeBpYD)*Q2Hxr*%=PThug z#+T;m&c-X34cp$TvT}~TU#GTnvC1m2=+~)jgFdiwnbuH0VAE2anN9;`d7HA7=&CrL zXVd_f;Z7*N5qM_oYI$iTwZvV^z{WodNzZe zZh!R7!V>f1PrG6RYGWnj>F9zk>e8%8g%#W$PPvH3^~d7`BEbDPLY{X^TETtb^z2yk z63C;TPJaaHNPp&Wx;!geGVJG=C+UxQ+4HIq&=~Kpc%1j$d_Cz&`{Pp<ABtL8L9Cnp8a})BGCE$x(--x*9MV#4~xEhkXSbDvXM^Rz5YMF&Jwcm59 zwZgUPY6XgIZn2Q@xYP4S2`a8!@J6FnD;PDiU_g0K)*5cWt3-AR%n!id{sJC5WIsy6 zk=w6*{Mx7KPu^^Wv}Q>A!^j^eem`+%>rWrv{jmAM8@r)%tq==^Tz!cO|{{?Nln zvYkk_W0wwm(xAFeq@em(C1XEnjZQxpoxV5LIy2WiGq*cBzq9l(5^YD~w<{l4+KE%` z=y)r7q8UBWjz8Cmr<(E9LC`m-?7X!fCO-eoi#IOb`uRsoZCSawcw_OSEHarJnOj?b zlgHYj=+31>2$*auQ=8Rwz>44+pj5GJI7X>g{IHIP#Np!W|36%KjSl1L(o=BJg8*+8 zprp$_1sBkA{x@jIi_a?ev)%ph;HV7AxDxkbSQ+)W947Y>>VVa>0N@}|0qkQoAEBOI zgt?ww-KQ(M{|L-IcjQ%=0?die0$M#VD_Bxq(u2A>DFR@Gwinb+d(xnUYKCL3Zc@#` z&Z6-Ycv*uo)C@~AN+sx2-6cp1ZLJP%gleqQ&TMr%xoDNDjM0*#aa_2e8ZIGf zbTA29q#MT`U!0jfQ_NqQ%P%i1co@(w za3crReU(I_x8J?}E@HC57zhcQ&Z0bR5>d*3D)GyU$?EKE#j}j-_qsIWK z!U_PDPhpt6mr_Yrfip0zb%t2s`qvETA~loY*E0m*CpLDdyEy6HeHhlg8?oQtG&V~c zw9=RaBw~0saQ%S0lV}KU$8G>;kax6uj5y_&AA6GHK3)ghA(Yx^$LNX?ojbQGor~S! zCslX`F}y#(b*XdDlWSEQat@mkT&WP?`61mU{A)WW8F&Lc*Ei{#EDh^;g|fef$G#*8 z!X6p@j-1;i=k~~}d*t{YN$ip5_lWi#`NbYN@qIA*NpeSdB=~P8ZzON6+?m)(-WO&L z6cU^G?BwrW`t3`*(HH;i8y8}a0)zWF?kgmc*!LqBAWHCY5V%8$h-ddjl)XyC05^5@ tbBd;3{ysY1GxW{Yw~Yg7L{QpbjE_Hin~#rtfyX|OqoLCWB5?fc{sUXcyB+`l literal 0 HcmV?d00001 diff --git a/Backend/app/api/__pycache__/deps.cpython-313.pyc b/Backend/app/api/__pycache__/deps.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3b9c8ae52f3958bf82fd4a7fd4a76a4eae322052 GIT binary patch literal 2245 zcmah~O>7%Q6rT0|c>V9hcAKU&b(<)`fjB=+MOtV}8YfNck`}K`09#lad!0CSyzcC} zB@u^!IHXjlP+FA>QV@ur8%Q8=MB>INRt~X7iBiO=oD!u%72?2~wYQEC5+i%xH*em1 zv-95fX7^n#JA$#Z)?di;2>nhc=CC%v{vQDDBMC`N3`H5tL@n47q;pG*jdGZa@|ceb zSTJ;K%o-K3Xkad8i`ub0>c9?T&c~cl7j_v~h`FO4>@l!4=8d-C76Xg1)~FBr3~Y<} zqX8TUqO^Dtr9JWI%~xwmH(?^$~RmS%~eDQc|$2tC)ya%6dU9$;A*$tP!QGlyVwz zO(qgo$KTE>#u^bcUDhjFh#~$fqZNIA=&Gz~H&vXwsK{8s5RhHCsS`0elNiTX#b9Wa z6itJ;07XsF^+IWm`a2uy%W4*OWC)kkz@H(ugnCUWMPyxGcJ#}zMSoG9D;VB7T}k31 z5wN0Ha4FkN5keeHI^n;s8z2bP>_DkTW)M2vuq~k^x`FO9L6C8_p*$c=yC9lFC(z9Q z*yPbU_BhHjFR*zg#7u`Q*afi&pDn7I0*A5WW;OZ>K{Ay+o2Dn!(z9x*kd7$YHC-*I z7u4BBSyx7<(oqxjU6iwM&Rp7*a=DzA%Y}4KDQn^K5)tMUJ(HWohafQBPJ0#}kI)L* zwQ#&|%XQ@TTdS6u+xvm|P_*9>Z;2n;pR8Ky;BUonXrlJNf|J}okkZsst2j;mzlOv}7Kt5TB<>7bvOpY zlw?vyn^Ile*<3NvfmOGuYs!8`;$a^)$;MlnS`$fv*%T!%$FxN=_;11-Ym#duL{5`e+MN zMHW_x(CA^7IO<)V$*MU}=M-Ho6p3|Sk#j&`ptW5t&Z)Sd&o63()he@~;*~`(3VAUt zGp5%n3DBz}GdwVuNluR@6O&isQ*Vw(u#K)kP#PT5LKC|*J{BKOWMboQ5ZmbGOJJCo ze1)*UbL^qvC@~0sy`tbHOp8y1e4(f-7}J~)K3~9^9~EZEz3(q zRnFD@>;~5}kQte9722EhwS)D612m48wpzA=F(fHNO)$NlY3{XO;jw}q3+PzymcM<| zKT!1#)I9yQmd>@;*S+CAD|bv-=Z@^!ZG6{OYv4ie-QJqB<=)7que<8&{$X-+c%nKy zv3}~(9&7R0SFO7OI&|zo>f_W}U?b48%GVs;Tjy%6J?qY%tw6_GvKn}9)xIktr~A&x zt&w|?+vhhO!Kx$prT^>p&)dIQ-t50nh2Mz_8;+MZ9TQc@#D-&XB~r6G-hHR$?pzl; zYYxv!?8##na{BiX!*|uSyDY%JG+KzyMq9^N^b6}6^Rm;SqeRe`%5Ym_+WnZeII-qs zphmfX;X8^Bl-k?ShcyMVRnV6(?Qy~_z%^(Tyk0`LcD*PUv-8U0Qdq@{*g=gvaKDAK zDpuf8$`|J9obXdEEzPW~DG=89gw$_W!(kEFQ`EvnO$V+3yf(g zJ7}nihPKh*4(ixOC$~}8HtO3(!EMy@o44z`w#~svbuh9%F#dCDJ^X6b8((qlay;Yv w$o3e4*z+P^`={NX^nY*t$+a;svBw@_yfx9YYslU{Lq6nffAXNmXJO-i0PKtcqyPW_ literal 0 HcmV?d00001 diff --git a/Backend/app/api/auth.py b/Backend/app/api/auth.py new file mode 100644 index 0000000..4cbda99 --- /dev/null +++ b/Backend/app/api/auth.py @@ -0,0 +1,62 @@ +from typing import Any +from fastapi import APIRouter, Depends, HTTPException, status +from fastapi.security import OAuth2PasswordRequestForm +from sqlalchemy.orm import Session +from app.api import deps +from app.core import security +from app.models.user import User +from app.schemas.user import UserCreate, User as UserSchema +from app.schemas.token import Token + +router = APIRouter() + +@router.post("/register", response_model=UserSchema) +def register( + *, + db: Session = Depends(deps.get_db), + user_in: UserCreate, +) -> Any: + """ + Create new user. + """ + user = db.query(User).filter(User.email == user_in.email).first() + if user: + raise HTTPException( + status_code=400, + detail="The user with this username already exists in the system.", + ) + + hashed_password = security.get_password_hash(user_in.password) + db_user = User( + email=user_in.email, + hashed_password=hashed_password, + is_active=user_in.is_active, + is_superuser=user_in.is_superuser, + ) + db.add(db_user) + db.commit() + db.refresh(db_user) + return db_user + +@router.post("/login", response_model=Token) +def login_access_token( + db: Session = Depends(deps.get_db), form_data: OAuth2PasswordRequestForm = Depends() +) -> Any: + """ + OAuth2 compatible token login, get an access token for future requests + """ + user = db.query(User).filter(User.email == form_data.username).first() + if not user or not security.verify_password(form_data.password, user.hashed_password): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Incorrect email or password", + headers={"WWW-Authenticate": "Bearer"}, + ) + elif not user.is_active: + raise HTTPException(status_code=400, detail="Inactive user") + + access_token = security.create_access_token(subject=user.email) + return { + "access_token": access_token, + "token_type": "bearer", + } diff --git a/Backend/app/api/deps.py b/Backend/app/api/deps.py new file mode 100644 index 0000000..67e9d26 --- /dev/null +++ b/Backend/app/api/deps.py @@ -0,0 +1,42 @@ +from typing import Generator, Optional +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +from jose import jwt, JWTError +from sqlalchemy.orm import Session +from app.core.config import settings +from app.database.session import SessionLocal +from app.models.user import User +from app.schemas.token import TokenData + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login") + +def get_db() -> Generator: + try: + db = SessionLocal() + yield db + finally: + db.close() + +def get_current_user( + db: Session = Depends(get_db), token: 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, settings.SECRET_KEY, algorithms=[settings.ALGORITHM] + ) + email: str = payload.get("sub") + if email is None: + raise credentials_exception + token_data = TokenData(email=email) + except JWTError: + raise credentials_exception + + user = db.query(User).filter(User.email == token_data.email).first() + if user is None: + raise credentials_exception + return user diff --git a/Backend/app/core/__pycache__/__init__.cpython-313.pyc b/Backend/app/core/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..88830ec3dd8dab7991ce8b6a66331aa8658408e4 GIT binary patch literal 175 zcmey&%ge<81m%6|nIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABz4O#*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(TN2GG0FKwsWI{KnR%Hd l@$q^EmA5!-a`RJ4b5iY!Sb@fatS<&JJ~A^hG8QodSpYU}EkytT literal 0 HcmV?d00001 diff --git a/Backend/app/core/__pycache__/config.cpython-313.pyc b/Backend/app/core/__pycache__/config.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6782fb11f06221d67dd228b55a71e3c6ad0002fa GIT binary patch literal 1174 zcmY*Y&ubGw6rSzwrrR_>TD7&>YKtNzmNr(&28$R!L2f~Ve;6udfbH&Hr=Z@zEen|;4#y6JQh$@TIm?_~xM`U#@H z^d9NnmE;p5h$yS5tT1H;_`vEwS!L=Bnm|_&Q7PUxDYnH8t;%=NcLYQ0`7LshVb}-JLH@(nZ&gb(BdC95MYAtMUiSy(^ z#jF&Hcx{v3Gc9uk!fw<{r<$)sl_S)hkmPjpq4h+S6V-CM$o3nuPzd}d-p+Aa`deR7 zHJ1G`f7NOPtk?*`?r&%Mxl;fgUz zdFX^*gF0czysf>^<&?6sGE5h86qUxZy}@0^?XAG~?0YVM76vW*X@4^IzZuQp0IA5y^Fx*anFW9=761MMFo+R{oCKEW1Gw?3gmU^u4^UOjj32Hg?g*)dUpcr*t@IQ zb-`5+9NTN8C>&ii2O>p1^^ju@IrNCdfocRztM<|x70tbG);7?d^0Yhey>E7AzIpS_ zp2lKP1lDZbFrV`X{lSu+g}TA%j}SaYGLpFh$|HjE9O05I4-^7Boh`WRmj z^C1%QW1$evM@YnvgN0~bB2p45k#Qu8H#j*YhZh5+Pacw^ilD?4em)R{m1Ic?Dx4qn zDY1D`j$K7np~{hdxnGINad`WbIN$>eCj)ZAM`H7WJm@2djYxX} z>!O0z@NAerDYZt%iHixtF=c z?%bt7S?b{>v;b_G$$`0m#h3d!8Sw1yMdyATmG}#*44n7$q6@u)J?z}e{334&B_V~< zLJ^K5R5z`Lhh3;Y4+W+?lpElPR@<2222QNl_}#fGO__7)K!Ax!CXG?UHdWLaHX zDddW3?*3=Xt2y=Fa!VoSFTOrvrxyP8{53%PX~$$WZeWp#P&?!9!7 z#Mz8&Bk-wz#Go%*?T(T<-cUhJE~|5r87=C!l#+{yt5j^uR6fD(L(g`Us$HprY%Aq8?eRZoYSO3I>9hd)pQ-ZuIkw^guDmKMc{P}Xm-%A zq2Z&%l_z75#tstWtuNY%555!sJOiGt|FnI0v)qPfLTM+IZ^hp(zx&kPyZOU6?Sc87 z<)gt%Pi7v??5;l#A5PzHPv3sEeRyZR4bR}`JB44Qk$qw06xKns->GS)r8=7HZrQ}3 z@lDO$#Ksx$AIY=h@Fo8~1dlI@oT2}-``kiT`YH5`E5eye&X%F-oy$qZuP`wmG}8fc z9R`vVG%ou-nIuVYG~VeyBSO-E4#DfPW(OTfBZtzpwsdXRI*^w3g(Wi1hJ|}O(Yrba zutWN-iJ#w*6e5!_8aPgYqXMfa{;+0nK%x_kFP zn%Ea6e6pR%2Yt!=Qm3NVbl`VInDz*}-h}0{E zGx&kI;oljbG=ZNH^2J4kifz~YGWztMp$>^8n;`sQ-8LFEyhP?9WqWhKhUSFlIPNu? zevPKuXzCaZAEW7Gl={8@YU^(6){Dt@e`ZJeTL^RSv@W&Wy=-gy`>_)Q>C4MMr`wq| rKi>aRf5WhoVUGK#l|4b=UR?Lx%hW$CI}tfxRR literal 0 HcmV?d00001 diff --git a/Backend/app/core/config.py b/Backend/app/core/config.py new file mode 100644 index 0000000..5414eed --- /dev/null +++ b/Backend/app/core/config.py @@ -0,0 +1,16 @@ +from pydantic_settings import BaseSettings +from typing import Optional + +class Settings(BaseSettings): + PROJECT_NAME: str = "JobmateAI Backend" + API_V1_STR: str = "/api/v1" + SECRET_KEY: str = "YOUR_SECRET_KEY_HERE_PLEASE_CHANGE_IT" # TODO: Change this in production + ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 8 days + ALGORITHM: str = "HS256" + + SQLALCHEMY_DATABASE_URI: Optional[str] = "sqlite:///./sql_app.db" + + class Config: + case_sensitive = True + +settings = Settings() diff --git a/Backend/app/core/security.py b/Backend/app/core/security.py new file mode 100644 index 0000000..cdb8e13 --- /dev/null +++ b/Backend/app/core/security.py @@ -0,0 +1,23 @@ +from datetime import datetime, timedelta +from typing import Any, Union, Optional +from jose import jwt +from passlib.context import CryptContext +from app.core.config import settings + +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +def create_access_token(subject: Union[str, Any], expires_delta: Optional[timedelta] = None) -> str: + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) + + to_encode = {"exp": expire, "sub": str(subject)} + encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM) + return encoded_jwt + +def verify_password(plain_password: str, hashed_password: str) -> bool: + return pwd_context.verify(plain_password, hashed_password) + +def get_password_hash(password: str) -> str: + return pwd_context.hash(password) diff --git a/Backend/app/database/__pycache__/__init__.cpython-313.pyc b/Backend/app/database/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9516d58ee54b0e0076417dfeddff825cb77bbcb5 GIT binary patch literal 179 zcmey&%ge<81m%6|nIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}q&*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(TN2GF)4{9iAjmYsWI{K pnR%Hd@$q^EmA5!-a`RJ4b5iY!SbVTM-DlxM1-+(%REFp@Rx&__9ik(YR>f!Ty-+Q*d_nz}gWd`(m^Jy(!Ljb>xGg^6Oz&{BSd+e$v#gb-IM7u z)ffr+0}ZFJ3p>-$rLi*+c}Yz>v!csx1r|3gS8kHMV%+p0N`x@))Kx2bh<63!0=Kg? z##>Ar%B+VEvUZnBw($%%C)T}98XdBBwu+SUbO1C8E5RS3~c!p22aO|^|H zoRK0HG`xj#zxv_${qZ+{@vFah<}ZIKFXuV{pP?1Z6q;uU>1LgNVmfq#kfT0L##j4< zyokAwNu07YBZQkaD9zF|&7_`emI{5y7Ls%GulP+9Um!%7U^JR_-0n-Jv-rG8s2cDM zlqO*2B+Ojj!Fl=m$&>H1b0^P-9(eOZ3%M&Fqa5^>+nUG?QQTpUw*pUja?p#@y+T<{ z93?b~_E~q(V6SE4zaPlWGlj^}%U9K?@}r^!kNF*)XXYkWH4JTp&=0uv6SjUK{|wFj TwMuAZ2pY5?tNO>dBSHTF>eJ17 literal 0 HcmV?d00001 diff --git a/Backend/app/database/__pycache__/session.cpython-313.pyc b/Backend/app/database/__pycache__/session.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b8557830ddca6d23641e8dbec34c1856e485017e GIT binary patch literal 541 zcmYLGzi$&U6t>TImmj*&f(k4QRTT+kAb`pegeqNW3rzy3H64&FPL9vL9J#ZD?F ztplF{1{e-NA1b(7VQ=8|kwX758uXtCm|Vjl+&AP#$;su8pXaDW3+ z!?hk#^{n1${HaDe(6*T3)bNChEEn7nk>*QC=7*Be z;tQBN5wLsgF*(p&X)=-`CtF-kjGU5pa#XsT$GfDzK=(E&n{bhmMRhJnN(~)R&B^7% zbf-tQ{-l)Ytl%4J#hu_j66l^we-VW647AU{U6+3~ZvFJ5AAbAPZ=XDR_WkC``m1x~ bHNB7Fc?g#89M8T6-*!%y2A?8z!>RuP9LSw) literal 0 HcmV?d00001 diff --git a/Backend/app/database/base.py b/Backend/app/database/base.py new file mode 100644 index 0000000..670a4e1 --- /dev/null +++ b/Backend/app/database/base.py @@ -0,0 +1,12 @@ +from typing import Any +from sqlalchemy.ext.declarative import as_declarative, declared_attr + +@as_declarative() +class Base: + id: Any + __name__: str + + # Generate __tablename__ automatically + @declared_attr + def __tablename__(cls) -> str: + return cls.__name__.lower() diff --git a/Backend/app/database/session.py b/Backend/app/database/session.py new file mode 100644 index 0000000..760daf5 --- /dev/null +++ b/Backend/app/database/session.py @@ -0,0 +1,8 @@ +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from app.core.config import settings + +engine = create_engine( + settings.SQLALCHEMY_DATABASE_URI, connect_args={"check_same_thread": False} +) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) diff --git a/Backend/app/main.py b/Backend/app/main.py index e69de29..e224d31 100644 --- a/Backend/app/main.py +++ b/Backend/app/main.py @@ -0,0 +1,16 @@ +from fastapi import FastAPI +from app.core.config import settings +from app.api import auth +from app.database.base import Base +from app.database.session import engine + +# Create database tables +Base.metadata.create_all(bind=engine) + +app = FastAPI(title=settings.PROJECT_NAME) + +app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"]) + +@app.get("/") +def read_root(): + return {"message": "Welcome to JobmateAI Backend"} diff --git a/Backend/app/models/__pycache__/__init__.cpython-313.pyc b/Backend/app/models/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2259b16ff4b9c32e67075e8e8c96119057deb9ea GIT binary patch literal 177 zcmey&%ge<81m%6|nIQTxh=2h`DC08=kTI1Zok5e)ZzV$!6Oi{ABy}qw*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(TN2GF}e9EsX4_l@$s2? mnI-Y@dIgoYIBbA|r8%i~MXW#zKn^GdF+MUgGBOr116cs1#V#TM literal 0 HcmV?d00001 diff --git a/Backend/app/models/__pycache__/user.cpython-313.pyc b/Backend/app/models/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7d0f14b1273d73012e742ae856e0dbe4dfa61acf GIT binary patch literal 808 zcmYLH&1(}u6rcT0($rL|MXN|Dg6yFcKiX1}itz)QmU`IgX8Y-v_4YY<1`jU*RJ4V9#4L|bedl5CW$nRzps^TlrwB^wQk_G9DuV~M{z)KiMB%~Pg3dUjVWr6go z7jDYV+^t&$gy+P_dtr~1`UZH@Y*3^ht$IfVeBXd59Dxp*=Lxv zQvV(0RpGKMp^%Br`4x;=N>aatNh+q=%u>ok{+b|-#f$iE>!B+(GPfB=zWa=_P8uif z+qfB$ls?&Z8&hwkPFfurdG2&4N#Y7YG+?f*wVL#xEK=r13Sf_g`G`j4+Oc)E|LyXy zyxy;ms+W$}&u$I8VfB9h`Do$B>Fdu?|K(_I;rM8v59e0;+oSnwr?&@d!}+@(c42Xv zanCOC3hM?WXdTk9m+SsNr(#AIMN6w5Nl8=On^kGRn(}kaMY`)E#!HWipRolu1(dgf e$y&cPMNxjDjqhmV2U`B48_K;;5B?&t<=lVt@z8G4b)4 od6^~g@p=W7w>WHa^HWN5QtgUZfi{3#Pz+*xWM*V!EMf+-0NMgBg8%>k literal 0 HcmV?d00001 diff --git a/Backend/app/schemas/__pycache__/token.cpython-313.pyc b/Backend/app/schemas/__pycache__/token.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e17995a5cd81b70a534e1e9647585eeba6e3370b GIT binary patch literal 815 zcma)3&ubGw6rR~%*~G-w)}XXtgHmJ#^(2U>V69+NOb@HKfnmBC5zW2?0Z{NK4y?Lk8BJh3wayR}4_9tHI&s~!F zb0DWABq7}=eX3{=xwUWgZDse!RdSt#_67+ZX5X``f0R>tUGRE;dEHSX@MoA3P~#uu zMVzG~0p8pZTE57JGKs1J6)vlh`7OAg5=ImaiLyef>}|pv=7!cyh?LAm7COosxq)Zg zm)Vg_XAbyGTSSr6niuFYtc`C+|bJOB{oDC-l@;2x1CL&ozRycnh zD_taUD$@+Ywl0(buSnCZ5IBbhZ&4%Ke*3eQQEc0;ttP$3-`XeiFO|z7BR}^CALwsOd=qFCf34^A(fX}rN zuoZO?;R*sSQmr7YB3wd1RjLNpk#%dp!Wz?Hl+CU8&yMe(JpJ_OhqF~BW8MHZ?7;@( zbX1l$em)(F6ogkOL?ugE@ZU#_|FBx{W3>T!6pp?D@XMx@o|DaUa&6&Q^z!lEf`D20 Nl=e=VzX_P~@juRtx}E?4 literal 0 HcmV?d00001 diff --git a/Backend/app/schemas/__pycache__/user.cpython-313.pyc b/Backend/app/schemas/__pycache__/user.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..05e0e1c154c58d16d8dc5777d85addb39205e942 GIT binary patch literal 1350 zcmaJ=&1(};5P$obP0|l*E!L`S(Tc8Md$CYN1Y4{2`>^%)VA&+wxHj31^ETFAq)@08 zFTE55FJAp~M3C}8Jb21YNW`l%yGd;&y0E`DZ)V=SncvK7_V(r&e(yg_1zQSZ-)YnN zjJD7$pm4|><|s>SQ2}L~WOYeh)IeL*K_6$s>E}&6Kltu0aQFMOtgMIEX^8VyiMc>j{4)D)|*@%~!pk;>1u)FG#U{8*47({*ZA7 zio-y46wt0=mY{Qkn^X$VINEhw9Ubsyo^9)lL}x;dXVD{*>@3KaRXZ!KC&C9xprPZz zRcZK?!Me-_!u5)A@WOv6dr%hjnh$mK{mASdX}NCbRejf$IoGX5rFw)C*Bmfjo&rt}Dn|biFu+piqx} za_d1`6MrUGig!4rFL)se1OC7ln{ia*&!R%ri~aixe6b_ln)QmCepuq2)vMLGC_cqv z1*fVeYuk{)mac;=J#g~9)HST{;W^Xc47bJJN?Zo+FcmkSw5KfqL0G4 z&e`sbyNTw%8^^J2j!MnoI6TLV%ajeflFoO4!U{5CsrkX(wWGd)y_M9Fd`?wEw9wbQjqvYk z{ydavHSuR)ItFCQrk#;G)zzS^r5cn@g~zv_paE5=xhA*7d)4|Z3Y@pdf;%gLj`=5Lq05T653BhoG>Bbzic ziG8}u(z=^I&3iyoM*NB?gddusC|}vdZ|p`(*OamS?H0qOl~a|o2g5CfOUu$xKl78} HlBDz-cvT#w literal 0 HcmV?d00001 diff --git a/Backend/app/schemas/token.py b/Backend/app/schemas/token.py new file mode 100644 index 0000000..cbd8894 --- /dev/null +++ b/Backend/app/schemas/token.py @@ -0,0 +1,9 @@ +from typing import Optional +from pydantic import BaseModel + +class Token(BaseModel): + access_token: str + token_type: str + +class TokenData(BaseModel): + email: Optional[str] = None diff --git a/Backend/app/schemas/user.py b/Backend/app/schemas/user.py new file mode 100644 index 0000000..84ae085 --- /dev/null +++ b/Backend/app/schemas/user.py @@ -0,0 +1,17 @@ +from typing import Optional +from pydantic import BaseModel, EmailStr + +class UserBase(BaseModel): + email: Optional[EmailStr] = None + is_active: Optional[bool] = True + is_superuser: bool = False + +class UserCreate(UserBase): + email: EmailStr + password: str + +class User(UserBase): + id: int + + class Config: + from_attributes = True diff --git a/Backend/requirements.txt b/Backend/requirements.txt index 8b13789..035d20a 100644 --- a/Backend/requirements.txt +++ b/Backend/requirements.txt @@ -1 +1,12 @@ - +fastapi +uvicorn +sqlalchemy +alembic +passlib[bcrypt] +python-jose[cryptography] +python-multipart +pydantic-settings +httpx +pytest +email-validator +bcrypt==4.0.1 diff --git a/Backend/sql_app.db b/Backend/sql_app.db new file mode 100644 index 0000000000000000000000000000000000000000..61a54cd45fa19577ff0ea590466ad5e89a9a46f5 GIT binary patch literal 16384 zcmeI#O;5rw7zgl<5e!QdJuF-wPeMYxcr-B%Y;bXQGB+cqWx5%ffDks6s~-GbeluHy z2p&wll7Ew?Tl@6ww_Tpwo+pcpp2pKrQqT$8XNJMfi5O$1s%2GI<@dX>8u+6%+3`nn zR|S}RpRCra9jFEc0uX=z1Rwwb2tWV=5P$##An<GI0SG_<0uX=z1Rwwb2tWV= ej3NKWUjP9JKmY;|fB*y_009U<00I!$et|EdifRr3 literal 0 HcmV?d00001 diff --git a/Backend/test.db b/Backend/test.db new file mode 100644 index 0000000000000000000000000000000000000000..3cc300d055424567425a9693584ef6b8e389d066 GIT binary patch literal 16384 zcmeI&&raJg90zclbg)#~GBj1adQv5*1Z5QMGzpP{)m4OKO_OQtQl-QUOHET6L#y53 zz{~6@_C7oU2b@v_-C+_ZB=-Hui7n^nf1jJBblQ$of*q%$BvNdPyd#v79mWVDh1sL4 zX&CyQ_uU7*Z~UV!klI!CjZQG${vlS|TGkZ;0uX=z1Rwwb2tWV=5P$##Ankv<8%~1{v@+*bfo0DU=7c6xb5cox-&Z) z-mUz&|Eu3Ru>C`}&kxzE-fVGtH>cKbq6-275P$##AOHafKmY;|fB*y_0D*(xTqIJKxa zCMiEJGsY#gIJ+djASNq6DL1hs)zLG?3&zrQN=(j9%}a@a(Iu(HCB-rE@tJvmmm+e|E|ur!+3lf;WNd0|1+9d{e^8d6l>SM* zvcG%Iz2}~}_uQF#&bb@maF9R=|0Y$~=_TZ^$hZY~RyIVAkb6WXGB-{r$7+5|F!?wc z6R9VTJ^Z+L%tw8U7RLQ!0UBVmI3666sKjW`cxWt4!;JQhw~V#YRz~~A+r}a^0<@pD zX9K%Qwp|X$!9$!Z9pz~>EA1w7P!49Ja_Fc)JF=}42iz&v>xqMP+NEGb_$UW@Te=C2 zWnog=Mx+a!#s01S+|Hl?1BG=#fUg;=sVue6Q=?{f%FqkhGn#qLGD_JGjj1^>^ymlKF_$`cO3hu<^nBK>(T-*oQ31QmCQA!; zt8rbUw2;>n$M7@?4~f9vOaMG3i)5V_J<+ei@rU8vOZ-|(+b8}f{@@+|vj3OCzb)|& z@prQWq<_kda38nqA-|4{aKCLI68fe>15!vdS%BUgcR%4YVIIhmIQS(H6V zLG~Wy(f$*-S1%NulYK^ShCgG(6kASVZ|v=8z_ zRn2nnD*NnFej>ElD0iQ~3!V8LO;2grOg=;0Psg2`+SxR!?d7As&%vDPE7@F{bD!}_ zN#2!|rKE5W^y;D8G-HJl53;9(PX1<+dn_-U+WdKzJCpK-rQ0?cdoCL6kJKU zEy*+61muX^o(WVf6?_9r1zlZkUA2@CEcG)vYTU?p@qIGt>;K(rtiL0J>)&?e`c~F2 zcfR#E-RrK--Nn~>(`%#PP4ARrRa;APm%KeARc$T4fvrV%7H;j>x^#1fB)Tu%+LlsF zh1B)_6dr*os?8KkOQX-*@K`*l-BjmFMJ);L!y<47G!m`T%8t$9<) z8F?-EQ9SK*3?)q4Pc^e-=%%(J*aFBR97Y&=-U9Q{sKs;)gD5l(Q~bu@S?#E}KViD6 z1{3BAj}rX|gH2TG^W&|qJ!JF1K|CqHPm3Yj52dTEOC}ngW6GL+MwVA742rJsOqW(J+vDp zmtfaWzR zdMCY{{`CFDGiy@oUEgxLEGEl4lMYs-TxJrI*iX@`4MCm%BDnox~^cF3B2{w`k#+W0sF{O86bK^Buh`pu$2=?!8M$&E$ax-tG^ zamn-crnsbNK)y`3!8t`CTOi=e{pb|X2huKV;Fvd}kbpn}j_?iw`Z*d$*o&|azz(Uo z9ORUWWk51W@iKs>5YU^^euM!8bmexC2`Sb>Nwd9E8hB_*`>+=;0w`WC(Dx7q0jh45 zp2UVxgi{Eo5zZj62b@Lf9Kr_(-$OVL@GWkzmBk8d^$PQXlOT&TFvpv{;Lf$~?|go+ z5)O_F*u=ML@=l}PC|BF5lPMq*1 zdJ(ps9Wi$J1~v2@+E1A@fO)>e07e+6fK7aP&kCZc`lcT3b<+Dm=;)3mUZg*)b(?6$KDk`*t)occG zK6b5WcYrRqkJR+sg5usgn$GKk$qvP{p5k7v5N0oD-~GqujQo62J3(im3tmWK{sLfK z;5hCtgnUfCB#Fml?_;w6G3k0tc0VRPPr6fO68k#PxhC!?zjxx#p7Mcn-w1q=U-W+C zC9yrf*FK;B;z*@)qD-Psi1_pHPr|FDt3tZ&PnWUY_e}I~(e)OxcVJPd_H