From 076c4ff68fe5c069eecde1fa98817df40815b3c0 Mon Sep 17 00:00:00 2001 From: Narayanan Madaswamy Date: Wed, 5 Aug 2026 22:00:49 +0530 Subject: [PATCH] User Auth Done --- backend/app/__pycache__/main.cpython-313.pyc | Bin 0 -> 1184 bytes .../app/api/__pycache__/auth.cpython-313.pyc | Bin 0 -> 6041 bytes .../api/__pycache__/scanner.cpython-313.pyc | Bin 0 -> 1635 bytes backend/app/api/auth.py | 106 +++++++++++ backend/app/api/scanner.py | 27 +++ .../core/__pycache__/config.cpython-313.pyc | Bin 0 -> 411 bytes .../core/__pycache__/security.cpython-313.pyc | Bin 0 -> 4014 bytes backend/app/core/config.py | 6 + backend/app/core/security.py | 61 ++++++ .../db/__pycache__/database.cpython-313.pyc | Bin 0 -> 800 bytes .../app/db/__pycache__/models.cpython-313.pyc | Bin 0 -> 871 bytes backend/app/db/database.py | 15 ++ backend/app/db/models.py | 13 ++ backend/app/main.py | 26 +++ .../__pycache__/stock_engine.cpython-313.pyc | Bin 0 -> 20347 bytes .../app/services/stock_engine.py | 27 +-- backend/private.pem | 27 +++ backend/public.pem | 9 + package-lock.json | 17 +- package.json | 3 +- private.pem | 27 +++ public.pem | 9 + src/App.js | 117 +++++++----- src/components/Login.js | 70 +++++++ src/components/MarketIntel.js | 2 +- src/components/Profile.js | 173 ++++++++++++++++++ src/components/ProtectedRoute.js | 12 ++ src/components/Ribbon.js | 2 +- src/components/Scanner.js | 2 +- src/components/Signup.js | 69 +++++++ src/components/SmartMoney.js | 2 +- src/components/TrendScanner.js | 2 +- src/index.js | 13 +- 33 files changed, 758 insertions(+), 79 deletions(-) create mode 100644 backend/app/__pycache__/main.cpython-313.pyc create mode 100644 backend/app/api/__pycache__/auth.cpython-313.pyc create mode 100644 backend/app/api/__pycache__/scanner.cpython-313.pyc create mode 100644 backend/app/api/auth.py create mode 100644 backend/app/api/scanner.py 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/db/__pycache__/database.cpython-313.pyc create mode 100644 backend/app/db/__pycache__/models.cpython-313.pyc create mode 100644 backend/app/db/database.py create mode 100644 backend/app/db/models.py create mode 100644 backend/app/main.py create mode 100644 backend/app/services/__pycache__/stock_engine.cpython-313.pyc rename server.py => backend/app/services/stock_engine.py (94%) create mode 100644 backend/private.pem create mode 100644 backend/public.pem create mode 100644 private.pem create mode 100644 public.pem create mode 100644 src/components/Login.js create mode 100644 src/components/Profile.js create mode 100644 src/components/ProtectedRoute.js create mode 100644 src/components/Signup.js diff --git a/backend/app/__pycache__/main.cpython-313.pyc b/backend/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cd42198a04b9a4a9568a2494462df07f65aef6ee GIT binary patch literal 1184 zcmZ`&%}*0S6rbsCw=J|qTA&2f6a&Wk(KSXw5JDmv6WfTStG6a(=niF}-6b>IAaH?r zz#|+C9zE*GD@QNh%Ly_G0prOVrvHF%cDqy)on+?y-n@_fz4vBYi9{EIl_z}VTL_^a zQVCYH4O&kzLhq4*3|vG7%y3rXP%%_cnBrljs20L3T+o>2>1r`jh_YxQ#$s7ij+Od? zlpR^hbZ~&mQ6oHwAp+k(hGs-2F^iYurQyKdk!8h*>ez^Rr$k+oVTg;Du629@R1TM} z2e>1RZX*tHiAkv7KXKYWI#T{2=}$sO3NF}h^E5voh7d^7IZ+gzCjE&}TnS!S@{Ss?{>OTILCM%W15 z$D=5x%!x>ya!#srbL8(@W=Un0+Gf_`S=O*^%dX}>Bi1jIz(!(1f6alMUMHrxp+n^> zwBmA|%ZA)*bI}+5B*1SC94$M)wjp8+%35HKSvO_$h5V5{c>9Qio-Zlr8NTI$xC0^CchCq^2%Jyo5UrHFgg~ICiz>5 zD72f%d(j26ATC{i!3YHcL=uWx=zKyXExS@{m=tI?T*`#jmP(cSRd|i@I5YWEiRg6Q zF&i~H!$v`sZ;^k2<3hn0pQ7PY^xzDQoS|D68p7e-#7^QE4IZJv4@*rc$G&TcX5XD7 z?e4ZBJ=9~AIzp)vHFc66*c&?;81ki5F1is;|3VrLU8F8O_Z}Wh?N5E&I=p!}*p#ue tCrI&=>`ffZ?9Y74e~le(d8XnyR(CTynRn^8SAT^to^B~9l=P5S&~F!PA&vk5 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..df4a7b4c37355ebc73b8b054cbc993a856217bd9 GIT binary patch literal 6041 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 literal 0 HcmV?d00001 diff --git a/backend/app/core/__pycache__/security.cpython-313.pyc b/backend/app/core/__pycache__/security.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..12987d23ac382cc1860c6b1d622c61b1436ccc18 GIT binary patch literal 4014 zcmb^zTTmO<^{!UZY9&iTAcK(|OBNVQJOmEdj&V#PW3Z7@pb}80s8$wf1;Vh>++B?2 z&bXxgF!qljt_ME)!E~6Z^XJTDCi&_#ZN~kTDyLF6xJjp-$>fj3ox0=x^xTzpA$A=n zGrbz!d+xdCoIUqE?<1?#j9}cD5@**<2>qRG)M2awk2Yz9?w}}&QelKED}^c8>%zK_ z4(mcRrac5thxH)?HYl(@Yz#4&37N1-*;8Q_vp{1An?n|CQEjHy*2Y$nUK25t(>f0!|7gGH4g%rQ2 zQ1~0ZB-b4F>ts`slXxjT&&w>Cll+{-`6<~nl9kdKftw>Fp&yM6V4T4+J-aBuAsvYZ zVKI+}`=dwWBf)`D*jdkyjg1amN$?6Iu$iL+{gHvOczEDVnGL>iZX_}^cK!!fEm@qt z4Brf7`T48n?84MsIziwxg0dp(7N=w#o|5Z%A%SyQiBHC-j`zf~oG314a8fp6URuCH zqMEjfvq<@Y!h;^Y57QlV5KSs*GDWKCqagtBqH(0UQ=lgG>RE(Qp`CS{(kOM~x(K^- zqC${vRb86VAvCEJNpi*@&W?~kPg*rfU6hXMPwIFmhyop?Q&b97-C-DhVpxz;9j~NU zK-g8^kZ_rsOD8>GXHS;P&1JZxCxbmnUa<-I#gpRFb9@r4Y!M|+S`g!jOp=$4NnU~j zKO-BbIFUczBeP&VU?Yd-L^?ae<2V*MnW>5*9Lgc9c@C2+`1KgHN3wmF8*&ma%50U9 zPs$W865DxT$gg*fi#!%P=ecAu*ExzavwT7lJ4GpzxYQvgI6>fX=M3VP@D{b@@|0Yye{!|v+k!>cnPvNy z^I*x@QgF6x{H*9avCNk3`$~3q!S3E*igsV#byB9Kl`4==i)b%$p3u5K9D0*T)so6}-ifJhP& zyiAJ{mW|+eC8DO3kdv9j44=4^U4)3UW#_oGp!rF*&v4=lw%)BH?Qw~1me&)$^x;v;<4WW@PGE$3#<$=u|uJi z1W=}w ziI_)mjt+FYLy|;GC^)GO)kuPFoYGPXF1M2;5t5cqjUD5TQ{>Gj&?D~e$}y?+0C+y= zPFDG!Q~Bv}x=VABXIJfy?0HHh*h1IQP1;oaWQ__zN1&S_1&6;R1sA-=%%_C~Qfg_i z9_(3eZaM>1a%SGo%KAhmo5RF#vT;F52$@ApEC8D<>EoEFUx^G0fruVZ%DV_mJPbWP z6IV04>KwvPYIr4I>k^m;L2)jT;6*VmWuRi?SApa>{KXhdOQ>AqTw_<++m@xFzcS7( zN8_6N9e2^;-S|nt@$wDhKQzSVoA+?xaBsoUyKF4$ttGv)pm(nD>k}*S2l~TX7Tc}f zo4qUPP1pOmf+d*O2bJLQQ^WptS(gzpsm#h{tCy5=*|ac?Nn*?T*$i|X5=NCvE16K! zPu>ifz^L-72=h5iilA(*qWhHwh)EZhnG^^04#Jb9r)-Cw6d*kbCLxuc#+vzLCp6*s zXk=(2I5t4uGV#&i*m=25!H%DKW2k=zsn2rK43y3*X;Bho6Qm=L!I(0AiOyVx4grs=&U8I=2;(ODp^O@wr9N&j806ob^@fys~Cn@Sv z#D0pbpCR8vwEr`75a5T%^AI&ZM2EgWM?Y_9&Ld~J-kV2tpEK{;rQ@3Z9EsE5?k#AP{gXO*TYZI#zw=XQ4 z6?XV!UHw|%oxsNaVr|Egy=-l`b@ArK^^u~reaXD7H&Sm=cca^gtoNe-RMxL+4AiNw zZ8SANePw2-Gt_!=8|_2&na|mo{{p#N BI?w str: + try: + encrypted_bytes = base64.b64decode(encrypted_b64_password) + decrypted = cipher_rsa.decrypt(encrypted_bytes) + return decrypted.decode("utf-8") + except Exception as e: + raise HTTPException(status_code=400, detail="Invalid RSA payload or decryption failed") + +def verify_password(plain_password, hashed_password): + if isinstance(plain_password, str): + plain_password = plain_password.encode('utf-8') + if isinstance(hashed_password, str): + hashed_password = hashed_password.encode('utf-8') + return bcrypt.checkpw(plain_password, hashed_password) + +def get_password_hash(password): + if isinstance(password, str): + password = password.encode('utf-8') + return bcrypt.hashpw(password, bcrypt.gensalt()).decode('utf-8') + +def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) diff --git a/backend/app/db/__pycache__/database.cpython-313.pyc b/backend/app/db/__pycache__/database.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6679c7b572c3560dec7f8fbb962c4a289e273ad9 GIT binary patch literal 800 zcmah{&ubGw6n>N0O*U!TRI64i)Yukm4rWia5wy^vJtRe>YflE^WOtIL*&p%ErWo)L zJoy8EAoS!#@Z=vLc=TV8UhHB(@zh%(7Y`nsNm?yle6Vl6_wC!+?|U=-(a{vh=-(4t zZwSB-RUAe95cD@C*Z~VHQi38ul2tfXiWMo+0j4FbsG~l>S}9&kU?K};G6Utrg4|UF z8fAS3EZx$}h84d`F?y+Ut7)5D8<@gUk8sU*eQ(el$`bFSJ>BeWF0#om`+FXhBL(M=5dNWv;!+y$-6$ zL{oo>Jbw`8kDVW?aYtjv8UplCS7b(t&f5WUnfzL#j_Mvp@!>VV5mjg_(#*iYd+197 za7@w0H7{`5E?>g4vQ*;|S7bV%gpeN0^6|Frr! XQ|hMo5|g`$b6u^k>0|;Y<)^;^863ac literal 0 HcmV?d00001 diff --git a/backend/app/db/__pycache__/models.cpython-313.pyc b/backend/app/db/__pycache__/models.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7491bf3e407689fd96fb634e47a6b0ce42df9165 GIT binary patch literal 871 zcmY*X%WD%s7@vJ+lO}GV541k09)wk!_0Se7#Y2T+Q?0^QPk~{wJ59IUNBYgKnp;2+ zj{zz4*kirP?8=oyoRJXW4JQ-+b@Ks8*{WalZI$@3Vr?PYKSCamI`u z2<8xZ$Wt~^Ljk3sg4)nPo0o6urrt2Xm`9hp-C#lHHh>@ zWSxj~$8otuV-iL<40t(CTcL=aq}*&%kv#Ajj9Esq(8nYbvuWiSWf+4kB84GI(jET&hH3fNx zzD{55J^fL(`=#UUPt$|)a&K*D&-8c3$X@O}9?o7ndif#gJsVD4Il6taIGDQMds3)m z1N)v(n4ImuI?)G{3%!kDZK}V1ygjJhdAknR&%z#Ug@Sz9Ly!dEw`V;JCF~Oza`t6i{v#n-k=hrYOpHR3D)FH#GlSFDrN7FaAN| H8r%K_0F3T7 literal 0 HcmV?d00001 diff --git a/backend/app/db/database.py b/backend/app/db/database.py new file mode 100644 index 0000000..c97b2de --- /dev/null +++ b/backend/app/db/database.py @@ -0,0 +1,15 @@ +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from app.core.config import DATABASE_URL + +engine = create_engine(DATABASE_URL) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) +Base = declarative_base() + +def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() diff --git a/backend/app/db/models.py b/backend/app/db/models.py new file mode 100644 index 0000000..3ae74a9 --- /dev/null +++ b/backend/app/db/models.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String +from app.db.database import Base + +class User(Base): + __tablename__ = "users" + + id = Column(Integer, primary_key=True, index=True) + username = Column(String, unique=True, index=True) + hashed_password = Column(String) + display_name = Column(String, nullable=True) + email_id = Column(String, nullable=True) + mobile_no = Column(String, nullable=True) + gender = Column(String, nullable=True) diff --git a/backend/app/main.py b/backend/app/main.py new file mode 100644 index 0000000..dfc1fe3 --- /dev/null +++ b/backend/app/main.py @@ -0,0 +1,26 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from app.api import auth, scanner +from app.db.database import engine, Base + +# Initialize the database tables +Base.metadata.create_all(bind=engine) + +app = FastAPI(title="Stock Scanner Pro API") + +# Configure CORS for frontend access +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Include Routers +app.include_router(auth.router, prefix="/api/auth", tags=["auth"]) +app.include_router(scanner.router, prefix="/api/scanner", tags=["scanner"]) + +@app.get("/") +def read_root(): + return {"message": "Stock Scanner Backend is running."} diff --git a/backend/app/services/__pycache__/stock_engine.cpython-313.pyc b/backend/app/services/__pycache__/stock_engine.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2136e4cb4051d5ccc2eb08aec448519c58043b69 GIT binary patch literal 20347 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 literal 0 HcmV?d00001 diff --git a/server.py b/backend/app/services/stock_engine.py similarity index 94% rename from server.py rename to backend/app/services/stock_engine.py index 01b7e56..83ed439 100644 --- a/server.py +++ b/backend/app/services/stock_engine.py @@ -1,20 +1,8 @@ -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware import yfinance as yf import pandas as pd import numpy as np from concurrent.futures import ThreadPoolExecutor -app = FastAPI() - -app.add_middleware( - CORSMiddleware, - allow_origins=["*"], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) - # ✅ PERSONAL # ✅ PERSONAL personal_stocks = [ @@ -437,8 +425,7 @@ def analyze(stock): # ========================================= # ✅ API - FAST SCAN # ========================================= -@app.get("/scan") -def scan(): +def get_scan(): sector_output = {} all_stocks = [] @@ -549,8 +536,7 @@ def analyze_ribbon(stock): return None -@app.get("/ribbon") -def ribbon(): +def get_ribbon(): results = [] @@ -561,8 +547,7 @@ def ribbon(): return results -@app.get("/smartmoney") -def smartmoney(): +def get_smartmoney(): results = [] @@ -590,8 +575,7 @@ def smartmoney(): "all": results } -@app.get("/trend") -def trend(): +def get_trend(): results = [] @@ -623,8 +607,7 @@ def trend(): # - Full sector_map (too large) # - Deep MACD + Supertrend # - 6 month data calls -@app.get("/marketintel") -def marketintel(): +def get_marketintel(): try: nifty_news = yf.Ticker("^NSEI").news or [] reliance_news = yf.Ticker("RELIANCE.NS").news or [] diff --git a/backend/private.pem b/backend/private.pem new file mode 100644 index 0000000..b1af8f6 --- /dev/null +++ b/backend/private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtqxw/s/eCLosxX61Zvd0PYicYUoKjbn6gsxe+jb4p0hMNK36 +YsOqYqRPKIh7OosX9CaKDDn8ws6LUt5MAOjwTkNBF8P3bPJ+R+qVXeiqFBIAkRNX +VqxU8eoWNaoNDcNQWSxOIJJanv6UZHbxlv04WLtw5WUtYUbLwO2TcAYghmzk/UTw +p0YjQ0kw6O2E7iQ3i/H7aNqKlEoZoXHcsnDaUsIa3qy6LG7UkGz0LQ3RKu9RqoVO +I8DOV8i1lO3Z5SERiSulfhqzoaD2vWU/sXBRC+yJ3UxZvqIUpR6taToZxPL0aZG9 +XoxFcS2ywogI1XGKWJAYxosgbA8KxKilQFs+EQIDAQABAoIBAE2cThW0sxz6HHN8 +Ng4dLGHIXMho8LruPSS9N80O9e38pYPsNuknQIjQTmFmOxTQa8jrZtNy/5S0tDTt +BVWNoiteH5W9SK4dCNH9NKDFbR1B2MPKd9z3Ms/lKLJ8ITert8NrM3ZbN+09NEbK +0jHYO8xXNsv/hJxDu+WoCnAZ6k+x1IFDXRfV4MrTBX27t4xtfA5KeXQyPzotUeBn +VjVIoRqkCSIJLr+f6ATa+OHlfBjyTbBQscG1/0JNfDDQFmvOYyyc4zpVn0bAINw7 +klvJaXP++DRaZsvNnM1SGpsJ+U86O56ODkX4v2a+H3czhpOA+q0yePHQ/nWr73Nf +7WQYxdECgYEA1OVJZoNPxqVefooY9Tm5mDRXeRVdOormEI8GZkrIAG85w+MD9MMr +2s8FjXDniN4khSClpF9YunB8e77ufu0TgpqhHhXSnn/5JEQ4UO5wiO1EqBvhkFUJ +CXitjYehgwDc5WKvUfPxeMCMvF+rYoHhN1aYIczuN36fhDAXA6pxGyMCgYEA26iy +2RHgu0XNa37Z9KeJCDGNtpj2dYttRAua4UVudxPYn+Q2IdKZ9HPjgWPKgdUjAEup +uuIDhbk1zmQiquvyNMP0HX+p2yG8DX77cdajyvBG5p3BxUX1MxXx4Q6ICVAr4K51 +cZg3YWsIsCsCCO7DkgW1kFRlVptUMMmbJjNoXzsCgYAE32qaqg69YTOUedywYC3b +SfdmkhKcMGmrn1pqJPQG7oTH8v44L+9lBq/92MOz4kG7uk+QP45sVf7DZk9XIF39 +80QUyDMV5Z/yMI2JbKuutp+HqXu0Lf4S9WwjfSM5OF/V8DhLC+ZO+Tk/ZoEptAdP +mO/KdkJNitxjziX4s4H7OQKBgB77oJ51oxlHMz5iWiPkLbP2KWMEGF9kFzlt2Z7E +yFwLdJa4/dmvdv/ACOsLRFkj0xgLlBlEH/MQuMIv5aPuO++tZBV1GGRMUdYlfxoD +iH7rfVSyE87bm0ZlZgS0pAOMR2Qdt3saWVVoX4VZy6Ou6e8C1yVQgirBJhLrnPK6 +dZJZAoGBAK/Nob7Lrxi2kXuJbUOp+Jcir/sPeWE0ZPH9vnAdDkDLrUFbyNWJHHgI +tn1rrPCJeF3+gGdbKcrubHGxJMZGDh3qALs35L6195ZaGys6l+Imp1FxsSFROf/S +Hy2wbjf7fz/gb0U7dfD7ZtVPDvqdFax7NrEdvdcslA0IUiVPqGkC +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/backend/public.pem b/backend/public.pem new file mode 100644 index 0000000..830f7ee --- /dev/null +++ b/backend/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtqxw/s/eCLosxX61Zvd0 +PYicYUoKjbn6gsxe+jb4p0hMNK36YsOqYqRPKIh7OosX9CaKDDn8ws6LUt5MAOjw +TkNBF8P3bPJ+R+qVXeiqFBIAkRNXVqxU8eoWNaoNDcNQWSxOIJJanv6UZHbxlv04 +WLtw5WUtYUbLwO2TcAYghmzk/UTwp0YjQ0kw6O2E7iQ3i/H7aNqKlEoZoXHcsnDa +UsIa3qy6LG7UkGz0LQ3RKu9RqoVOI8DOV8i1lO3Z5SERiSulfhqzoaD2vWU/sXBR +C+yJ3UxZvqIUpR6taToZxPL0aZG9XoxFcS2ywogI1XGKWJAYxosgbA8KxKilQFs+ +EQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f00c45b..4d48ef8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,10 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.18.1", "lightweight-charts": "^5.2.0", + "node-forge": "^1.4.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.2", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -13958,9 +13959,9 @@ } }, "node_modules/react-router": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", - "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz", + "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -13980,12 +13981,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", - "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz", + "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==", "license": "MIT", "dependencies": { - "react-router": "7.18.0" + "react-router": "7.18.2" }, "engines": { "node": ">=20.0.0" diff --git a/package.json b/package.json index 0355f93..1724a3c 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,10 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.18.1", "lightweight-charts": "^5.2.0", + "node-forge": "^1.4.0", "react": "^19.2.7", "react-dom": "^19.2.7", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.2", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/private.pem b/private.pem new file mode 100644 index 0000000..b87066d --- /dev/null +++ b/private.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuUUB5HsRK6mvKyjAClLckNxDK69MZTW2Yn9qOJZvw3j0R4sl +IhoyBRAm1fxFYthj+klxqw0XKUR7F6DcDuK+3z+a+Ds/uP3tDYbnfT4cr9VEhgq9 +FoLi7AgfH98mqp4VkqDCuYubt4XRtfhpN0TuShT/xImmqLfxkmvZSP/syPoFytEq +X66QZ2QyT4gqzz9/ZMo7EFMeuKA44IBv6qeQzFxSBt0xpeyMXHChn8AeocaOySC3 +kyZBefzmUF2lkOyZKlaPC+0/mUb6ndT1OBfK+EZXzWEbugBWOg/NSy9pnzPRGjAY +qknWs1912+H7t2FEA8BCRZCOK85B05BYeZM+SwIDAQABAoIBAAdhNs6Vkl0FYMtc +z615mHySsYBDds0PQykQPzhq0Z/dKQnSgpOlrHlkJ8/dQRxLV0woccUo+9OyfR63 +UxyqroSxAWjC32psfu1LgtwLxdPBMH4QXnDPnN9fAXJhDqa8LkrNaYQ/id/ZDj3r +27EOZ1l/FaMMAp302cQF1L1iKFnm/ksN9s4G2+8UwrtUWum5LOeD6x22H8443uRf +Lg4IvMC4nLYCQRUAeaKJoTZnw8KeOmKpF2opfu8L1vQ8ubYoZqBX+gNP6GflFG6j +zgoNWfZvRQnDfofH+ZOPcB7vbVKcXKFZ0kHzfw40Nq49GxZndsK30xCUtdDiW7eE +Mgfv9EUCgYEA1q/HUTKBFpvKGu1BwCVTIdvqI0qScWpYst525CbeI36MCkP6ReCX +kvi7259saMRdAaEhIj8EN3BuDu5k8jjNRqkAnkq0pNHkYD3lzXBc4H3RC0LdmEmn +cUUS16sLSkcciI3+SnGjZUbCjso2r2Sy1d/iPhR7L2QpmlzqtPNZxr0CgYEA3OwK +NJMNN1ARv+aOka+mxn6+MsbbWWB0CLSbhg5WRRnmsefDMBjtWsRr+/ufQO7rC+q1 +rv83jiCgJl3u5idS4Hrm/k77iZZzrvJruuR5W5GgvqMTcdut7l4pdqSlvV96D4z7 +29/sze3vVoZv4zIeWxj5QDeT7xLwvjoZpUbHDacCgYAsCjsVCQs6HBNFms4WIJIB +LB/HxZBs+6feaYxyGRcQqPEJWhCJLR1q5OOElhujEkUSBH/LiqnOxZ2OKpFCryxN +BnY+Ao00EmqK46e0kQw8cRLlAH58sv9KWSUYYNocDqJn0NkNZGpkaDOZHxpAuKOH +BDphCcqLWjy+kbkEDbeo8QKBgQCD9m7GJsyvLKHdmi+xMFYTnWOpWwVtZuMIzDFW +Kzw2/JjDzifWlB07qbbDBvOCyvQV4zZxeLvLpwtiv5tTWUv1ERTn9W/lKLyjVOUq +9wzSuLNnDGwyB8Hmb9KerwzdiKmVnmZXWXPPMoBTk+xDrw1Y5xsD0+8G0K6DQptN +EXEXYwKBgQDS5DR5IDGvTycguDhU1QGSW0iEzMfBlZftKAucB/KYcQj8A4+AECKM +9SqcTI2Aneou+7PubpCFDSEckSKZkxb6ibw/ynA1EE55wyjuctyHTpqeHJjoK7jS +R5hyVL5iFBwGxv6oiWeKld+ZbDSB+i89km+kARaXjIB2gZmkepAIyA== +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/public.pem b/public.pem new file mode 100644 index 0000000..bea9d69 --- /dev/null +++ b/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuUUB5HsRK6mvKyjAClLc +kNxDK69MZTW2Yn9qOJZvw3j0R4slIhoyBRAm1fxFYthj+klxqw0XKUR7F6DcDuK+ +3z+a+Ds/uP3tDYbnfT4cr9VEhgq9FoLi7AgfH98mqp4VkqDCuYubt4XRtfhpN0Tu +ShT/xImmqLfxkmvZSP/syPoFytEqX66QZ2QyT4gqzz9/ZMo7EFMeuKA44IBv6qeQ +zFxSBt0xpeyMXHChn8AeocaOySC3kyZBefzmUF2lkOyZKlaPC+0/mUb6ndT1OBfK ++EZXzWEbugBWOg/NSy9pnzPRGjAYqknWs1912+H7t2FEA8BCRZCOK85B05BYeZM+ +SwIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/src/App.js b/src/App.js index c7f4f07..b10f14d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,6 @@ -import React, { useState } from "react"; +import React, { useState, useEffect } from "react"; +import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom"; +import axios from "axios"; import Chart from "./components/Chart"; import Scanner from "./components/Scanner"; import Ribbon from "./components/Ribbon"; @@ -7,52 +9,76 @@ import Popup from "./components/Popup"; import AboutScanner from "./components/AboutScanner"; import MarketIntel from "./components/MarketIntel"; import TrendScanner from "./components/TrendScanner"; +import Login from "./components/Login"; +import Signup from "./components/Signup"; +import ProtectedRoute from "./components/ProtectedRoute"; +import Profile from "./components/Profile"; import "./index.css"; -function App() { +function Dashboard() { const [selectedStock, setSelectedStock] = useState("NSE:RELIANCE"); const [signal, setSignal] = useState(null); const [view, setView] = useState("scanner"); + const [userProfile, setUserProfile] = useState(null); + const [dropdownOpen, setDropdownOpen] = useState(false); + const navigate = useNavigate(); + + const fetchProfile = () => { + axios.get("http://127.0.0.1:8000/api/auth/me") + .then(res => setUserProfile(res.data)) + .catch(err => { + if (err.response?.status === 401) handleLogout(); + }); + }; + + useEffect(() => { + fetchProfile(); + }, []); + + const handleLogout = () => { + localStorage.removeItem("token"); + navigate("/login"); + }; return (
-

