from datetime import timedelta from flask import Flask, abort, jsonify, make_response, request from flask_bcrypt import Bcrypt from flask_jwt_extended import JWTManager, create_access_token, create_refresh_token, get_jwt, get_jwt_identity, jwt_required import logging import sqlite3 import sys import db app = Flask(__name__) app.config['JWT_SECRET_KEY'] = 'secret' # TODO change and load from a secrets store (should be over 32 bytes long) app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(hours=1) app.config['JWT_REFRESH_TOKEN_EXPIRES'] = timedelta(days=30) flask_bcrypt = Bcrypt(app) jwt = JWTManager(app) cur = sqlite3.connect("sms.db") file_handler = logging.FileHandler('log/out.log') file_handler.setLevel(logging.INFO) file_handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')) app.logger.addHandler(file_handler) app.logger.setLevel(logging.INFO) msg_403_not_primary = "Only a PRIMARY device can perform this action" msg_403_not_secondary = "Only a SECONDARY device can perform this action" @app.route("/api/v1/login", methods=["POST"]) def login(): access_key = request.json.get("access_key", None) if access_key is None: return make_response(jsonify({"msg": "Missing access key"}), 400) secret_key = request.json.get("secret_key", None) if secret_key is None: return make_response(jsonify({"msg": "Missing secret key"}), 400) res_tuple = cur.execute("SELECT secret_key_hash, type FROM devices WHERE access_key = ?", (access_key,)).fetchone() if res_tuple is None: return make_response(jsonify({"msg": "Invalid access key or secret key"}), 401) (secret_key_hash, type_claim) = res_tuple if flask_bcrypt.check_password_hash(secret_key_hash, secret_key): # "typ" claim means device type extra_claims = {"typ": type_claim} # generate and return JWT return make_response( jsonify({"access_token": create_access_token(identity=access_key, additional_claims=extra_claims), "refresh_token": create_refresh_token(identity=access_key, additional_claims=extra_claims)}), 200) else: return make_response(jsonify({"msg": "Invalid access key or secret key"}), 401) @app.route("/api/v1/verify-token", methods=["GET"]) @jwt_required() def verify_token(): return make_response(jsonify(logged_in_as=get_jwt_identity()), 200) @app.route("/api/v1/refresh-token", methods=["POST"]) @jwt_required(refresh=True) def refresh_token(): return make_response(jsonify({"access_token": create_access_token(identity=get_jwt_identity(), additional_claims={"typ": get_jwt()["typ"]})}), 200) @app.route("/api/v1/devices", methods=["GET"]) @jwt_required() def get_all_devices(): if get_jwt()["typ"] != "PRIMARY": return make_response(jsonify(msg=msg_403_not_primary), 403) return make_response(jsonify([d.to_dict() for d in db.get_all_devices(cur)]), 200) @app.route("/api/v1/sim-cards", methods=["GET"]) @jwt_required() def get_sim_cards_by_device(): if not is_primary(get_jwt()): return make_response(jsonify(msg=msg_403_not_primary), 403) access_key = request.args.get('access_key', None) return make_response(jsonify([s.to_dict() for s in db.get_sim_cards_by_device(cur, access_key)]), 200) @app.route("/api/v1/sms-messages", methods=["GET"]) @jwt_required() def get_sms_messages_by_local_phone_number(): if not is_primary(get_jwt()): return make_response(jsonify(msg=msg_403_not_primary), 403) local_phone_number = request.args.get("local_phone_number", None) return make_response(jsonify([n.to_dict() for n in db.get_sms_messages_by_local_phone_number(cur, local_phone_number)]), 200) @app.route("/api/v1/send-message", methods=["POST"]) @jwt_required() def send_sms_message(): if not is_primary(get_jwt()): return make_response(jsonify(msg=msg_403_not_primary), 403) content = request.json.get("content", None) access_key = get_jwt_identity() local_phone_number = request.json.get("local_phone_number", None) remote_phone_number = request.json.get("remote_phone_number", None) app.logger.debug(f"sending msg: content='{content}', access_key='{access_key}', local_num='{local_phone_number}', remote_num='{remote_phone_number}'") if db.send_sms_message(cur, content, access_key, local_phone_number, remote_phone_number): return make_response(jsonify(msg="Message successfully sent"), 200) else: return make_response(jsonify(msg="Failed to send message"), 400) @app.route("/api/v1/get-queued-sms-messages", methods=["GET"]) @jwt_required() def get_queued_sms_messages(): if not is_secondary(get_jwt()): return make_response(jsonify(msg=msg_403_not_secondary), 403) access_key = get_jwt_identity() local_phone_number = request.json.get("local_phone_number", None) return make_response(jsonify([m.to_dict() for m in db.get_queued_sms_messages(cur, local_phone_number)]), 200) def is_primary(jwt): return jwt["typ"] == "PRIMARY" def is_secondary(jwt): return jwt["typ"] == "SECONDARY"