1
0
mirror of https://github.com/LukePeters/flask-mongo-api-boilerplate.git synced 2026-05-17 07:36:30 +09:00

First push to GitHub

This commit is contained in:
Luke Peters
2019-02-17 17:04:03 -05:00
commit 6d77347505
18 changed files with 751 additions and 0 deletions

38
api/main/__init__.py Normal file
View File

@@ -0,0 +1,38 @@
from flask import Flask, request
from flask_cors import CORS
from pymongo import MongoClient
from main.tools import JsonResp
from jose import jwt
import os
# Import Routes
from main.user.routes import user_blueprint
def create_app():
# Flask Config
app = Flask(__name__)
app.config.from_pyfile("config/config.cfg")
cors = CORS(app, resources={r"/*": { "origins": app.config["FRONTEND_DOMAIN"] }})
# Misc Config
os.environ["TZ"] = app.config["TIMEZONE"]
# Database Config
if app.config["ENVIRONMENT"] == "development":
mongo = MongoClient(app.config["MONGO_HOSTNAME"], app.config["MONGO_PORT"])
app.db = mongo[app.config["MONGO_APP_DATABASE"]]
else:
mongo = MongoClient("localhost")
mongo[app.config["MONGO_AUTH_DATABASE"]].authenticate(app.config["MONGO_AUTH_USERNAME"], app.config["MONGO_AUTH_PASSWORD"])
app.db = mongo[app.config["MONGO_APP_DATABASE"]]
# Register Blueprints
app.register_blueprint(user_blueprint, url_prefix="/user")
# Index Route
@app.route("/")
def index():
return JsonResp({ "status": "Online" }, 200)
return app

65
api/main/auth/__init__.py Normal file
View File

@@ -0,0 +1,65 @@
from flask import current_app as app
from flask import request
from functools import wraps
from main.tools import JsonResp
from jose import jwt
import datetime
# Auth Decorator
def token_required(f):
@wraps(f)
def decorated(*args, **kwargs):
access_token = request.headers.get('AccessToken')
try:
data = jwt.decode(access_token, app.config['SECRET_KEY'])
except Exception as e:
return JsonResp({ "message": "Token is invalid", "exception": str(e) }, 401)
return f(*args, **kwargs)
return decorated
def encodeAccessToken(user_id, email, plan):
accessToken = jwt.encode({
"user_id": user_id,
"email": email,
"plan": plan,
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15) # The token will expire in 15 minutes
}, app.config["SECRET_KEY"], algorithm="HS256")
return accessToken
def encodeRefreshToken(user_id, email, plan):
refreshToken = jwt.encode({
"user_id": user_id,
"email": email,
"plan": plan,
"exp": datetime.datetime.utcnow() + datetime.timedelta(weeks=4) # The token will expire in 4 weeks
}, app.config["SECRET_KEY"], algorithm="HS256")
return refreshToken
def refreshAccessToken(refresh_token):
# If the refresh_token is still valid, create a new access_token and return it
try:
user = app.db.users.find_one({ "refresh_token": refresh_token }, { "_id": 0, "id": 1, "email": 1, "plan": 1 })
if user:
decoded = jwt.decode(refresh_token, app.config["SECRET_KEY"])
new_access_token = encodeAccessToken(decoded["user_id"], decoded["email"], decoded["plan"])
result = jwt.decode(new_access_token, app.config["SECRET_KEY"])
result["new_access_token"] = new_access_token
resp = JsonResp(result, 200)
else:
result = { "message": "Auth refresh token has expired" }
resp = JsonResp(result, 403)
except:
result = { "message": "Auth refresh token has expired" }
resp = JsonResp(result, 403)
return resp

View File

@@ -0,0 +1,21 @@
# Config files are not tracked in Git and must be placed manually in each
# app environment (e.g. development, staging, production).
# General
DEBUG = True
TIMEZONE = "US/Eastern"
SECRET_KEY = ""
ENVIRONMENT = "development"
FLASK_DIRECTORY = "##FLASK_DIRECTORY##"
FLASK_DOMAIN = "##FLASK_DOMAIN##"
FLASK_PORT = ##FLASK_PORT##
FRONTEND_DOMAIN = "##FRONTEND_DOMAIN##"
HTTP_HTTPS = "http://"
# Database
MONGO_HOSTNAME = "##MONGO_HOSTNAME##"
MONGO_PORT = 27017
MONGO_AUTH_DATABASE = ""
MONGO_AUTH_USERNAME = ""
MONGO_AUTH_PASSWORD = ""
MONGO_APP_DATABASE = "##MONGO_APP_DATABASE##"

View File

@@ -0,0 +1,43 @@
import random
import time
def nowEpoch():
return int(time.time()) * 1000
def JsonResp(data, status):
from flask import Response
import json
return Response(json.dumps(data), mimetype="application/json", status=status)
def randID():
randId = randString(3) + randString(3) + randString(3) + randString(3) + randString(3) + randString(3)
return randId
def randString(length):
randString = ""
for _ in range(length):
randString += random.choice("AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890")
return randString
def randStringCaps(length):
randString = ""
for _ in range(length):
randString += random.choice("ABCDEFGHJKLMNPQRSTUVWXYZ23456789")
return randString
def randStringNumbersOnly(length):
randString = ""
for _ in range(length):
randString += random.choice("23456789")
return randString
def validEmail(email):
import re
if re.match("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$", email) != None:
return True
else:
return False