📈 Stock Scanner Pro

+ {/* HEADER */} +
+

📈 Stock Scanner Pro

+ + {userProfile && ( +
+
setDropdownOpen(!dropdownOpen)} + > +
+ {userProfile.display_name ? userProfile.display_name.charAt(0).toUpperCase() : '?'} +
+ {userProfile.display_name || userProfile.username} + +
+ + {dropdownOpen && ( +
+ + +
+ )} +
+ )} +
- - - - - - - - - + + + + +
@@ -67,17 +93,26 @@ function App() { {signal && } )} - {view === "ribbon" && } - {view === "smartmoney" && } - {view === "marketintel" && } - {view === "trend" && } + {view === "profile" && }
); } +function App() { + return ( + + + } /> + } /> + } /> + + + ); +} + export default App; \ No newline at end of file diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 0000000..80a7c87 --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,70 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { useNavigate, Link } from "react-router-dom"; +import forge from "node-forge"; + +function Login() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleLogin = async (e) => { + e.preventDefault(); + setLoading(true); + setError(""); + try { + const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const publicKeyPem = resKey.data.public_key; + const publicKey = forge.pki.publicKeyFromPem(publicKeyPem); + const encrypted = publicKey.encrypt(password, 'RSA-OAEP'); + const encryptedBase64 = forge.util.encode64(encrypted); + + const res = await axios.post("http://127.0.0.1:8000/api/auth/login", { + username, + password: encryptedBase64 + }); + + localStorage.setItem("token", res.data.access_token); + navigate("/"); + } catch (err) { + setError(err.response?.data?.detail || "Login failed"); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

📈 Login

+
+ setUsername(e.target.value)} + style={{ padding: '10px', borderRadius: '5px', border: '1px solid var(--border-color)', background: 'var(--bg-tertiary)', color: 'white' }} + required + /> + setPassword(e.target.value)} + style={{ padding: '10px', borderRadius: '5px', border: '1px solid var(--border-color)', background: 'var(--bg-tertiary)', color: 'white' }} + required + /> + {error &&

{error}

} + +
+

