Skip to content

Commit

Permalink
adding basic auth, polish ui
Browse files Browse the repository at this point in the history
  • Loading branch information
cmeury committed Nov 7, 2021
1 parent 4b9dcd4 commit 9190b64
Show file tree
Hide file tree
Showing 12 changed files with 184 additions and 50 deletions.
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
Crude web app to enter transactions into 'Budget with Buckets' files. Hacked together to work with
a buckets file synced via Synology Drive and a container running on the same Synology device.

This is heavy work in progress, needs a lot of UI polish and added security.
This is work in progress, needs a lot of polish.

Bucket file can be configured with an environment variable: `DB_FILE=/path/to/file.buckets`

## Build
## Develop

Install node packages:

npm install

Generate CSS file:

Expand All @@ -19,6 +23,14 @@ Watch for changes:

npm start

Create the virtual environment and start the flask app:

python3 -m venv venv
pip3 install -r requirements.txt
python3 -m flask run --host=0.0.0.0

## Build

To build the container:

npm run docker-build
Expand All @@ -36,7 +48,11 @@ To push the container to Docker Hub:

## Synology Setup

Many screenshots taken, will be added in the future here with more instructions.
Many screenshots taken, will be added in the future here with more instructions. Needs environment variables to run:

* `HTTP_BASIC_AUTH_USERNAME`: username for basic auth
* 'HTTP_BASIC_AUTH_PASSWORD': password for basic auth
* `DB_FILE`: absolute path to buckets data file

### New Image

Expand Down
9 changes: 6 additions & 3 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,14 @@ def create_app():
SECRET_KEY='dev'
)

app.config['DB_FILE'] = os.environ.get("DB_FILE", default='/app/db.buckets')
app.logger.warn("Connecting to SQLite Database File: %s", app.config['DB_FILE'])
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + app.config['DB_FILE']
app.config['HTTP_BASIC_AUTH_USERNAME'] = os.environ.get("HTTP_BASIC_AUTH_USERNAME", default='buckets')
app.config['HTTP_BASIC_AUTH_PASSWORD'] = os.environ.get("HTTP_BASIC_AUTH_PASSWORD", default='dev')

db_file = os.environ.get("DB_FILE", default='/app/db.buckets')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + db_file
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

app.logger.warn("Connecting to SQLite Database File: %s", db_file)
db.init_app(app)

from . import views
Expand Down
Binary file added app/static/bucket.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 36 additions & 0 deletions app/templates/about.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{% extends "layout.html" %}

{% block title %}Transactions{% endblock %}

{% block head %}
{{ super() }}
<style type="text/css">
.important {
color: #336699;
}
</style>
{% endblock %}

{% block content %}
<section class="section">
<div class="container">
<h1 class="title has-text-centered">
About bread-bucket
</h1>
<div class="content has-text-centered">
<p>
Crude web app to enter transactions into <strong><a href="https://www.budgetwithbuckets.com/">Budget with
Buckets</a></strong> files. See <a href="https://github.com/cmeury/bread-bucket">GitHub repository</a> for
more details.
</p>
<p>The layout is done using <strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy Thomas</a>, licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. This web service
is licensed <a href="https://choosealicense.com/licenses/gpl-3.0/">GPL v3</a>. The bucket image is done by
Vertigophase from the Noun Project. The service is using <a href="http://fontawesome.io">Font Awesome</a>
icons by Dave Gandy.
</p>
</div>

</div>
</section>
{% endblock %}
28 changes: 28 additions & 0 deletions app/templates/access_denied.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{% extends "layout.html" %}

{% block title %}Transactions{% endblock %}

{% block head %}
{{ super() }}
<style type="text/css">
.important {
color: #336699;
}
</style>
{% endblock %}

{% block content %}
<section class="section">
<div class="container">
<h1 class="title has-text-centered">
{{ status }}: Access Denied
</h1>
<div class="content has-text-centered">
<p>
Incorrect credentials provided or failure in backend.
</p>
</div>

