"""Unsubscribe endpoints — bez logowania, weryfikacja przez signed token. GET /unsubscribe?t= — strona potwierdzenia (klik z maila) POST /unsubscribe — faktyczne wyłączenie (formularz lub List-Unsubscribe-Post z klienta pocztowego) """ import logging from flask import request, render_template, jsonify, abort from database import SessionLocal, User from utils.unsubscribe_tokens import ( verify_unsubscribe_token, column_for_type, label_for_type, ) from . import bp logger = logging.getLogger(__name__) def _apply_unsubscribe(token: str): """Apply the unsubscribe. Returns (ok: bool, label: str | None, user_email: str | None).""" parsed = verify_unsubscribe_token(token) if not parsed: return False, None, None user_id, ntype = parsed col = column_for_type(ntype) if not col: return False, None, None db = SessionLocal() try: user = db.query(User).filter(User.id == user_id).first() if not user or not hasattr(user, col): return False, None, None setattr(user, col, False) db.commit() logger.info("unsubscribe applied user=%s type=%s", user_id, ntype) return True, label_for_type(ntype), user.email except Exception as e: db.rollback() logger.error("unsubscribe error: %s", e) return False, None, None finally: db.close() @bp.route('/unsubscribe', methods=['GET']) def unsubscribe_view(): """Strona potwierdzenia (lub od razu wyłączenie dla List-Unsubscribe-Post).""" token = request.args.get('t', '').strip() parsed = verify_unsubscribe_token(token) if token else None if not parsed: return render_template('unsubscribe.html', status='invalid', label=None, token=None), 400 user_id, ntype = parsed return render_template('unsubscribe.html', status='confirm', label=label_for_type(ntype), token=token) @bp.route('/unsubscribe', methods=['POST']) def unsubscribe_apply(): """Faktyczne wyłączenie — formularz z GET lub RFC 8058 One-Click.""" token = (request.form.get('t') or request.args.get('t') or '').strip() # RFC 8058 One-Click: body = "List-Unsubscribe=One-Click" if not token: body = request.get_data(as_text=True) or '' if 'List-Unsubscribe=One-Click' in body: token = request.args.get('t', '').strip() ok, label, _ = _apply_unsubscribe(token) # RFC 8058 One-Click expects plain 200 if request.headers.get('Content-Type', '').startswith('application/x-www-form-urlencoded') \ and 'List-Unsubscribe=One-Click' in (request.get_data(as_text=True) or ''): return ('OK', 200) if ok else ('Invalid token', 400) return render_template('unsubscribe.html', status='done' if ok else 'invalid', label=label, token=None), 200 if ok else 400 def exempt_from_csrf(app): csrf = app.extensions.get('csrf') if csrf: csrf.exempt(bp)