Don't have an account? Sign up

+
+
+ ); +} + +export default Login; diff --git a/src/components/MarketIntel.js b/src/components/MarketIntel.js index a66deef..2a0e3f3 100644 --- a/src/components/MarketIntel.js +++ b/src/components/MarketIntel.js @@ -10,7 +10,7 @@ function MarketIntel() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/marketintel"); + const res = await axios.get("http://127.0.0.1:8000/api/scanner/marketintel"); setData(res.data); } catch (err) { console.error(err); diff --git a/src/components/Profile.js b/src/components/Profile.js new file mode 100644 index 0000000..238a860 --- /dev/null +++ b/src/components/Profile.js @@ -0,0 +1,173 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import forge from "node-forge"; + +function Profile({ userProfile, onProfileUpdated }) { + const [formData, setFormData] = useState({ + display_name: "", + email_id: "", + mobile_no: "", + gender: "", + password: "" + }); + + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(""); + const [error, setError] = useState(""); + const [showPassword, setShowPassword] = useState(false); + + useEffect(() => { + if (userProfile) { + setFormData({ + display_name: userProfile.display_name || "", + email_id: userProfile.email_id || "", + mobile_no: userProfile.mobile_no || "", + gender: userProfile.gender || "", + password: "" // password field is blank initially + }); + } + }, [userProfile]); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSave = async (e) => { + e.preventDefault(); + setLoading(true); + setMessage(""); + setError(""); + + try { + const payload = { ...formData }; + + if (payload.password) { + // Fetch Public Key to encrypt new password + const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const publicKey = forge.pki.publicKeyFromPem(resKey.data.public_key); + const encrypted = publicKey.encrypt(payload.password, 'RSA-OAEP'); + payload.password = forge.util.encode64(encrypted); + } else { + delete payload.password; + } + + await axios.put("http://127.0.0.1:8000/api/auth/update-profile", payload); + setMessage("Profile updated successfully! ✅"); + if (onProfileUpdated) onProfileUpdated(); + setFormData(prev => ({ ...prev, password: "" })); // Clear password field + } catch (err) { + setError(err.response?.data?.detail || "Failed to update profile"); + } finally { + setLoading(false); + } + }; + + if (!userProfile) return
Loading Profile...
; + + return ( +
+
+
+
+ {userProfile.display_name ? userProfile.display_name.charAt(0).toUpperCase() : '?'} +
+
+

Profile Settings

+

Manage your personal details and security

+
+
+ +
+ +
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+ +
+ + setShowPassword(!showPassword)} + style={{ position: 'absolute', right: '15px', cursor: 'pointer', fontSize: '1.2rem', userSelect: 'none' }} + title={showPassword ? "Hide password" : "Show password"} + > + {showPassword ? "🙈" : "👁️"} + +
+
+ + {message &&
{message}
} + {error &&
{error}
} + +
+ +
+
+
+
+ ); +} + +export default Profile; diff --git a/src/components/ProtectedRoute.js b/src/components/ProtectedRoute.js new file mode 100644 index 0000000..d4d17fc --- /dev/null +++ b/src/components/ProtectedRoute.js @@ -0,0 +1,12 @@ +import React from "react"; +import { Navigate } from "react-router-dom"; + +function ProtectedRoute({ children }) { + const token = localStorage.getItem("token"); + if (!token) { + return ; + } + return children; +} + +export default ProtectedRoute; diff --git a/src/components/Ribbon.js b/src/components/Ribbon.js index c93fa41..266e1ce 100644 --- a/src/components/Ribbon.js +++ b/src/components/Ribbon.js @@ -7,7 +7,7 @@ function Ribbon() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/ribbon"); + const res = await axios.get("http://127.0.0.1:8000/api/scanner/ribbon"); setData(res.data || []); setLoading(false); } catch (err) { diff --git a/src/components/Scanner.js b/src/components/Scanner.js index 49d8f9c..9a988fd 100644 --- a/src/components/Scanner.js +++ b/src/components/Scanner.js @@ -7,7 +7,7 @@ function Scanner({ onSelectStock, onSignal }) { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/scan"); + const res = await axios.get("http://127.0.0.1:8000/api/scanner/scan"); const newData = res.data; const best = newData.best_5 && newData.best_5.length > 0 ? newData.best_5[0] : null; diff --git a/src/components/Signup.js b/src/components/Signup.js new file mode 100644 index 0000000..dd4f7aa --- /dev/null +++ b/src/components/Signup.js @@ -0,0 +1,69 @@ +import React, { useState } from "react"; +import axios from "axios"; +import { useNavigate, Link } from "react-router-dom"; +import forge from "node-forge"; + +function Signup() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + + const handleSignup = async (e) => { + e.preventDefault(); + setLoading(true); + setError(""); + try { + const resKey = await axios.get("http://127.0.0.1:8000/api/auth/public-key"); + const publicKeyPem = resKey.data.public_key; + const publicKey = forge.pki.publicKeyFromPem(publicKeyPem); + const encrypted = publicKey.encrypt(password, 'RSA-OAEP'); + const encryptedBase64 = forge.util.encode64(encrypted); + + await axios.post("http://127.0.0.1:8000/api/auth/signup", { + username, + password: encryptedBase64 + }); + + navigate("/login"); + } catch (err) { + setError(err.response?.data?.detail || "Signup failed"); + } finally { + setLoading(false); + } + }; + + return ( +
+
+

