G

Untitled

public
Guest Jul 27, 2024 Never 19
Clone
Python paste1.py 182 lines (155 loc) | 8.2 KB
1
from flask import Flask, request, jsonify, url_for, redirect, flash, send_from_directory
2
from flask_sqlalchemy import SQLAlchemy
3
from flask_mail import Mail
4
from itsdangerous import URLSafeTimedSerializer, SignatureExpired, BadTimeSignature
5
from datetime import datetime
6
from flask_wtf.csrf import CSRFProtect, generate_csrf
7
from flask_cors import CORS
8
from dotenv import load_dotenv
9
import os
10
import logging
11
import requests
12
from sqlalchemy.exc import IntegrityError
13
14
load_dotenv()
15
16
app = Flask(__name__)
17
CORS(app, resources={r"/*": {"origins": "https://pnyx.nu"}}, supports_credentials=True)
18
19
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'your-default-secret-key')
20
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL').replace('postgres://', 'postgresql://')
21
app.config['MAILGUN_DOMAIN'] = os.getenv('MAILGUN_DOMAIN', 'your-mailgun-domain')
22
app.config['MAILGUN_API_KEY'] = os.getenv('MAILGUN_API_KEY', 'your-mailgun-api-key')
23
app.config['MAILGUN_DEFAULT_FROM'] = os.getenv('MAILGUN_DEFAULT_FROM', 'Pnyx <[email protected]>')
24
app.config['MAIL_SERVER'] = 'smtp.mailgun.org'
25
app.config['MAIL_PORT'] = 587
26
app.config['MAIL_USERNAME'] = os.getenv('MAIL_USERNAME', 'your-mail-username')
27
app.config['MAIL_PASSWORD'] = os.getenv('MAIL_PASSWORD', 'your-mail-password')
28
app.config['MAIL_USE_TLS'] = True
29
app.config['MAIL_USE_SSL'] = False
30
31
db = SQLAlchemy(app)
32
mail = Mail(app)
33
csrf = CSRFProtect(app)
34
serializer = URLSafeTimedSerializer(app.config['SECRET_KEY'])
35
36
# Configure logging
37
logging.basicConfig(level=logging.INFO)
38
logger = logging.getLogger(__name__)
39
40
class Registration(db.Model):
41
id = db.Column(db.Integer, primary_key=True)
42
firstname = db.Column(db.String(100), nullable=False)
43
lastname = db.Column(db.String(100), nullable=False)
44
email = db.Column(db.String(120), nullable=False, unique=True)
45
phone = db.Column(db.String(20), nullable=False)
46
birthday = db.Column(db.Date, nullable=False)
47
confirmed = db.Column(db.Boolean, default=False)
48
49
@app.route('/register', methods=['POST', 'OPTIONS'])
50
@csrf.exempt
51
def register():
52
if request.method == 'OPTIONS':
53
response = jsonify({'message': 'CORS preflight'})
54
response.headers.add('Access-Control-Allow-Origin', 'https://pnyx.nu')
55
response.headers.add('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE, PUT')
56
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With,X-CSRFToken,Accept')
57
response.headers.add('Access-Control-Allow-Credentials', 'true')
58
return response
59
60
data = request.get_json()
61
62
logger.info('Received registration data: %s', data)
63
64
if not data or not all(key in data for key in ['firstname', 'lastname', 'email', 'phone', 'countryCode', 'birthday']):
65
logger.warning('Missing data in the request')
66
return jsonify({'message': 'Missing data'}), 400
67
68
token = serializer.dumps(data['email'], salt='email-confirm-salt')
69
confirm_url = url_for('confirm_email', token=token, _external=True)
70
71
try:
72
birthday = datetime.strptime(data['birthday'], '%Y-%m-%d').date()
73
except ValueError:
74
logger.warning('Invalid date format: %s', data['birthday'])
75
return jsonify({'message': 'Invalid date format. Please use YYYY-MM-DD.'}), 400
76
77
new_registration = Registration(
78
firstname=data['firstname'],
79
lastname=data['lastname'],
80
email=data['email'],
81
phone=data['countryCode'] + data['phone'],
82
birthday=birthday
83
)
84
85
try:
86
db.session.add(new_registration)
87
db.session.commit()
88
logger.info('Registration saved: %s', new_registration)
89
except IntegrityError as e:
90
db.session.rollback()
91
if 'duplicate key value violates unique constraint "registration_email_key"' in str(e):
92
logger.warning('Duplicate email: %s', data['email'])
93
return jsonify({'message': 'This email is already registered.'}), 400
94
logger.error('Error saving registration: %s', str(e))
95
return jsonify({'message': 'Error saving registration. Please try again later.'}), 500
96
97
send_confirmation_email(data['email'], confirm_url)
98
return jsonify({'message': 'Please check your email to confirm your registration.'}), 200
99
100
def send_confirmation_email(to_email, confirm_url):
101
subject = "Please confirm your registration"
102
body = f"Thank you for registering. Please confirm your registration by clicking the following link: {confirm_url}"
103
104
logger.info('Sending email to: %s', to_email)
105
logger.info('Using MAILGUN_DOMAIN: %s', app.config['MAILGUN_DOMAIN'])
106
logger.info('Using MAILGUN_API_KEY: %s', app.config['MAILGUN_API_KEY'])
107
logger.info('Using MAILGUN_DEFAULT_FROM: %s', app.config['MAILGUN_DEFAULT_FROM'])
108
109
response = requests.post(
110
f"https://api.eu.mailgun.net/v3/{app.config['MAILGUN_DOMAIN']}/messages",
111
auth=("api", app.config['MAILGUN_API_KEY']),
112
data={"from": app.config['MAILGUN_DEFAULT_FROM'],
113
"to": [to_email],
114
"subject": subject,
115
"text": body})
116
117
logger.info('Mailgun response status code: %s', response.status_code)
118
logger.info('Mailgun response text: %s', response.text)
119
120
if response.status_code != 200:
121
logger.error("Failed to send email: %s", response.text)
122
123
@app.route('/confirm/<token>')
124
def confirm_email(token):
125
try:
126
email = serializer.loads(token, salt='email-confirm-salt', max_age=3600)
127
except (SignatureExpired, BadTimeSignature) as e:
128
logger.error('Error confirming email: %s', e)
129
flash('The confirmation link is invalid or has expired.', 'danger')
130
return redirect(url_for('index'))
131
132
registration = Registration.query.filter_by(email=email).first_or_404()
133
if registration and not registration.confirmed:
134
registration.confirmed = True
135
db.session.commit()
136
logger.info('Email confirmed for: %s', email)
137
return redirect("https://pnyx.nu/confirmation_success.html")
138
else:
139
flash('Email is already confirmed.', 'success')
140
return redirect(url_for('index'))
141
142
@app.route('/')
143
def index():
144
user_agent = request.headers.get('User-Agent')
145
if "Mobi" in user_agent or "Tablet" in user_agent:
146
return redirect(url_for('index_mobile'))
147
return send_from_directory('.', 'index.html')
148
149
@app.route('/index.mobile.html')
150
def index_mobile():
151
return send_from_directory('.', 'index.mobile.html')
152
153
@app.route('/get_csrf_token', methods=['GET'])
154
@csrf.exempt
155
def get_csrf_token():
156
response = jsonify({'csrf_token': generate_csrf()})
157
response.headers.add('Access-Control-Allow-Origin', 'https://pnyx.nu')
158
response.headers.add('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, DELETE, PUT')
159
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Requested-With,X-CSRFToken,Accept')
160
response.headers.add('Access-Control-Allow-Credentials', 'true')
161
return response
162
163
@app.after_request
164
def add_security_headers(response):
165
response.headers['Content-Security-Policy'] = (
166
"default-src 'self'; "
167
"script-src 'self' 'sha256-Bed9qzUU2IVkAAxp73fh88GAnMchALEvifOr4XcPQ/4=' 'sha256-rx35CH5hONLWpWC9d43ABhaVMBiWHzn6/Npxn7D7UT0=' 'sha256-HC/7mAU+xhanEo18GAjE8J4Wnp9Fo4+i/e2PeNW2VeQ=' 'sha256-66gRKVjRFyDmaZVN1QTwH4ThLwRwMwvOZGYWKJ8uXP4=' 'unsafe-eval' https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js; "
168
"style-src 'self' 'sha256-WmyMLJokW+oizRe7FT8qTk+WRdfJE6vhzFX5xSPiGiw=' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css; "
169
"font-src 'self' https://fonts.gstatic.com data:; "
170
"connect-src 'self' https://pnyx-0fe8297d9f8b.herokuapp.com; "
171
"object-src 'none'; "
172
"base-uri 'self';"
173
)
174
response.headers['Referrer-Policy'] = 'no-referrer'
175
response.headers['X-Content-Type-Options'] = 'nosniff'
176
response.headers['X-Frame-Options'] = 'DENY'
177
response.headers['X-XSS-Protection'] = '1; mode=block'
178
response.headers.add('X-CSRF-TOKEN', generate_csrf())
179
return response
180
181
if __name__ == '__main__':
182
app.run(debug=False, ssl_context='adhoc')