</div>
</section>
{% endblock %}
9 changes: 0 additions & 9 deletions app/templates/layout.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,5 @@
<body>
{% include 'navbar.html' %}
{% block content %}{% endblock %}
<footer class="footer">
<div class="content has-text-centered">
<p>
<strong>Bulma</strong> by <a href="https://jgthms.com">Jeremy Thomas</a>. The source code is licensed
<a href="http://opensource.org/licenses/mit-license.php">MIT</a>. The website content
is licensed <a href="http://creativecommons.org/licenses/by-nc-sa/4.0/">CC BY NC SA 4.0</a>.
</p>
</div>
</footer>
</body>
</html>
13 changes: 7 additions & 6 deletions app/templates/navbar.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
<a class="navbar-item" href="/about">
<img src="{{ url_for('static', filename='breadbucket.png') }}" height="40">
</a>

Expand All @@ -13,17 +13,18 @@

<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
Home

<a class="navbar-item" href="/transaction">
New Transaction
</a>

<a class="navbar-item" href="/transactions">
Transactions
</a>

<a class="navbar-item" href="/transaction">
New Transaction
<a class="navbar-item" href="/about">
About
</a>


</div>
</nav>
15 changes: 9 additions & 6 deletions app/templates/transaction-success.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
<section class="section">
<div class="content">
<h1 class="title">Success!</h1>
<div class="success">
Successfully added the transaction!
</div>
<div>
<a href="/transactions?success">View All Transactions</a>
</div>
<span class="icon-text has-text-success">
<span class="icon">
<i class="fas fa-check-square"></i>
</span>
<span>Successfully added the transaction!</span>
</span>
<div>
<a href="/transactions?success">View All Transactions</a>
</div>
</div>
</section>
{% endblock %}
46 changes: 29 additions & 17 deletions app/templates/transaction.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@ <h1 class="title">Enter New Transaction</h1>
</div>
</div>
</div>

<div class="field is-horizontal">
<div class="field-label is-normal">
{{ form.amount.label(class="label") }}
</div>
<div class="field-body">
<div class="field is-narrow">
<p class="control has-icons-left">
{{ form.amount(size=20, class="input") }}
{{ form.amount(size=20, class="input", placeholder="0.00") }}
<span class="icon is-small is-left">
<i class="fas fa-solid fa-coins"></i>
</span>
Expand All @@ -43,16 +44,22 @@ <h1 class="title">Enter New Transaction</h1>

<div class="field">
{{ form.memo.label(class="label") }}
<div class="control">
<p class="control has-icons-left">
{{ form.memo(size=20, class="input") }}
</div>
<span class="icon is-small is-left">
<i class="fas fa-solid fa-pencil-alt"></i>
</span>
</p>
</div>

<div class="field">
{{ form.notes.label(class="label") }}
<div class="control">
<p class="control has-icons-left">
{{ form.notes(size=20, class="input") }}
</div>
<span class="icon is-small is-left">
<i class="fas fa-solid fa-file-alt"></i>
</span>
</p>
</div>

<div class="field is-grouped">
Expand All @@ -66,20 +73,25 @@ <h1 class="title">Enter New Transaction</h1>

</form>
</div>
<div class="column is-half">
<div class="container">
Cupcake ipsum dolor sit amet candy shortbread. Halvah jelly chocolate bar dragée cookie liquorice apple pie
carrot cake muffin. Candy cookie toffee gingerbread marshmallow. Donut muffin icing ice cream fruitcake
candy canes carrot cake wafer halvah. Liquorice pie topping jelly-o sesame snaps cake chocolate cake.
Chocolate jujubes topping sesame snaps apple pie sweet cupcake sweet. Bear claw muffin gummi bears sweet
roll marzipan lollipop pie bonbon. Bear claw wafer topping pastry jelly beans chocolate. Tart lemon
drops wafer toffee chocolate bar bonbon cupcake carrot cake gummi bears. Biscuit candy canes cookie
dragée wafer sugar plum chocolate bar. Danish dragée dragée jelly beans sweet liquorice. Pastry topping
chocolate bar jelly chocolate bar fruitcake danish. Lollipop chocolate bar tart jelly-o caramels
biscuit sweet roll. Sweet roll icing tiramisu bonbon sugar plum candy chupa chups. Shortbread fruitcake
soufflé fruitcake brownie caramels bonbon liquorice. Ice cream soufflé bonbon candy canes sugar plum.

