diff options
Diffstat (limited to 'app')
-rw-r--r-- | app/__init__.py | 2 | ||||
-rw-r--r-- | app/auth/__init__.py | 41 | ||||
-rw-r--r-- | app/auth/forms.py | 1 | ||||
-rw-r--r-- | app/config.py | 1 | ||||
-rw-r--r-- | app/database.py | 27 | ||||
-rw-r--r-- | app/manage/__init__.py | 50 | ||||
-rw-r--r-- | app/manage/forms.py | 8 | ||||
-rw-r--r-- | app/meta/__init__.py | 1 | ||||
-rw-r--r-- | app/static/gen/style.css | 46 | ||||
-rw-r--r-- | app/static/scss/style.scss | 10 | ||||
-rw-r--r-- | app/templates/base.html | 11 | ||||
-rw-r--r-- | app/templates/index.html | 8 | ||||
-rw-r--r-- | app/templates/network_list.html | 42 | ||||
-rw-r--r-- | app/templates/profile.html | 46 | ||||
-rw-r--r-- | app/templates/register.html | 17 |
15 files changed, 272 insertions, 39 deletions
diff --git a/app/__init__.py b/app/__init__.py index a2486ed..37c992e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -30,11 +30,13 @@ def create_app(): from .database import User, Role from . import auth from . import meta + from . import manage # Blueprints app.register_blueprint(auth.bp) app.register_blueprint(meta.bp) + app.register_blueprint(manage.bp) print(app.url_map) diff --git a/app/auth/__init__.py b/app/auth/__init__.py index b419351..06faa6d 100644 --- a/app/auth/__init__.py +++ b/app/auth/__init__.py @@ -1,6 +1,8 @@ -from flask import Blueprint, request, redirect, url_for, flash, render_template -from flask_login import current_user, login_user +from flask import Blueprint, request, redirect, url_for, flash, render_template, current_app +from flask_login import current_user, login_user, login_required +import flask_login from werkzeug.security import check_password_hash, generate_password_hash +from datetime import datetime from app.auth.forms import LoginForm, RegisterForm from app.database import User @@ -22,15 +24,21 @@ def login(): email = request.form.get('username') password = request.form.get('password') - u = User.query.fetch_one().filter_by(email=email) + u = User.query.filter_by(email=email).first() if u is not None: if check_password_hash(u.password, password): - login_user(u) + if u.active: + login_user(u) - flash("Logged in successfully") + u.last_login = datetime.now() + db.session.commit() + + flash("Logged in successfully") - return redirect(url_for("meta.home")) + return redirect(url_for("meta.home")) + else: + flash("User is inactive. Contact an administrator") else: flash("Incorrect password") @@ -57,7 +65,7 @@ def register(): # Passwords match user = User( - id=str(ulid.new()), + id=str(ulid.ulid()), email=email, password=generate_password_hash(password), pref_name=pref_name, @@ -73,4 +81,21 @@ def register(): else: flash("Passwords do not match") - return render_template("register.html") + return render_template("register.html", form=form) + + +@bp.route("/logout") +@login_required +def logout(): + flask_login.logout_user() + + return redirect("/") + +@bp.route("/profile") +@login_required +def profile(): + debug = current_app.config['DEBUG'] + peers = current_user.peers + networks = current_user.networks + return render_template("profile.html", debug=debug, peers=peers, + nets=networks) diff --git a/app/auth/forms.py b/app/auth/forms.py index 778e4fb..ce8ed4c 100644 --- a/app/auth/forms.py +++ b/app/auth/forms.py @@ -9,6 +9,7 @@ class LoginForm(FlaskForm): class RegisterForm(FlaskForm): username = StringField("Email", validators=[DataRequired()]) + pref_name = StringField("Preferred Name", validators=[DataRequired()]) password = PasswordField("Password", validators=[DataRequired()]) password_confirm = PasswordField("Confirm Password", validators=[DataRequired()]) submit = SubmitField("Register") diff --git a/app/config.py b/app/config.py index 2a157fa..fc37f36 100644 --- a/app/config.py +++ b/app/config.py @@ -6,6 +6,7 @@ def load_config(path): result = { 'SQLALCHEMY_DATABASE_URI': contents['database']['postgres_url'], 'SECRET_KEY': contents['server']['secret_key'], + 'DEBUG': contents['server']['debug'], 'SECURITY_REGISTERABLE': True, 'SECURITY_PASSWORD_SALT': contents['server']['secret_key'] } diff --git a/app/database.py b/app/database.py index 532f971..c66f603 100644 --- a/app/database.py +++ b/app/database.py @@ -5,15 +5,24 @@ from . import db from . import login from flask_login import UserMixin +from flask import redirect, url_for, flash + class User(db.Model, UserMixin): id = Column(String, primary_key=True) email = Column(String, unique=True, nullable=False) password = Column(String, nullable=False) pref_name = Column(String, nullable=False) last_login = Column(DateTime, nullable=False) - fs_uniquifier = Column(String, unique=True, nullable=False) active = Column(Boolean, nullable=False) + is_admin = Column(Boolean, nullable=False, default=False) roles = relationship('Role', secondary='roles_users',backref=backref('users', lazy='dynamic')) + peers = relationship('Peer', backref='owner', lazy=True) + networks = relationship('Network', backref="manager", lazy=True) + + def __repr__(self): + return f"<User {email}>" + def __str__(self): + return self.pref_name class Role(db.Model): id = Column(String, primary_key=True) @@ -28,15 +37,27 @@ class RolesUsers(db.Model): @login.user_loader def load_user(user_id): - return User.query.filer_by(id=user_id) + return User.query.filter_by(id=user_id).first() +@login.unauthorized_handler +def unauth(): + flash("Please log in first") + return redirect(url_for("auth.login")) class Peer(db.Model): id = Column(String, primary_key=True) addr = Column(CIDR, nullable=False) public_key = Column(String, nullable=False) + description = Column(String, nullable=False) + owner_id = Column(String, ForeignKey('user.id'), nullable=False) class Network(db.Model): id = Column(String, primary_key=True) - subnet = Column(CIDR, nullable=False) + subnet = Column(CIDR, nullable=False, unique=True) description = Column(String, nullable=True) + manager_id = Column(String, ForeignKey('user.id'), nullable=False) + + def __repr__(self): + return f"{self.description} ({self.subnet})" + def __str__(self): + return f"{self.description} ({self.subnet})" diff --git a/app/manage/__init__.py b/app/manage/__init__.py new file mode 100644 index 0000000..c69376f --- /dev/null +++ b/app/manage/__init__.py @@ -0,0 +1,50 @@ +from flask import Blueprint, render_template, request, flash, redirect, url_for +from flask_login import login_required, current_user +import ulid + +from app import db +from app.database import Network + +from .forms import NewNetworkForm + +bp = Blueprint('manage', __name__, url_prefix="/manage") + +@bp.route("/networks", methods=["GET", "POST"]) +@login_required +def list_networks(): + nets = current_user.networks + + form = NewNetworkForm(request.form) + + if request.method == "POST" and form.validate_on_submit(): + subnet = request.form.get('subnet') + description = request.form.get('description') + + n = Network( + id=str(ulid.ulid()), + subnet=subnet, + description=description, + manager_id=str(current_user.id) + ) + db.session.add(n) + db.session.commit() + + flash("Network added") + + return render_template("network_list.html", nets=nets, form=form) + +@bp.route("/networks/<string:id>/delete") +@login_required +def del_net(id): + n = Network.query.filter_by(id=id).first() + + if n.manager_id != current_user.id: + flash("You aren't a manager of this network.") + return redirect(url_for("manage.list_networks")) + + db.session.delete(n) + db.session.commit() + + flash("Network deleted") + + return redirect(url_for("manage.list_networks")) diff --git a/app/manage/forms.py b/app/manage/forms.py new file mode 100644 index 0000000..0849367 --- /dev/null +++ b/app/manage/forms.py @@ -0,0 +1,8 @@ +from flask_wtf import FlaskForm +from wtforms.fields.simple import PasswordField, StringField, SubmitField +from wtforms.validators import DataRequired, Regexp + +class NewNetworkForm(FlaskForm): + subnet = StringField("Subnet CIDR", validators=[DataRequired(), Regexp(r"^([0-9]{1,3}\.){3}[0-9]{1,3}($|/(8|16|24|32))$")]) + description = StringField("Description", validators=[DataRequired()]) + submit = SubmitField("Create") diff --git a/app/meta/__init__.py b/app/meta/__init__.py index 4fbe23c..8b6f6fa 100644 --- a/app/meta/__init__.py +++ b/app/meta/__init__.py @@ -6,5 +6,4 @@ bp = Blueprint("meta", __name__) @bp.route("/") def home(): - flash("Test") return render_template("index.html") diff --git a/app/static/gen/style.css b/app/static/gen/style.css index 9bca965..5159ee1 100644 --- a/app/static/gen/style.css +++ b/app/static/gen/style.css @@ -1,17 +1,14 @@ body { background: #282828; color: #ebdbb2; - font-family: monospace; -} + font-family: monospace; } a a:active, a:visited { - color: #458588; -} + color: #458588; } .container { margin: auto; - width: 60%; -} + width: 60%; } button, input[type=submit] { @@ -19,13 +16,15 @@ input[type=submit] { background-color: #458588; border-color: #458588; border: none; - margin: 0.5rem; -} + margin: 0.5rem; } button.accent { background-color: #d79921; - border-color: #d79921; -} + border-color: #d79921; } + +h1, h2, h3, h4, h5, h6 { + border-bottom: 1px solid; + width: 50%; } .navbar { list-style-type: none; @@ -34,44 +33,39 @@ button.accent { border-bottom: 1px solid; margin-bottom: 2rem; padding-bottom: 0.4rem; - text-align: center; -} + text-align: center; } .navbar-item { display: inline; - margin-right: 1rem; -} + margin-right: 1rem; } .flashes { list-style-type: none; display: flex; -} + justify-content: center; } .message { - width: 30%; + width: 80%; justify-content: center; border: 1px solid #ebdbb2; background-color: #d79921; - color: black; -} + padding: 0.2rem; + font-size: large; + color: black; } form { - width: 40%; -} + width: 40%; } label, input { margin-bottom: 0.5rem; margin-top: 0.5rem; - display: inline-block; -} + display: inline-block; } label { width: 40%; - text-align: left; -} + text-align: left; } label + input { width: 40%; - margin: 0 30% 0 4%; -} + margin: 0 30% 0 4%; } diff --git a/app/static/scss/style.scss b/app/static/scss/style.scss index cc23959..7c8760c 100644 --- a/app/static/scss/style.scss +++ b/app/static/scss/style.scss @@ -36,6 +36,11 @@ button.accent { border-color: $color-accent-bg; } +h1,h2,h3,h4,h5,h6 { + border-bottom: 1px solid; + width:50%; +} + // Navbar .navbar { @@ -56,13 +61,16 @@ button.accent { .flashes { list-style-type: none; display: flex; + justify-content: center; } .message { - width: 30%; + width: 80%; justify-content: center; border: 1px solid $color-fg; background-color: $color-accent-bg; + padding: 0.2rem; + font-size: large; color: black; } diff --git a/app/templates/base.html b/app/templates/base.html index d905156..735d430 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -14,6 +14,17 @@ <div class="navbar"> <ul> <li class="navbar-item"><a href="/">Home</a></li> + {% if not current_user.is_authenticated %} + <li class="navbar-item"><a + href="{{url_for('auth.login')}}">Login</a></li> + <li class="navbar-item"><a + href="{{url_for('auth.register')}}">Register</a></li> + {% else %} + <li class="navbar-item"><a + href="{{url_for('auth.profile')}}">Profile</a></li> + <li class="navbar-item"><a + href="{{url_for('auth.logout')}}">Logout</a></li> + {% endif %} </ul> </div> {% with messages = get_flashed_messages() %} diff --git a/app/templates/index.html b/app/templates/index.html index 3c69e80..efaf29e 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -1,5 +1,13 @@ {% extends "base.html" %} {% block content %} +{% if current_user.is_authenticated %} +<h1>Hi, {{current_user.pref_name}}!</h1> +{% else %} +<h1>Hello!</h1> +<p>Much of this tool requires authentication. Head on over <a + href="{{url_for('auth.login')}}">here</a> +to login, or <a href="{{url_for('auth.register')}}">here</a> to register. +{% endif %} {% endblock %} diff --git a/app/templates/network_list.html b/app/templates/network_list.html new file mode 100644 index 0000000..f56c6dc --- /dev/null +++ b/app/templates/network_list.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} + +{% block content %} +<h1>Managed Network Subnets</h1> + +<table> + <thead> + <tr> + <th scope="col">ID</th> + <th scope="col">Subnet</th> + <th scope="col">Description</th> + <th scope="col">Manager</th> + <th scope="col">Delete</th> + </tr> + </thead> + <tbody> + {% for n in nets %} + <tr> + <td>{{n.id}}</td> + <td>{{n.subnet}}</td> + <td>{{n.description}}</td> + <td>{{n.manager_id}}</td> + <td><a href="{{url_for('manage.del_net', id=n.id)}}">Delete</a></td> + </tr> + {% endfor %} + </tbody> +</table> + +<p>Want to add another one?</p> +<form method="POST"> + {{form.csrf_token}} + <div> + {{form.description.label}} {{form.description}} + </div> + <div> + {{form.subnet.label}} {{form.subnet}} + </div> + <div> + {{form.submit}} + </div> + </form> +{% endblock %} diff --git a/app/templates/profile.html b/app/templates/profile.html new file mode 100644 index 0000000..6d4d60a --- /dev/null +++ b/app/templates/profile.html @@ -0,0 +1,46 @@ +{% extends 'base.html' %} + +{% block content %} +<h1>{{current_user.pref_name}} - {{current_user.email}} ({{current_user.id}})</h1> + +{% if debug %} +<table> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + </tr> + </thead> + <tbody> + {% for attr, value in current_user.__dict__.items() %} + <tr> + <td>{{attr}}</td> + <td>{{value}}</td> + </tr> + {% endfor %} + </tbody> +</table> +{% endif %} + +<h3>Owned Peers</h3> +<table> + <thead> + <tr> + <th scope="col">ID</th> + <th scope="col">Description</th> + <th scope="col">Address</th> + <th scope="col">Public Key</th> + </tr> + </thead> + <tbody> + {% for p in peers %} + <tr> + <td>{{p.id}}</td> + <td>{{p.description}}</td> + <td>{{p.addr}}</td> + <td>{{p.public_key}}</td> + </tr> + {% endfor %} + </tbody> +</table> +{% endblock %} diff --git a/app/templates/register.html b/app/templates/register.html index 56d9855..6e19559 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -1,5 +1,22 @@ {% extends 'base.html' %} {% block content %} +<form method="POST"> + {{ form.csrf_token }} + <div> + {{form.username.label}} {{form.username}} + </div> + <div> + {{form.pref_name.label}} {{form.pref_name}} + </div> + <div> + <span> + {{form.password.label}} {{form.password}} + {{form.password_confirm.label}} {{form.password_confirm}} + </span> + </div> + + {{form.submit}} +</form> {% endblock %} |