| @@ -0,0 +1,11 @@ | |||
| version: "3.9" | |||
| services: | |||
| web: | |||
| build: ./web | |||
| container_name: python | |||
| ports: | |||
| - "5000:5000" | |||
| volumes: | |||
| - ./web:/code | |||
| env_file: | |||
| - ./.env.dev | |||
| @@ -0,0 +1,7 @@ | |||
| # syntax=docker/dockerfile:1 | |||
| FROM python:3.8-slim-buster | |||
| WORKDIR /code | |||
| COPY requirements.txt requirements.txt | |||
| RUN pip install -r requirements.txt | |||
| COPY . . | |||
| CMD ["python3", "-m", "flask", "run"] | |||
| @@ -0,0 +1,35 @@ | |||
| from flask import Flask | |||
| from flask_sqlalchemy import SQLAlchemy | |||
| from flask_login import LoginManager | |||
| # init SQLAlchemy so we can use it later in our models | |||
| db = SQLAlchemy() | |||
| def create_app(): | |||
| app = Flask(__name__) | |||
| app.config['SECRET_KEY'] = 'f9DWPyF70N' | |||
| app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite' | |||
| db.init_app(app) | |||
| login_manager = LoginManager() | |||
| login_manager.login_view = 'auth.login' | |||
| login_manager.init_app(app) | |||
| from .models import User | |||
| @login_manager.user_loader | |||
| def load_user(user_id): | |||
| # since the user_id is just the primary key of our user table, use it in the query for the user | |||
| return User.query.get(int(user_id)) | |||
| # blueprint for auth routes in our app | |||
| from .auth import auth as auth_blueprint | |||
| app.register_blueprint(auth_blueprint) | |||
| # blueprint for non-auth parts of app | |||
| from .main import main as main_blueprint | |||
| app.register_blueprint(main_blueprint) | |||
| return app | |||
| @@ -0,0 +1,60 @@ | |||
| from flask import Blueprint, render_template, redirect, url_for, request, flash | |||
| from werkzeug.security import generate_password_hash, check_password_hash | |||
| from flask_login import login_user, logout_user, login_required | |||
| from .models import User | |||
| from . import db | |||
| auth = Blueprint('auth', __name__) | |||
| @auth.route('/login') | |||
| def login(): | |||
| return render_template('login.html') | |||
| @auth.route('/login', methods=['POST']) | |||
| def login_post(): | |||
| email = request.form.get('email') | |||
| password = request.form.get('password') | |||
| remember = True if request.form.get('remember') else False | |||
| user = User.query.filter_by(email=email).first() | |||
| # check if the user actually exists | |||
| # take the user-supplied password, hash it, and compare it to the hashed password in the database | |||
| if not user or not check_password_hash(user.password, password): | |||
| flash('Please check your login details and try again.') | |||
| return redirect(url_for('auth.login')) # if the user doesn't exist or password is wrong, reload the page | |||
| # if the above check passes, then we know the user has the right credentials | |||
| login_user(user, remember=remember) | |||
| return redirect(url_for('main.profile')) | |||
| @auth.route('/signup') | |||
| def signup(): | |||
| return render_template('signup.html') | |||
| @auth.route('/signup', methods=['POST']) | |||
| def signup_post(): | |||
| email = request.form.get('email') | |||
| name = request.form.get('name') | |||
| password = request.form.get('password') | |||
| user = User.query.filter_by(email=email).first() # if this returns a user, then the email already exists in database | |||
| if user: # if a user is found, we want to redirect back to signup page so user can try again | |||
| flash('Email address already exists') | |||
| return redirect(url_for('auth.signup')) | |||
| # create a new user with the form data. Hash the password so the plaintext version isn't saved. | |||
| new_user = User(email=email, name=name, password=generate_password_hash(password, method='sha256')) | |||
| # add the new user to the database | |||
| db.session.add(new_user) | |||
| db.session.commit() | |||
| return redirect(url_for('auth.login')) | |||
| @auth.route('/logout') | |||
| @login_required | |||
| def logout(): | |||
| logout_user() | |||
| return redirect(url_for('main.index')) | |||
| @@ -0,0 +1,14 @@ | |||
| from flask import Blueprint, render_template | |||
| from flask_login import login_required, current_user | |||
| from . import db | |||
| main = Blueprint('main', __name__) | |||
| @main.route('/') | |||
| def index(): | |||
| return render_template('index.html') | |||
| @main.route('/profile') | |||
| @login_required | |||
| def profile(): | |||
| return render_template('profile.html', name=current_user.name) | |||
| @@ -0,0 +1,15 @@ | |||
| from flask_login import UserMixin | |||
| from . import db | |||
| from datetime import datetime | |||
| class User(UserMixin, db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | |||
| email = db.Column(db.String(100), unique=True) | |||
| password = db.Column(db.String(100)) | |||
| name = db.Column(db.String(1000)) | |||
| class Tools(db.Model): | |||
| id = db.Column(db.Integer, primary_key=True) # primary keys are required by SQLAlchemy | |||
| created = db.Column(db.DateTime, default=datetime.utcnow) | |||
| name = db.Column(db.Text) | |||
| description = db.Column(db.Text) | |||
| @@ -0,0 +1,57 @@ | |||
| <!DOCTYPE html> | |||
| <html> | |||
| <head> | |||
| <meta charset="utf-8"> | |||
| <meta http-equiv="X-UA-Compatible" content="IE=edge"> | |||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||
| <title>Flask Auth Example</title> | |||
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.2/css/bulma.min.css" /> | |||
| </head> | |||
| <body> | |||
| <section class="hero is-primary is-fullheight"> | |||
| <div class="hero-head"> | |||
| <nav class="navbar"> | |||
| <div class="container"> | |||
| <div id="navbarMenuHeroA" class="navbar-menu"> | |||
| <div class="navbar-end"> | |||
| <a href="{{ url_for('main.index') }}" class="navbar-item"> | |||
| Home | |||
| </a> | |||
| {% if current_user.is_authenticated %} | |||
| <a href="{{ url_for('main.profile') }}" class="navbar-item"> | |||
| Profile | |||
| </a> | |||
| {% endif %} | |||
| {% if not current_user.is_authenticated %} | |||
| <a href="{{ url_for('auth.login') }}" class="navbar-item"> | |||
| Login | |||
| </a> | |||
| <a href="{{ url_for('auth.signup') }}" class="navbar-item"> | |||
| Sign Up | |||
| </a> | |||
| {% endif %} | |||
| {% if current_user.is_authenticated %} | |||
| <a href="{{ url_for('auth.logout') }}" class="navbar-item"> | |||
| Logout | |||
| </a> | |||
| {% endif %} | |||
| </div> | |||
| </div> | |||
| </div> | |||
| </nav> | |||
| </div> | |||
| <div class="hero-body"> | |||
| <div class="container has-text-centered"> | |||
| {% block content %} | |||
| {% endblock %} | |||
| </div> | |||
| </div> | |||
| </section> | |||
| </body> | |||
| </html> | |||
| @@ -0,0 +1,10 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <h1 class="title"> | |||
| COPIM online toolkit | |||
| </h1> | |||
| <h2 class="subtitle"> | |||
| testing online toolkit site | |||
| </h2> | |||
| {% endblock %} | |||
| @@ -0,0 +1,36 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3 class="title">Login</h3> | |||
| <div class="box"> | |||
| {% with messages = get_flashed_messages() %} | |||
| {% if messages %} | |||
| <div class="notification is-danger"> | |||
| {{ messages[0] }} | |||
| </div> | |||
| {% endif %} | |||
| {% endwith %} | |||
| <form method="POST" action="/login"> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="email" name="email" placeholder="Your Email" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="password" name="password" placeholder="Your Password"> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <label class="checkbox"> | |||
| <input type="checkbox"> | |||
| Remember me | |||
| </label> | |||
| </div> | |||
| <button class="button is-block is-info is-large is-fullwidth">Login</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||
| @@ -0,0 +1,7 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <h1 class="title"> | |||
| Welcome, {{ name }}! | |||
| </h1> | |||
| {% endblock %} | |||
| @@ -0,0 +1,37 @@ | |||
| {% extends "base.html" %} | |||
| {% block content %} | |||
| <div class="column is-4 is-offset-4"> | |||
| <h3 class="title">Sign Up</h3> | |||
| <div class="box"> | |||
| {% with messages = get_flashed_messages() %} | |||
| {% if messages %} | |||
| <div class="notification is-danger"> | |||
| {{ messages[0] }}. Go to <a href="{{ url_for('auth.login') }}">login page</a>. | |||
| </div> | |||
| {% endif %} | |||
| {% endwith %} | |||
| <form method="POST" action="/signup"> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="email" name="email" placeholder="Email" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="text" name="name" placeholder="Name" autofocus=""> | |||
| </div> | |||
| </div> | |||
| <div class="field"> | |||
| <div class="control"> | |||
| <input class="input is-large" type="password" name="password" placeholder="Password"> | |||
| </div> | |||
| </div> | |||
| <button class="button is-block is-info is-large is-fullwidth">Sign Up</button> | |||
| </form> | |||
| </div> | |||
| </div> | |||
| {% endblock %} | |||
| @@ -0,0 +1,4 @@ | |||
| flask | |||
| requests | |||
| flask-sqlalchemy | |||
| flask-login | |||