<div class="column is-half is-vcentered is-expanded">

<div class="columns is-expanded">
<div class="column is-half">
This will form will insert a new transaction into the buckets database file, using the current date. The
transaction will be uncategorized.
</div>

<div class="column is-half is-centered is-vcentered">
<figure class="image is-128x128">
<img src="{{ url_for('static', filename='bucket.png') }}">
</figure>
</div>
</div>

</div>

</div>

</section>
{% endblock %}
11 changes: 9 additions & 2 deletions app/templates/transactions.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,16 @@ <h1 class="title">
Transactions List
</h1>
<p class="subtitle">
Displaying {{ limit }} transactions for {% if account %} account {{ account }}{% else %}all accounts{% endif %}.
Displaying last {{ limit }} transactions for {% if account %} account {{ account }}{% else %}all accounts{% endif %}.
</p>

{% if not account %}
<div class="content is-small">
View transactions for account
{% for acc in accounts %}
<a href="/transactions/{{ acc.id }}">{{ acc.name }}</a> {{ "|" if not loop.last }}
{% endfor %}
</div>
{% endif %}
<table class="table is-bordered is-striped is-narrow is-hoverable">
{# <caption>Transactions</caption>#}
<thead>
Expand Down
40 changes: 37 additions & 3 deletions app/views.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from flask import request, render_template, redirect, Blueprint, current_app
from flask_httpauth import HTTPBasicAuth
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from sqlalchemy import exc
from werkzeug.security import generate_password_hash, check_password_hash

from app import db
from app.forms import TransactionEntryForm
Expand All @@ -8,6 +12,17 @@
DEFAULT_LIMIT = 10

views = Blueprint('views', __name__)
limiter = Limiter(current_app, key_func=get_remote_address)
auth = HTTPBasicAuth()


@auth.verify_password
def verify_password(username, password):
cfg_username = current_app.config['HTTP_BASIC_AUTH_USERNAME']
cfg_password = current_app.config['HTTP_BASIC_AUTH_PASSWORD']
cfg_password_hash = generate_password_hash(cfg_password)
if username == cfg_username and check_password_hash(cfg_password_hash, password):
return username


@views.app_template_filter()
Expand All @@ -22,7 +37,19 @@ def handle_db_exceptions(error):
db.session.rollback()


@auth.error_handler
def auth_error(status):
return render_template("access_denied.html", status=status)


@views.route('/about')
def about():
return render_template('about.html')


@views.route('/transactions/<account_id>')
@auth.login_required
@limiter.limit("10/minute")
def transactions_for(account_id=None):
limit = request.args.get('limit') or DEFAULT_LIMIT
tx = Transaction.query.filter_by(account_id=account_id).order_by(Transaction.posted.desc()).limit(limit)
Expand All @@ -31,16 +58,21 @@ def transactions_for(account_id=None):


@views.route('/transactions')
@auth.login_required
@limiter.limit("10/minute")
def transactions():
limit = request.args.get('limit') or DEFAULT_LIMIT
tx = Transaction.query.order_by(Transaction.posted.desc()).limit(limit)
return render_template('transactions.html', tx=tx, limit=limit)


# get non-closed accounts
accounts = Account.query.filter_by(closed=0).order_by('name')

tx = Transaction.query.order_by(Transaction.posted.desc()).limit(limit)
return render_template('transactions.html', tx=tx, limit=limit, accounts=accounts)


@views.route('/transaction', methods=['GET', 'POST'])
@auth.login_required
@limiter.limit("10/minute")
def new_transaction():

# instantiate new entry form
Expand All @@ -66,5 +98,7 @@ def new_transaction():


@views.route('/transaction/success')
@auth.login_required
@limiter.limit("10/minute")
def transaction_success():
return render_template('transaction-success.html')
5 changes: 4 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@ Flask-WTF~=0.15.1
WTForms~=2.3.3
Flask-SQLAlchemy~=2.5.1
SQLAlchemy~=1.4.26
pytz~=2021.3
pytz~=2021.3
Flask-HTTPAuth~=4.5.0
Werkzeug~=2.0.2
Flask-Limiter~=1.4

0 comments on commit 9190b64

Please sign in to comment.