✨ Sign Up

+
+ setUsername(e.target.value)} + style={{ padding: '10px', borderRadius: '5px', border: '1px solid var(--border-color)', background: 'var(--bg-tertiary)', color: 'white' }} + required + /> + setPassword(e.target.value)} + style={{ padding: '10px', borderRadius: '5px', border: '1px solid var(--border-color)', background: 'var(--bg-tertiary)', color: 'white' }} + required + /> + {error &&

{error}

} + +
+

Already have an account? Login

+
+
+ ); +} + +export default Signup; diff --git a/src/components/SmartMoney.js b/src/components/SmartMoney.js index 32065b7..19c9e2f 100644 --- a/src/components/SmartMoney.js +++ b/src/components/SmartMoney.js @@ -12,7 +12,7 @@ function SmartMoney() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/smartmoney"); + const res = await axios.get("http://127.0.0.1:8000/api/scanner/smartmoney"); setData(res.data); } catch (err) { console.error(err); diff --git a/src/components/TrendScanner.js b/src/components/TrendScanner.js index c7ac2cc..d876751 100644 --- a/src/components/TrendScanner.js +++ b/src/components/TrendScanner.js @@ -12,7 +12,7 @@ function TrendScanner() { const loadData = async () => { try { - const res = await axios.get("http://127.0.0.1:8000/trend"); + const res = await axios.get("http://127.0.0.1:8000/api/scanner/trend"); setData(res.data); } catch (err) { console.error(err); diff --git a/src/index.js b/src/index.js index bd365f0..b1559f9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,17 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; +import App from './App'; +import axios from 'axios'; -const root = ReactDOM.createRoot(document.getElementById("root")); +axios.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +const root = ReactDOM.createRoot(document.getElementById('root')); root.render(