aboutsummaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/__init__.py2
-rw-r--r--app/auth/__init__.py41
-rw-r--r--app/auth/forms.py1
-rw-r--r--app/config.py1
-rw-r--r--app/database.py27
-rw-r--r--app/manage/__init__.py50
-rw-r--r--app/manage/forms.py8
-rw-r--r--app/meta/__init__.py1
-rw-r--r--app/static/gen/style.css46
-rw-r--r--app/static/scss/style.scss10
-rw-r--r--app/templates/base.html11
-rw-r--r--app/templates/index.html8
-rw-r--r--app/templates/network_list.html42
-rw-r--r--app/templates/profile.html46
-rw-r--r--app/templates/register.html17
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 %}