from flask import Flask from flask import make_response from flask import jsonify from flask import request from flask import g from flask import redirect from markupsafe import escape import json import datetime import logging import os import sqlite3 import random MEETUP_INVITE_CLIENT_VERSION='v3.0.0' DATABASE = '/.hmc-meetups/hmc-meetups.db' AGREEMENT_SUCCESS_URL = 'https://handmadecities.com/meetups-success' # NOTE(jmug): This was a large list of invite codes, request the "invite-codes" # string array secret from secrets server, client at: # https://git.handmadecities.com/HMC/shhh/src/commit/36af43779460dcfd818d1481abeded14fa45e1a4/pkg/secretsclient/cli.go#L12-L34 INVITE_CODES = [""] # NOTE(jmug): This was a map of "cities" to a shared secret corresponding to each city, request the "city-secrets" # string map secret from secrets server, client at: # https://git.handmadecities.com/HMC/shhh/src/commit/36af43779460dcfd818d1481abeded14fa45e1a4/pkg/secretsclient/cli.go#L12-L34 shared_secrets = { '': '', } # Flask config def home(): if os.name == 'nt': return os.getenv('HOMEPATH') else: return os.getenv('HOME') app = Flask(__name__) # NOTE(jmug): Flask uses this secret_key to validate cookies, among other things, # we hsould not have a need for this in a Go port. app.secret_key = '' start_time = datetime.datetime.now() logname = start_time.strftime('flask-%Y-%m-%d-%Hh-%Mm-%Ss.log') logging.basicConfig(filename=home() + '/.hmc-meetups/logs/' + logname, level=logging.INFO, format=f'%(asctime)s %(levelname)s %(name)s %(threadName)s : %(message)s') @app.before_request def before_request(): g.db = sqlite3.connect(home() + DATABASE) g.db.row_factory = sqlite3.Row @app.teardown_request def teardown_request(exception): if hasattr(g, 'db'): g.db.close() def insert_subscriber(email, agent, city): cur = g.db.cursor() creation_date = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') query = ''' INSERT INTO subscribers ( email, creation_date, suppressed, suppressed_date, user_agent, city ) VALUES (?, ?, ?, ?, ?, ?) ''' cur.execute(query, (email, creation_date, 0, None, agent, city)) g.db.commit() # @robustness @abner catch exceptions @app.route('/v1/meetups/wishlist', methods=["POST"]) def meetups_wishlist(): address = request.form['email'] # @robustness @abner what if it's empty? city = request.form['city'] # @robustness @abner insert_subscriber(address, request.headers.get('User-Agent'), city); app.logger.info("'" + address + "' has wishlisted '" + city + "'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/vienna', methods=["POST"]) def meetups_vienna(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Vienna'); app.logger.info("'" + address + "' has subscribed to 'Vienna'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/denmark', methods=["POST"]) def meetups_denmark(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Denmark'); app.logger.info("'" + address + "' has subscribed to 'Denmark'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/montreal', methods=["POST"]) def meetups_montreal(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Montreal'); app.logger.info("'" + address + "' has subscribed to 'Montreal'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/revolt', methods=["POST"]) def meetups_revolt(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Revolt'); app.logger.info("'" + address + "' has subscribed to 'Revolt'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/dc', methods=["POST"]) def meetups_dc(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'D.C.'); app.logger.info("'" + address + "' has subscribed to 'D.C.'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/vancouver', methods=["POST"]) def meetups_vancouver(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Vancouver'); app.logger.info("'" + address + "' has subscribed to 'Vancouver'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/cambridge', methods=["POST"]) def meetups_cambridge(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Cambridge'); app.logger.info("'" + address + "' has subscribed to 'Cambridge'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/atlanta', methods=["POST"]) def meetups_atlanta(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Atlanta'); app.logger.info("'" + address + "' has subscribed to 'Atlanta'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/australia', methods=["POST"]) def meetups_australia(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Australia'); app.logger.info("'" + address + "' has subscribed to 'Australia'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/tokyo', methods=["POST"]) def meetups_tokyo(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Tokyo'); app.logger.info("'" + address + "' has subscribed to 'Tokyo'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/berlin', methods=["POST"]) def meetups_berlin(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Berlin'); app.logger.info("'" + address + "' has subscribed to 'Berlin'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/termina', methods=["POST"]) def meetups_termina(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Termina'); app.logger.info("'" + address + "' has subscribed to 'Termina'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/london', methods=["POST"]) def meetups_london(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'London'); app.logger.info("'" + address + "' has subscribed to 'London'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/seattle', methods=["POST"]) def meetups_seattle(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Seattle'); app.logger.info("'" + address + "' has subscribed to 'Seattle'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/portland', methods=["POST"]) def meetups_portland(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Portland'); app.logger.info("'" + address + "' has subscribed to 'Portland'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/boston', methods=["POST"]) def meetups_boston(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Boston'); app.logger.info("'" + address + "' has subscribed to 'Boston'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/las_vegas', methods=["POST"]) def meetups_las_vegas(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Las Vegas'); app.logger.info("'" + address + "' has subscribed to 'Las Vegas'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/san_francisco', methods=["POST"]) def meetups_san_francisco(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'San Francisco'); app.logger.info("'" + address + "' has subscribed to 'San Francisco'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/new_york_city', methods=["POST"]) def meetups_new_york_city(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'New York City'); app.logger.info("'" + address + "' has subscribed to 'New York City'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/detroit', methods=["POST"]) def meetups_detroit(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Detroit'); app.logger.info("'" + address + "' has subscribed to 'Detroit'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/stockholm', methods=["POST"]) def meetups_stockholm(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Stockholm'); app.logger.info("'" + address + "' has subscribed to 'Stockholm'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/gothenburg', methods=["POST"]) def meetups_gothenburg(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Gothenburg'); app.logger.info("'" + address + "' has subscribed to 'Gothenburg'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/grenoble', methods=["POST"]) def meetups_grenoble(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Grenoble'); app.logger.info("'" + address + "' has subscribed to 'Grenoble'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/israel', methods=["POST"]) def meetups_israel(): address = request.form['email'] # @robustness @abner what if it's empty? insert_subscriber(address, request.headers.get('User-Agent'), 'Israel'); app.logger.info("'" + address + "' has subscribed to 'Israel'") return redirect(AGREEMENT_SUCCESS_URL); @app.route('/v1/meetups/subscribers', methods=["POST"]) def meetups_subscribers(): # Verify headers content_type = ( request.headers['Content-Type'] if 'Content-Type' in request.headers else '' ) if 'application/json' in content_type: payload = request.json else: resp = { 'error' : 'JSON payload expected'} return make_response(jsonify(resp), 415) # Confirm client version is compatible if 'client' not in payload or payload['client'] != MEETUP_INVITE_CLIENT_VERSION: resp = { 'error' : 'Outdated client. Download latest version: https://git.handmadecities.com/meetups/meetupinvite2000/releases' } return make_response(jsonify(resp), 403) # Extract city from POST request if 'city' not in payload: resp = { 'error' : 'city field missing' } return make_response(jsonify(resp), 400) city = payload['city'] app.logger.info('Received subscribers request with city \'' + city + '\'') if city not in shared_secrets: resp = { 'error' : 'There are no official Handmade meetups in ' + city } return make_response(jsonify(resp), 400) auth_header = ( request.headers['Authorization'] if 'Authorization' in request.headers else '') if shared_secrets[city] not in auth_header: resp = { 'error' : 'wrong shared secret for ' + city } return make_response(jsonify(resp), 401) # Query emails given the city cur = g.db.cursor() cur.execute('SELECT email FROM Subscribers WHERE city="' + city + '"') rows = cur.fetchall() emails = [row[0] for row in rows] resp = { 'emails' : emails } return make_response(jsonify(resp), 200) @app.route('/v1/meetups/chat/invite', methods=["POST"]) def meetups_chat_invite(): content_type = ( request.headers['Content-Type'] if 'Content-Type' in request.headers else '' ) if 'application/json' not in content_type: resp = { 'error': 'JSON payload expected' } return make_response(jsonify(resp), 415) payload = request.json if not payload or 'city' not in payload: resp = { 'error': 'city field missing' } return make_response(jsonify(resp), 400) city = payload['city'] app.logger.info('Received chat invite request for city \'' + city + '\'') if city not in shared_secrets: resp = { 'error' : 'There are no official Handmade meetups in ' + city } return make_response(jsonify(resp), 400) auth_header = ( request.headers['Authorization'] if 'Authorization' in request.headers else '') if shared_secrets[city] not in auth_header: resp = { 'error' : 'wrong shared secret for ' + city } return make_response(jsonify(resp), 401) # Pick a random invite code and return it code = random.choice(INVITE_CODES) app.logger.info('Issued chat invite for ' + city + ': ' + code) resp = { 'invite_code': code, # TODO(jmug): Verify with Abner is this is an actual secret. # NOTE(jmug): Request "revolt-invite-link" secret. 'invite_link': '', # NOTE(jmug): Request postmark-server-token secret. 'postmark_server_token': '', 'postmark_template_id_register': 41923378, 'postmark_template_id_join': 41930444 } return make_response(jsonify(resp), 200)