Skip to content
Snippets Groups Projects
Commit 9218c8f2 authored by Victor Löfgren's avatar Victor Löfgren
Browse files

config: Initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 574 additions and 0 deletions
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
/// <reference types="react-scripts" />
import { ReportHandler } from 'web-vitals';
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
# Server
This document describes how to install and run the server.
## Installing
You will need to do the following things to install the server:
1. Install [Visual Studio Code](https://code.visualstudio.com/) (VSCode).
2. Install [Python](https://www.python.org/downloads/).
3. Clone this repository if you haven't done so already.
4. Open the project folder in VSCode.
5. Open the integrated terminal by pressing `ctrl+ö`.
6. Type the following commands (or if you are on Windows, simply paste them) into your terminal:
```bash
# Install virtualenv package. You may need to open as administrator if you get
# permission denied.
pip install virtualenv
# Go into the server folder.
cd server
# Create a python virtual environment. If it can't find Python, try
# python or py -3 instead of py.
py -m venv env
# Activate the virtual environment.
# This step is different depending on your operating system.
# Windows
# You migt to run the following before activating the virtual environment.
Set-ExecutionPolicy Unrestricted -Scope Process
./env/Scripts/activate
# Linux/Mac
# source env/bin/activate
# Install all the required packages into your virtual environment.
pip install -r requirements.txt
```
## Using
After you have done every step described in setup, you are ready to run the server.
You can either run the server using tasks (recommended) or run it directly in the terminal.
### Tasks
You can run the server using Visual Studio Code tasks.
This is done by pressing `ctrl+shift+b` and running the `Server` task.
### Terminal
You can also run the server and tests directly from the terminal.
Before doing anything in the terminal, you need to activate the Python virtual environment (see Setup).
All of the following snippets assume you are in the `server` folder.
Running the server:
```bash
python main.py
```
Running the tests:
```bash
python test.py
```
Adding new packages:
```bash
pip install new_package
pip freeze > requirements.txt
```
from flask import Flask
from flask_bcrypt import Bcrypt
from flask_jwt_extended.jwt_manager import JWTManager
from flask_sqlalchemy import SQLAlchemy
from app.database import Base
bcrypt = Bcrypt()
jwt = JWTManager()
db = SQLAlchemy(model_class=Base)
from app.database import models
def create_app(config_name="configmodule.DevelopmentConfig"):
app = Flask(__name__)
app.config.from_object(config_name)
with app.app_context():
bcrypt.init_app(app)
jwt.init_app(app)
db.init_app(app)
from app.api import api_blueprint
app.register_blueprint(api_blueprint, url_prefix="/api")
return app
def identity(payload):
user_id = payload["identity"]
return models.User.query.filter_by(id=user_id)
@jwt.token_in_blacklist_loader
def check_if_token_in_blacklist(decrypted_token):
jti = decrypted_token["jti"]
return models.Blacklist.query.filter_by(jti=jti).first() != None
from flask import Blueprint
api_blueprint = Blueprint("api", __name__)
# Import the rest of the routes.
from app.api import users, admin
###
# Admin stuff placed here for later use
# No need to implement this before the application is somewhat done
# Only adding basic thigns like addning countries
###
from flask import Blueprint
admin_blueprint = Blueprint("admin", __name__)
import datetime
from flask import request
from flask_jwt_extended import (
jwt_required,
create_access_token,
jwt_refresh_token_required,
create_refresh_token,
get_jwt_identity,
get_raw_jwt,
)
from app import db
from app.api import api_blueprint
from app.database.models import User, Blacklist
from app.utils.validator import validateObject, login_schema, register_schema, edit_user_schema
def get_current_user():
return User.query.filter_by(id=get_jwt_identity()).first()
@api_blueprint.route("/users/test", methods=["GET"])
def test():
return {"message": "hello teknik8"}, 200
@api_blueprint.route("/users/test_auth", methods=["GET"])
@jwt_required
def test_auth():
return {"message": "you are authenticated"}, 200
@api_blueprint.route("/users/login", methods=["POST"])
def login():
json_dict = request.get_json(force=True)
validate_msg = validateObject(login_schema, json_dict)
if validate_msg != None:
return {"message": validate_msg}, 400
email = json_dict["email"]
password = json_dict["password"]
user = User.query.filter_by(email=email).first()
# Dont show the user that the email was correct unless the password was also correct
if not user:
return {"message": "The email or password you entered is incorrect."}, 401
if not user.is_correct_password(password):
return {"message": "The email or password you entered is incorrect."}, 401
expires = datetime.timedelta(days=7)
access_token = create_access_token(identity=user.id, expires_delta=expires)
refresh_token = create_refresh_token(identity=user.id)
return (
{"id": user.id, "access_token": access_token, "refresh_token": refresh_token},
200,
)
@api_blueprint.route("/users/logout", methods=["POST"])
@jwt_required
def logout():
jti = get_raw_jwt()["jti"]
db.session.add(Blacklist(jti))
db.session.commit()
return {"message": "message fully logged out"}, 200
@api_blueprint.route("/users/refresh", methods=["POST"])
@jwt_refresh_token_required
def refresh():
current_user = get_jwt_identity()
ret = {"access_token": create_access_token(identity=current_user)}
return ret, 200
@api_blueprint.route("/users/", methods=["POST"])
def create():
json_dict = request.get_json(force=True)
validate_msg = validateObject(register_schema, json_dict)
if validate_msg != None:
return {"message": validate_msg}, 400
existing_user = User.query.filter_by(email=json_dict["email"]).first()
if existing_user != None:
return {"message": "User already exists"}, 400
user = User(json_dict["email"], json_dict["password"])
db.session.add(user)
db.session.commit()
return user.get_dict(), 200
@api_blueprint.route("/users/", methods=["PUT"])
@jwt_required
def edit():
json_dict = request.get_json(force=True)
validate_msg = validateObject(edit_user_schema, json_dict)
if validate_msg != None:
return {"message": validate_msg}, 400
user = get_current_user()
user.name = json_dict["name"].title()
db.session.commit()
return user.get_dict(), 200
@api_blueprint.route("/users/", methods=["DELETE"])
@jwt_required
def delete():
user = get_current_user()
db.session.delete(user)
jti = get_raw_jwt()["jti"]
db.session.add(Blacklist(jti))
db.session.commit()
return {"message": "User was deleted"}, 200
###
# Getters
###
@api_blueprint.route("/users/", defaults={"UserID": None}, methods=["GET"])
@api_blueprint.route("/users/<int:UserID>", methods=["GET"])
@jwt_required
def get(UserID):
if UserID:
user = User.query.filter_by(id=UserID).first()
else:
user = get_current_user()
if not user:
return {"message": "User not found"}, 404
return user.get_dict(), 200
# Searchable, returns 10 max at default
@api_blueprint.route("/users/search", methods=["GET"])
def search():
arguments = request.args
query = User.query
if "name" in arguments:
query = query.filter(User.name.like(f"%{arguments['name']}%"))
if "email" in arguments:
query = query.filter(User.email.like(f"%{arguments['email']}%"))
if "page" in arguments:
page_size = 15
if "page_size" in arguments:
page_size = int(arguments["page_size"])
query = query.limit(page_size)
query = query.offset(int(arguments["page"]) * page_size)
else:
query = query.limit(15)
return [i.get_dict() for i in query.all()], 200
from flask_sqlalchemy.model import Model
import sqlalchemy as sa
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.sql import func
class Base(Model):
@declared_attr
def __tablename__(self):
return self.__class__.__name__.replace("Model", "s").lower()
created = sa.Column(sa.DateTime(timezone=True), server_default=func.now())
updated = sa.Column(sa.DateTime(timezone=True), onupdate=func.now())
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method
from sqlalchemy.ext.declarative import declared_attr
from app import bcrypt, db
from app.database import Base
class Blacklist(db.Model):
id = db.Column(db.Integer, primary_key=True)
jti = db.Column(db.String, unique=True, nullable=False)
@declared_attr
def __tablename__(self):
return "blacklist"
def __init__(self, jti):
self.jti = jti
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(254), unique=True, nullable=False)
name = db.Column(db.String(50), nullable=True)
_password = db.Column(db.LargeBinary(60), nullable=False)
authenticated = db.Column(db.Boolean, default=False)
twoAuthConfirmed = db.Column(db.Boolean, default=True) # Change to false for Two factor authen
twoAuthCode = db.Column(db.String(100), nullable=True)
def __init__(self, email, plaintext_password, name=""):
self._password = bcrypt.generate_password_hash(plaintext_password)
self.email = email
self.name = name
self.authenticated = False
def get_dict(self):
return {"id": self.id, "email": self.email, "name": self.name}
@hybrid_property
def password(self):
return self._password
@password.setter
def set_password(self, plaintext_password):
self._password = bcrypt.generate_password_hash(plaintext_password)
@hybrid_method
def is_correct_password(self, plaintext_password):
return bcrypt.check_password_hash(self._password, plaintext_password)
import datetime
def parse_date(date_to_parse):
return datetime.datetime.strptime(date_to_parse, "%Y-%m-%d").date()
from requests import *
import json
HOST = "http://localhost:5000/"
def _post(url: str, jdict: dict):
post(HOST + url, json.dumps(jdict))
def asdasd(i: int):
return i
print("Populate default database data")
_post("user/", {"email": "admin@test.com", "password": "password", "name": "Admin Adminsson"})
from cerberus import Validator
def validateObject(schema, obj, allow_unknown=False):
v = Validator(schema, allow_unknown)
if not v.validate(obj):
return v.errors
_email_regex = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$"
login_schema = {
"email": {"type": "string", "required": True, "regex": _email_regex},
"password": {"type": "string", "required": True, "minlength": 6, "maxlength": 128},
}
register_schema = {
"email": {"type": "string", "required": True, "regex": _email_regex},
"password": {"type": "string", "required": True, "minlength": 6, "maxlength": 128},
}
edit_user_schema = {
"name": {"type": "string", "required": False, "minlength": 1, "maxlength": 50},
}
class Config:
DEBUG = False
TESTING = False
SQLALCHEMY_DATABASE_URI = "sqlite:///database.db"
JWT_SECRET_KEY = "super-secret"
JWT_BLACKLIST_ENABLED = True
JWT_BLACKLIST_TOKEN_CHECKS = ["access", "refresh"]
class DevelopmentConfig(Config):
DEBUG = True
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = "sqlite:///test.db"
SQLALCHEMY_TRACK_MODIFICATIONS = False
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = "sqlite:///database.db"
# HOST = 'postgresql'
# PORT = 5432
# USER = 'asd'
# PASSWORD = 'asd'
# DATABASE = 'asd'
# DATABASE_URI = 'postgresql://'+USER+":"+PASSWORD+"@"+HOST+":"+str(PORT)+"/"+DATABASE
from app import create_app, db
# Development port
DEFAULT_DEV_PORT = 5000
# Production port
DEFAULT_PRO_PORT = 8080
if __name__ == "__main__":
app = create_app("configmodule.DevelopmentConfig")
with app.app_context():
db.create_all()
app.run(port=5000)
# CONFIG = "configmodule.DevelopmentConfig"
# if "production-teknik8" in os.environ:
# CONFIG = "configmodule.ProductionConfig"
# if "configmodule.DevelopmentConfig" == CONFIG:
# app.run(port=DEFAULT_DEV_PORT)
# else:
# app.run(host="0.0.0.0", port=DEFAULT_PRO_PORT)
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
from app import create_app, db
import unittest
import json
class Test(unittest.TestCase):
def setUp(self):
"""Before each test, set up a blank database"""
self.app = create_app("configmodule.TestingConfig")
self.app.testing = True
self.client = self.app.test_client()
with self.app.app_context():
db.drop_all()
db.create_all()
# Called after every test
def tearDown(self):
with self.app.app_context():
db.session.remove()
db.drop_all()
def test_user(self):
# Create user
rv = self.client.post(
"/api/users/",
data=json.dumps({"email": "test@test.se", "password": "abc123"}),
)
rv_dict = json.loads(rv.data.decode())
assert rv.status_code == 200
assert rv_dict["id"] == 1
assert "password" not in rv_dict
assert rv_dict["email"] == "test@test.se"
# Try loggin with wrong PASSWORD
rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc1234"}))
assert rv.status_code == 401
# Try loggin with wrong Email
rv = self.client.post("/api/users/login", data=json.dumps({"email": "test1@test.se", "password": "abc1234"}))
assert rv.status_code == 401
# Try loggin with right PASSWORD
rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc123"}))
rv_dict = json.loads(rv.data.decode())
assert rv.status_code == 200
headers = {"Authorization": "Bearer " + rv_dict["access_token"]}
# Get the current user
rv = self.client.get("/api/users/", headers=headers)
rv_dict = json.loads(rv.data.decode())
assert rv.status_code == 200
assert rv_dict["email"] == "test@test.se"
rv = self.client.put("/api/users/", data=json.dumps({"name": "carl carlsson"}), headers=headers)
rv_dict = json.loads(rv.data.decode())
assert rv.status_code == 200
assert rv_dict["name"] == "Carl Carlsson"
def test_empty(self):
# Try loggin withou any users
rv = self.client.post("/api/users/login", data=json.dumps({"email": "test@test.se", "password": "abc123"}))
assert rv.status_code == 401
if __name__ == "__main__":
unittest.main()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment