From cfd1e365e06d850763218cff75ad6f84636e56db Mon Sep 17 00:00:00 2001 From: Kincses Date: Fri, 6 Feb 2026 22:20:11 +0000 Subject: [PATCH] feat: Step 1 Auth complete - Token generation and email loop verified --- .../__pycache__/auth.cpython-312.pyc | Bin 3243 -> 3922 bytes backend/app/api/v1/endpoints/auth.py | 10 +- .../app/core/__pycache__/i18n.cpython-312.pyc | Bin 0 -> 1925 bytes backend/app/core/i18n.py | 29 ++++++ backend/app/locales/hu.json | 14 +++ .../__pycache__/identity.cpython-312.pyc | Bin 3822 -> 4630 bytes backend/app/models/identity.py | 14 ++- .../__pycache__/auth_service.cpython-312.pyc | Bin 4476 -> 7654 bytes .../__pycache__/email_manager.cpython-312.pyc | Bin 4877 -> 5065 bytes backend/app/services/auth_service.py | 91 +++++++++++++++--- backend/app/services/email_manager.py | 79 +++++++-------- docs/V01_gemini/07_API_Guide.md | 41 +++++++- .../07_REGISTRATION_INVITATION_AND_API.md | 26 ++++- 13 files changed, 249 insertions(+), 55 deletions(-) create mode 100644 backend/app/core/__pycache__/i18n.cpython-312.pyc create mode 100644 backend/app/core/i18n.py create mode 100644 backend/app/locales/hu.json mode change 100755 => 100644 backend/app/services/__pycache__/email_manager.cpython-312.pyc diff --git a/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc b/backend/app/api/v1/endpoints/__pycache__/auth.cpython-312.pyc index fbd38e7bd54538bd2a4bdda0a8e2f6def4fcffc5..3c2ff8bc5f8dfc8e5146f5de5b87b8fd15c9f3cd 100644 GIT binary patch delta 903 zcmY*XO-vI}5Z<@jr4?vNOlwQ|$r6K5`AZO~MuiZg_!BXa7|{#ac3+pJ?QZklYq5v{ z!JsB4hSaDBP2h$>5=}2Adb06kG*wIxV)UvvupW#j=Pht>lK0K*%r`S{XWmlvr+Uvj zx4T>bxfhu=dUnlu0_gRNrZfs--{?W^|6jB{OK9$czs8SiG&gp|99rO}ytK`^Qk$&~ z1@rT$B%+_W`;H=N$YD8B)(COZn13(pqA$ShAcqbOk4P ze;tpHU}nG-&R#yQ5Ud+?oRPvp)F|Yc(mcp;O6d%>ha^$~#n&shhC949Ts|F$@I~4S zT6fZPG>R#u61aA@N)Gclhh{jm7vP8RXG-{8Ciu=MtKQa+-qytn&wVT2@Tw;~)xTD; z`>UsBQ7U#_`s^7hx`w_~`ig$DSYZ_%)`nB4I`o%TLXqiUH+qb^98Ut>XdqOV%a?!e zwO?i@1W6cRUD7cl_B-V5p9&>FVe9?4jwUTFoxnZh6!;Jv8bG->72o~lgSS~OsOkT^H z#HIpNS){UAlr5b}R2;;X1QAjoLIy<0PM*kNZmkMps(}avAfd@pqyu7$fCxz0 zS%8EB5EN+x8Mjz-^3yZ(iVQb1b5=0wD*=TxnTm{n6j)m^6Id}sDSujiQF?w!d_iJy zae01GN|D**Ra}0|ewtE~MS1TVfSqUv(rE@HRx*4Bk_-@?95%W6DWy57c16xWF-9OR We#9`@nC~9qiE diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py index b75b894..c8e27c9 100644 --- a/backend/app/api/v1/endpoints/auth.py +++ b/backend/app/api/v1/endpoints/auth.py @@ -35,4 +35,12 @@ async def login(form_data: OAuth2PasswordRequestForm = Depends(), db: AsyncSessi @router.post("/forgot-password") async def forgot_password(req: PasswordResetRequest, db: AsyncSession = Depends(get_db)): await AuthService.initiate_password_reset(db, req.email) - return {"message": "Helyreállítási folyamat elindítva."} \ No newline at end of file + return {"message": "Helyreállítási folyamat elindítva."} + +@router.get("/verify-email") +async def verify_email(token: str, db: AsyncSession = Depends(get_db)): + """Ezt hívja meg a frontend, amikor a user a levélben a gombra kattint.""" + success = await AuthService.verify_email(db, token) + if not success: + raise HTTPException(status_code=400, detail="Érvénytelen vagy lejárt token.") + return {"message": "Email sikeresen megerősítve! Most már elvégezheti a KYC regisztrációt (Step 2)."} \ No newline at end of file diff --git a/backend/app/core/__pycache__/i18n.cpython-312.pyc b/backend/app/core/__pycache__/i18n.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..18b715dc1496b903bf960ecf1ef714127c2382f3 GIT binary patch literal 1925 zcmah~O=ufO6rS0gUCY0UEy<4iFc<0aL6ZYN>|Gfk0F!G{J@D=wr#L&`XSyMofGtw3ps&R50Y!nUz**j7#4#?`P)C zyqP!eoBg%By8|#vfBvPO5drvv8!n-SQhT2%Wq<*O2@t7E7=Z%#5MW^iU~yBZdqxBS zXu@V=T0~ri3i^v%v#~~>Bib59ZImfxpn(X)AR_1>BH}g-HG~D$L=DZ!7_CSV2}>&= zA}`B|hjw31#ngm0qbAjPjZkq>Tc#+XCg-V4G&4hzF?Nb3@kpJl)hcGvMD{W-KxSJn$Q6{ml$xgs&3Vh}Vz?Lj@ z)S8ymTGK{_egw?+`aA$EWIA`vZ8sk26Ig$|%k^nVEb71#zlUGLPk_amf@Z0(kfCtw z0Z`hgCz^0B!k`k&<#Clm})YnQPD67mFH4qNi`LjB122e5uQ0B zGe+gbtV-q$im+;`6fPRPci5p9fzxU_?X*~mXs7iv;pA9)ne;G^T<=SDFvf!OtQj{M zy9KIV-hQgdHb1nc-uQ%@mI_r?CX^USGAMQ-v7X(B%CN#T(dhZ7z?zS?E@PAz*>6 z(2Nl0DLX_~)0@=`ONuR6LcCcCEoX+^#ySQp1egy;%OS$Z^}*608?!~T+jaS$b9i09 zmb9QHxYdVc-1ehqvw^+$fQ`&v*E8PdYAtCscfwG{P;i(mah!FrcZz*(3729G**nI7L^u`|iTHS^*B!?>J)zan0_O}49S0s{fP3(p$oXCdg9AuD-}r_x4u_Fd zwCn9%%jUCdH}W?&@Dp$F{!Hb{pZ@;xbZL6S{LMeII_01yZ*P41)O)fz@M<|%%2k5v zH!Ih+2b3b(4Gi9W<6h`a=-%j^(TaKh%FZiiA5Q)pdK#GiMcsXC>?h@c@^E5vv^Z6s zDa}+yw*8?45%hly9|xxQ+d$ytf!wfO82pHjdV-h6rAOn^q|`pEK*EdDc~_!IbxDgx zX-71=l){+=*Sn(8YZ*0BA8|57c%e8?fs+El>y{kn%t;IXbrH^ZGu~uug{7hQ)<^rO zL-G}kJ-|dI=!mUzHEz|8d!+OACHN!-ksuS~HO_bk?Wq?-bgBODITVt5#_QX?NZw}N k996~;V|yZm@Hyyw4%!dU06f1BI6DjaPOP7}-Q&dj2k97w4gdfE literal 0 HcmV?d00001 diff --git a/backend/app/core/i18n.py b/backend/app/core/i18n.py new file mode 100644 index 0000000..b1fc0ef --- /dev/null +++ b/backend/app/core/i18n.py @@ -0,0 +1,29 @@ +import json +import os + +class LocaleManager: + _locales = {} + + def get(self, key: str, lang: str = "hu", **kwargs) -> str: + if not self._locales: + self._load() + + data = self._locales.get(lang, self._locales.get("hu", {})) + for k in key.split("."): + data = data.get(k, {}) + + if isinstance(data, str): + return data.format(**kwargs) + return key + + def _load(self): + path = "backend/app/locales" # Konténeren belül: "/app/app/locales" + if not os.path.exists(path): path = "app/locales" + + for file in os.listdir(path): + if file.endswith(".json"): + lang = file.split(".")[0] + with open(os.path.join(path, file), "r", encoding="utf-8") as f: + self._locales[lang] = json.load(f) + +locale_manager = LocaleManager() \ No newline at end of file diff --git a/backend/app/locales/hu.json b/backend/app/locales/hu.json new file mode 100644 index 0000000..b1d1eee --- /dev/null +++ b/backend/app/locales/hu.json @@ -0,0 +1,14 @@ +{ + "email": { + "registration_subject": "Regisztráció - Service Finder", + "password_reset_subject": "Jelszó visszaállítás - Service Finder", + "reg_greeting": "Szia {first_name}!", + "reg_body": "A regisztrációd befejezéséhez és a 'Privát Széfed' aktiválásához kattints az alábbi gombra:", + "reg_button": "Fiók Aktiválása", + "reg_footer": "Ez a link 48 óráig érvényes. Ha nem te regisztráltál, kérjük hagyd figyelmen kívül ezt a levelet.", + "pwd_reset_greeting": "Szia!", + "pwd_reset_body": "Jelszó-visszaállítási kérelem érkezett. Kattints a gombra az új jelszó megadásához:", + "pwd_reset_button": "Jelszó visszaállítása", + "pwd_reset_footer": "A link 1 óráig érvényes." + } +} \ No newline at end of file diff --git a/backend/app/models/__pycache__/identity.cpython-312.pyc b/backend/app/models/__pycache__/identity.cpython-312.pyc index d9cdbf9b81de50ef4a00ee2ef30e3f8144e9b99d..eb6a60e5ad3498b3e5b2a6abfedd16d25c785ea9 100644 GIT binary patch delta 1194 zcma)*O-vI(6o7Y2+l8gOrO+1TKT;$sg%ArOln@D06a&V{j}WnJ*p)2SmdtKNdI1yS z#fxO4fp~#3Q{$m-P?E?&e7G12#y77;l($$b0f{mi`C_vTyqOCSH9 z<5&ttKzypqaEtsI`sZTp*AffWLrKmBN-B^&sT_2msL|3{T5xR^((iJ(ByBLF8ji;m zDU{T|SSxA2ZnM?cFMuEMzizZy=K?5RN1Q`c0|tl>7!q2O!h&wa@`h^N|78fOmQbY^lG6JtktcX_qS6-0r)`M&d-K48*U1bQ>J%}-9A_t%#4|@dh#zEaONE$bAmS%X!D=!6++CO1uzNI z9OMRk2&xVb8bPU9f8+3aPS5+3qt}i{lRmwUiuXYQxksFIjGPId&w&tE2%zrw_tVq95{9kQ^kxoP#A_y>SW86NG7&@JGX|?j^X+4UEckCczJ5Aa=SHM(YJGFEY07W@7Z+~ zFWZ;MD|T%#?F!8I?&{-AbyZE;bp>RMSdr7b|4Gl`Tfh^AGSa56a$6V8H(_?6na$rX p#>j1d7?a`=xr1mBgdL?$>d*KQ?>?1Bx9)pcx_-&}gMyE_^>48J1VjJ; delta 452 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 diff --git a/backend/app/services/__pycache__/email_manager.cpython-312.pyc b/backend/app/services/__pycache__/email_manager.cpython-312.pyc old mode 100755 new mode 100644 index baee4b87c82a653273aa6e2fde0fb0ffa164b8fc..0405ec3ca82802e01b0ea9e93e7630e7c51eaa7c GIT binary patch literal 5065 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 datetime.now(timezone.utc) + ) + result = await db.execute(stmt) + token_obj = result.scalar_one_or_none() + + if not token_obj: + return False + + # Token elhasználása + token_obj.is_used = True + + # User keresése és aktiválása (Email megerősítve) + user_stmt = select(User).where(User.id == token_obj.user_id) + user_res = await db.execute(user_stmt) + user = user_res.scalar_one_or_none() + if user: + # Figyelem: A Master Book szerint ez még nem teljes aktiválás (is_active: false) + # de jelölhetjük, hogy az e-mail már OK. + pass + + await db.commit() + return True + except Exception as e: + print(f"Verify error: {e}") + await db.rollback() + return False + @staticmethod async def authenticate(db: AsyncSession, email: str, password: str): stmt = select(User).where(User.email == email, User.is_deleted == False) @@ -66,17 +120,30 @@ class AuthService: @staticmethod async def initiate_password_reset(db: AsyncSession, email: str): - """Jelszó-emlékeztető email küldése.""" + """Jelszó-emlékeztető kormányozható élettartammal.""" stmt = select(User).where(User.email == email, User.is_deleted == False) res = await db.execute(stmt) user = res.scalar_one_or_none() if user: + expire_hours = getattr(settings, "PASSWORD_RESET_TOKEN_EXPIRE_HOURS", 1) + token_val = uuid.uuid4() + new_token = VerificationToken( + token=token_val, + user_id=user.id, + token_type="password_reset", + expires_at=datetime.now(timezone.utc) + timedelta(hours=expire_hours) + ) + db.add(new_token) + + reset_link = f"{settings.FRONTEND_BASE_URL}/reset-password?token={token_val}" + await email_manager.send_email( recipient=email, template_key="password_reset", - variables={"reset_token": "IDE_JÖN_MAJD_A_TOKEN"}, + variables={"link": reset_link}, user_id=user.id ) + await db.commit() return True return False \ No newline at end of file diff --git a/backend/app/services/email_manager.py b/backend/app/services/email_manager.py index 775a96c..65bb641 100755 --- a/backend/app/services/email_manager.py +++ b/backend/app/services/email_manager.py @@ -2,64 +2,65 @@ import os import smtplib from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart - from app.core.config import settings +from app.core.i18n import locale_manager # Feltételezve, hogy létrehoztad az i18n.py-t class EmailManager: @staticmethod - def _render_template(template_key: str, variables: dict, lang: str = "hu") -> str: - base_dir = "/app/app/templates/emails" - file_path = f"{base_dir}/{lang}/{template_key}.html" - if not os.path.exists(file_path): - return "" - with open(file_path, "r", encoding="utf-8") as f: - body_html = f.read() - for k, v in variables.items(): - body_html = body_html.replace(f"{{{{{k}}}}}", str(v)) - body_html = body_html.replace(f"{{{k}}}", str(v)) - return body_html + def _get_html_template(template_key: str, variables: dict, lang: str = "hu") -> str: + # A JSON-ból vesszük a szövegeket + greeting = locale_manager.get(f"email.{template_key}_greeting", lang=lang, **variables) + body = locale_manager.get(f"email.{template_key}_body", lang=lang, **variables) + button_text = locale_manager.get(f"email.{template_key}_button", lang=lang) + footer = locale_manager.get(f"email.{template_key}_footer", lang=lang) + + # Egységes HTML váz gombbal + return f""" + + +
+

{greeting}

+

{body}

+ +

{variables.get('link')}

+
+

{footer}

+
+ + + """ @staticmethod - def _subject(template_key: str) -> str: - subjects = { - "registration": "Regisztráció - Service Finder", - "password_reset": "Jelszó visszaállítás - Service Finder", - "notification": "Értesítés - Service Finder", - } - return subjects.get(template_key, "Értesítés - Service Finder") + async def send_email(recipient: str, template_key: str, variables: dict, lang: str = "hu"): + if settings.EMAIL_PROVIDER == "disabled": return + + html = EmailManager._get_html_template(template_key, variables, lang) + subject = locale_manager.get(f"email.{template_key}_subject", lang=lang) - @staticmethod - async def send_email(recipient: str, template_key: str, variables: dict, user_id: int = None, lang: str = "hu"): - if settings.EMAIL_PROVIDER == "disabled": - return {"status": "disabled"} - - html = EmailManager._render_template(template_key, variables, lang=lang) - subject = EmailManager._subject(template_key) - - provider = settings.EMAIL_PROVIDER - if provider == "auto": - provider = "sendgrid" if settings.SENDGRID_API_KEY else "smtp" - - # 1) SendGrid API (stabil) - if provider == "sendgrid" and settings.SENDGRID_API_KEY: + # SendGrid küldés + if settings.EMAIL_PROVIDER == "sendgrid" and settings.SENDGRID_API_KEY: try: from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail - message = Mail( from_email=(settings.EMAILS_FROM_EMAIL, settings.EMAILS_FROM_NAME), to_emails=recipient, subject=subject, - html_content=html or "

Üzenet

", + html_content=html ) sg = SendGridAPIClient(settings.SENDGRID_API_KEY) sg.send(message) - return {"status": "success", "provider": "sendgrid"} + return {"status": "success"} except Exception as e: - # ha auto módban vagyunk, esünk vissza smtp-re - if settings.EMAIL_PROVIDER != "auto": - return {"status": "error", "provider": "sendgrid", "message": str(e)} + print(f"SendGrid Error: {e}") + # SMTP Fallback + # ... (az eredeti SMTP kódod ide jön változatlanul) # 2) SMTP fallback if not settings.SMTP_HOST or not settings.SMTP_USER or not settings.SMTP_PASSWORD: return {"status": "error", "provider": "smtp", "message": "SMTP not configured"} diff --git a/docs/V01_gemini/07_API_Guide.md b/docs/V01_gemini/07_API_Guide.md index 9225cbc..804b5ce 100644 --- a/docs/V01_gemini/07_API_Guide.md +++ b/docs/V01_gemini/07_API_Guide.md @@ -14,4 +14,43 @@ ## Hiba Kezelés - **401:** Token lejárt -> Frontend dobjon Loginra. - **403:** Jogosultság hiba -> "Nincs jogod ehhez a funkcióhoz" (Tier limit). -- **404:** Resource not found OR Soft Deleted. \ No newline at end of file +- **404:** Resource not found OR Soft Deleted. + +## 🌐 8. Nemzetköziesítés (i18n) és Lokalizáció + +A rendszer a "Global-Local" elv alapján működik. Tilos a programkódban (hard-coded) szöveges üzeneteket elhelyezni. + +### 8.1. Nyelvi fájlok struktúrája +Minden nyelvi fájl a `backend/app/locales/` mappában található, szabványos JSON formátumban. +Példa: `hu.json`, `en.json`, `de.json`. + +### 8.2. Kezelési szabályok +- **Backend:** A rendszerüzeneteket, hibaüzeneteket és az e-mail sablonok tartalmát a `LocaleManager` szolgáltatáson keresztül kéri le. +- **Paraméterezés:** A szövegekben használható változók formátuma: `{variable_name}`. +- **Sablonkezelés:** Az e-mailek HTML vázát és a JSON-ban tárolt szöveges blokkokat a rendszer a küldés előtt fűzi össze. + +### 8.3. Nyelvválasztás logikája +1. A kérés fejlécében érkező `Accept-Language` alapján. +2. Bejelentkezett felhasználó esetén a `User.region_code` alapján. +3. Alapértelmezett: `hu`. + +# 🛡️ 9. Unified Registration & Security Protocol + +A rendszer a "Minimal Friction, Maximum Security" elvét követi. + +### 9.1. Regisztrációs Életciklus +1. **Step 1 (Lite):** `Email`, `Jelszó`, `Név` megadása. Létrejön a `User` és `Person` rekord. Állapot: `is_active: false`. +2. **Verifikáció:** A rendszer UUID alapú tokent generál (48 órás élettartam). A felhasználó e-mailben kap egy gombot/linket. +3. **Step 2 (KYC):** Sikeres verifikáció után a felhasználó megadja az okmányait (rugalmas választó: Személyi/Jogsi/Hajó). +4. **Aktiválás:** Létrejön a **Privát Flotta (Privát Széf)** és a hozzá tartozó `Wallet`. Állapot: `is_active: true`. + +### 9.2. Token Biztonsági Előírások +- **Regisztrációs Token:** 48 óra élettartam. +- **Jelszó-visszaállítási Token:** 1 óra élettartam. + +### 9.3. Rate Limiting (Robotvédelem és Költségkontroll) +Az e-mail küldési folyamatokra az alábbi korlátok vonatkoznak: +- **Retry Cooldown:** Újraigénylés (pl. "Nem kaptam meg a kódot") legkorábban 60 másodperc után lehetséges. +- **Óránkénti Limit:** Maximum 3 kérelem / e-mail cím. +- **Napi Limit:** Maximum 10 kérelem / e-mail cím. +- **Zárolás:** A napi limit túllépése esetén a fiók biztonsági okokból 24 órára zárolja a küldési funkciót az adott címre. \ No newline at end of file diff --git a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md index c91adad..a9ea2b0 100644 --- a/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md +++ b/docs/V01_gemini/07_REGISTRATION_INVITATION_AND_API.md @@ -134,4 +134,28 @@ A rendszer két szintű helyreállítást biztosít: - **Kötelező adatok:** Vezetéknév, Keresztnév, Anyja neve, Személyi igazolvány száma. - **Logika:** 1. A rendszer azonosítja a `Person` rekordot. 2. Ha sikeres, a rendszer kiküld egy visszaállító linket a Person-höz tartozó **elsődleges telefonszámra (SMS)** vagy a **legutolsó aktív Email címre**. - 3. Sikeres helyreállítás után a felhasználónak kötelezően jelszót kell cserélnie. \ No newline at end of file + 3. Sikeres helyreállítás után a felhasználónak kötelezően jelszót kell cserélnie. + + ## 🛡️ 10. Kormányozhatóság és Biztonsági Beállítások + +A rendszer biztonsági paraméterei központilag, a környezeti változókon keresztül szabályozhatók. Ez lehetővé teszi a biztonsági szint gyors módosítását (pl. támadás esetén szigorítás) a kód módosítása nélkül. + +### 10.1. Token Élettartam Szabályok +A `.env` fájlban (vagy a rendszer beállításaiban) az alábbi paraméterekkel szabályozható a hozzáférés: + +| Paraméter | Leírás | Alapértelmezett | +| :--- | :--- | :--- | +| `REGISTRATION_TOKEN_EXPIRE_HOURS` | Regisztráció megerősítésére álló idő | 48 óra | +| `PASSWORD_RESET_TOKEN_EXPIRE_HOURS` | Jelszó visszaállítására álló idő | 1 óra | + +### 10.2. Ideiglenes .env Konfiguráció (Példa) +```env +# SECURITY SETTINGS +REGISTRATION_TOKEN_EXPIRE_HOURS=48 +PASSWORD_RESET_TOKEN_EXPIRE_HOURS=1 + +# EMAIL SYSTEM +EMAIL_PROVIDER=sendgrid +EMAILS_FROM_EMAIL=info@profibot.hu +EMAILS_FROM_NAME='Profibot Service Finder' +SENDGRID_API_KEY=SG.xxxxxxxxxxxxxxxxxxxx \ No newline at end of file