Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User Management backend - Base - JWT support #40

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions Vagrantfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
FLASK_PORT=5000
POSTGRESQL_PORT=5432
db_file_exists = "test -f /vagrant/edison/db.sql"
restore_db = "sudo -u postgres psql edison < /vagrant/edison/db.sql"
db_restored_msg = "echo \"Database restored.\""
db_not_exists_msg = "echo \"db.sql not exists.\""
try_restore_db = "bash -c '#{db_file_exists} && #{restore_db} && #{db_restored_msg} || #{db_not_exists_msg}'"
save_db_data = "sudo -u postgres pg_dump edison > /vagrant/edison/db.sql"

Vagrant.configure("2") do |config|
config.trigger.before :destroy do |trigger|
trigger.info = "Saving database data inside synced folder..."
trigger.run_remote = {inline: "#{save_db_data}"}
end

config.trigger.after :up do |trigger|
trigger.info = "Trying to restore database from /vagrant/edison/db.sql..."
trigger.run_remote = {inline: "#{try_restore_db}"}
end

config.vm.box = "ubuntu/bionic64"
config.vm.provision :shell, path: "setup.sh"
config.vm.network :forwarded_port, guest: FLASK_PORT, host: FLASK_PORT
config.vm.network :forwarded_port, guest: POSTGRESQL_PORT, host: POSTGRESQL_PORT
config.vm.network "private_network", type: "dhcp"
config.vm.provider "virtualbox" do |v|
v.gui = false
v.name = "Edison_test"
Expand All @@ -14,3 +27,11 @@ Vagrant.configure("2") do |config|
end

end

# Fixes a dhcp configuration conflict of the private network.
# Issue: https://github.com/hashicorp/vagrant/issues/8878
class VagrantPlugins::ProviderVirtualBox::Action::Network
def dhcp_server_matches_config?(dhcp_server, config)
true
end
end
12 changes: 12 additions & 0 deletions edison/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import os

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from edison.config import get_config_object


# Put app here so the entire app could import it.
app = Flask(__name__)
app.config.from_object(get_config_object(app.config["ENV"]))
basedir = os.path.abspath(os.path.dirname(__file__))
db = SQLAlchemy(app)
34 changes: 34 additions & 0 deletions edison/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import edison
import edison.models as models

from flask import render_template
from flask_restful import Api
from flask_jwt_extended import JWTManager


app = edison.app
db = edison.db
api = Api(app)

# Creates all tables defined in the database models and the only ones that are not created yet.
# If there's any change in the database models you should perform a migration to apply this change in the database itself.
# More about database migrations can be found in /edison/migrations/README.
db.create_all()

# Creation of Json-Web-Token manager.
# In order to reach secured endpoints client should add an authorization header with the value Bearer <token>.
jwt = JWTManager(app)

# This decorator is a callback and it is called every time user is trying to access secured endpoints.
# The function under the decorator should return true or false depending on if the passed token is blacklisted.
@jwt.token_in_blacklist_loader
def check_if_token_in_blacklist(decrypted_token):
jti = decrypted_token['jti']
return models.Token.query.filter_by(jti=jti).first() is not None

@app.route("/")
def index():
return render_template('index.html')

if __name__ == "__main__":
app.run(host='0.0.0.0')
40 changes: 40 additions & 0 deletions edison/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import secrets
import importlib, inspect
import sys, inspect

config_dict = {}

# If config_dict is empty this function builds it dynamically
# and returns the appropriate config object path.
def get_config_object(env_keyword: str):
if(len(config_dict) == 0):
# Iterating through all config.py members
for name, obj in inspect.getmembers(sys.modules[__name__]):
# We're interested only with the derived classes of the Config class
if inspect.isclass(obj) and name != "Config":
config_dict[obj.ENV_KEYWORD] = ".".join([obj.__module__, name])

return config_dict[env_keyword]

class Config:
ENV_KEYWORD = ""
DEBUG = False
# Enables response message for unauthenticated requests
PROPAGATE_EXCEPTIONS = True
# This tells the JWTManager to use jwt.token_in_blacklist_loader callback
JWT_BLACKLIST_ENABLED = True
# JWTManager uses this secret key for creating tokens
JWT_SECRET_KEY = secrets.token_hex(24)
# We're going to check if both access_token and refresh_token are black listed
JWT_BLACKLIST_TOKEN_CHECKS = ['access', 'refresh']
# Turns off the Flask-SQLAlchemy event system
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:[email protected]/edison'

# PostgreSQL connection string should be updated once an actual production environment is established.
class ProductionConfig(Config):
ENV_KEYWORD = "production"

class DevelopmentConfig(Config):
ENV_KEYWORD = "development"
DEBUG = True
2 changes: 2 additions & 0 deletions edison/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .token import Token
from .user import User
12 changes: 12 additions & 0 deletions edison/models/token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from edison import db


class Token(db.Model):
__tablename__ = 'token_blacklist'

id = db.Column(db.Integer, primary_key=True)
jti = db.Column(db.String(150), nullable=False, unique=True)
creation_timestamp = db.Column(db.TIMESTAMP(timezone=False), nullable=False)

def __repr__(self):
return f"<Token: jti: {self.jti}, creation time: {self.creation_timestamp}>"
25 changes: 25 additions & 0 deletions edison/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from edison import db


class User(db.Model):
__tablename__ = 'users'
__table_args__ = {'extend_existing': True}

id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(150), nullable=False)
first_name = db.Column(db.String(50), nullable=False)
last_name = db.Column(db.String(50), nullable=False)
email = db.Column(db.String(150), nullable=False)

def to_json(self):
return {
"username": self.username,
"first_name": self.first_name,
"last_name": self.last_name,
"email": self.email
}

def __repr__(self):
return f"<User: id = {self.id}, first_name = {self.first_name}, " \
f"last_name = {self.last_name}, username = {self.username}, email = {self.email}>"
File renamed without changes
File renamed without changes.
10 changes: 0 additions & 10 deletions flask_init.py

This file was deleted.

5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
Flask==1.1.1
flask-sqlalchemy==2.4.1
psycopg2-binary==2.8.5
flask-restful==0.3.8
flask-jwt-extended==3.24.1
passlib==1.7.2
14 changes: 11 additions & 3 deletions setup.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/bin/bash

FLASK_PORT=5000

echo "updating apt before installation"
sudo apt-get update

Expand All @@ -15,6 +17,12 @@ sudo apt-get install -y postgresql postgresql-contrib
echo "install requirements"
pip3 install -r /vagrant/requirements.txt

echo "running flask_init.py"
export FLASK_APP=/vagrant/flask_init.py
python3 -m flask run --host=0.0.0.0 >> /vagrant/log.log 2>&1 &
echo "configuring database"
sudo -u postgres createdb edison
sudo -u postgres psql -c "ALTER ROLE postgres WITH PASSWORD 'edison';"

export FLASK_ENV=development

echo "running app.py"
export FLASK_APP=/vagrant/edison/app.py
flask run -h 0.0.0.0 -p $FLASK_PORT >> /vagrant/edison/app.log 2>&1 &
File renamed without changes.