View File

152
api/main/user/models.py Normal file
View File

@@ -0,0 +1,152 @@
from flask import current_app as app
from flask import Flask, request
from passlib.hash import pbkdf2_sha256
from jose import jwt
from main import tools
from main import auth
import json
class User:
def __init__(self):
self.defaults = {
"id": tools.randID(),
"ip_addresses": [request.remote_addr],
"acct_active": True,
"date_created": tools.nowEpoch(),
"last_login": tools.nowEpoch(),
"first_name": "",
"last_name": "",
"email": "",
"plan": "free"
}
def get(self):
token_data = jwt.decode(request.headers.get('AccessToken'), app.config['SECRET_KEY'])
user = app.db.users.find_one({ "id": token_data['user_id'] }, {
"_id": 0,
"password": 0
})
if user:
resp = tools.JsonResp(user, 200)
else:
resp = tools.JsonResp({ "message": "User not found" }, 404)
return resp
def getAuth(self):
access_token = request.headers.get("AccessToken")
refresh_token = request.headers.get("RefreshToken")
resp = tools.JsonResp({ "message": "User not logged in" }, 401)
if access_token:
try:
decoded = jwt.decode(access_token, app.config["SECRET_KEY"])
resp = tools.JsonResp(decoded, 200)
except:
# If the access_token has expired, get a new access_token - so long as the refresh_token hasn't expired yet
resp = auth.refreshAccessToken(refresh_token)
return resp
def login(self):
resp = tools.JsonResp({ "message": "Invalid user credentials" }, 403)
try:
data = json.loads(request.data)
email = data["email"].lower()
user = app.db.users.find_one({ "email": email }, { "_id": 0 })
if user and pbkdf2_sha256.verify(data["password"], user["password"]):
access_token = auth.encodeAccessToken(user["id"], user["email"], user["plan"])
refresh_token = auth.encodeRefreshToken(user["id"], user["email"], user["plan"])
app.db.users.update({ "id": user["id"] }, { "$set": {
"refresh_token": refresh_token,
"last_login": tools.nowEpoch()
} })
resp = tools.JsonResp({
"id": user["id"],
"email": user["email"],
"first_name": user["first_name"],
"last_name": user["last_name"],
"plan": user["plan"],
"access_token": access_token,
"refresh_token": refresh_token
}, 200)
except Exception:
pass
return resp
def logout(self):
try:
tokenData = jwt.decode(request.headers.get("AccessToken"), app.config["SECRET_KEY"])
app.db.users.update({ "id": tokenData["user_id"] }, { '$unset': { "refresh_token": "" } })
# Note: At some point I need to implement Token Revoking/Blacklisting
# General info here: https://flask-jwt-extended.readthedocs.io/en/latest/blacklist_and_token_revoking.html
except:
pass
resp = tools.JsonResp({ "message": "User logged out" }, 200)
return resp
def add(self):
data = json.loads(request.data)
expected_data = {
"first_name": data['first_name'],
"last_name": data['last_name'],
"email": data['email'].lower(),
"password": data['password']
}
# Merge the posted data with the default user attributes
self.defaults.update(expected_data)
user = self.defaults
# Encrypt the password
user["password"] = pbkdf2_sha256.encrypt(user["password"], rounds=20000, salt_size=16)
# Make sure there isn"t already a user with this email address
existing_email = app.db.users.find_one({ "email": user["email"] })
if existing_email:
resp = tools.JsonResp({
"message": "There's already an account with this email address",
"error": "email_exists"
}, 400)
else:
if app.db.users.save(user):
# Log the user in (create and return tokens)
access_token = auth.encodeAccessToken(user["id"], user["email"], user["plan"])
refresh_token = auth.encodeRefreshToken(user["id"], user["email"], user["plan"])
app.db.users.update({ "id": user["id"] }, {
"$set": {
"refresh_token": refresh_token
}
})
resp = tools.JsonResp({
"id": user["id"],
"email": user["email"],
"first_name": user["first_name"],
"last_name": user["last_name"],
"plan": user["plan"],
"access_token": access_token,
"refresh_token": refresh_token
}, 200)
else:
resp = tools.JsonResp({ "message": "User could not be added" }, 400)
return resp

27
api/main/user/routes.py Normal file
View File

@@ -0,0 +1,27 @@
from flask import Blueprint
from flask import current_app as app
from main.auth import token_required
from main.user.models import User
user_blueprint = Blueprint("user", __name__)
@user_blueprint.route("/", methods=["GET"])
@token_required
def get():
return User().get()
@user_blueprint.route("/auth/", methods=["GET"])
def getAuth():
return User().getAuth()
@user_blueprint.route("/login/", methods=["POST"])
def login():
return User().login()
@user_blueprint.route("/logout/", methods=["GET"])
def logout():
return User().logout()
@user_blueprint.route("/", methods=["POST"])
def add():
return User().add()