diff --git a/CHANGELOG.md b/CHANGELOG.md index ff728bc..24a2232 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ + Codebase refactor (with breaking changes!) + Move ALL config to .env (config.py is NO MORE supported) + Config SITE_NAME replaced with APP_NAME ++ Add CSRF token and flask_WTF ## 0.9.0 diff --git a/README.md b/README.md index b96fffc..3ee8c96 100644 --- a/README.md +++ b/README.md @@ -16,10 +16,19 @@ This is the server. For the client, see [coriplusapp](https://github.com/sakurag * Add info to your profile * In-site notifications * Public API -* SQLite-based app +* SQLite (or PostgreSQL)-based app ## Requirements -* **Python 3** only. We don't want to support Python 2. -* **Flask** web framework (also required extension **Flask-Login**). +* **Python 3.10+** with **pip**. +* **Flask** web framework. * **Peewee** ORM. +* A \*nix-based OS. + +## Installation + +* Install dependencies: `pip install .` +* Set the `DATABASE_URL` (must be SQLite or PostgreSQL) +* Run the migrations: `sh ./genmig.sh @` +* i forgor + diff --git a/genmig.sh b/genmig.sh new file mode 100755 index 0000000..a29c9fb --- /dev/null +++ b/genmig.sh @@ -0,0 +1,9 @@ +#!/usr/bin/bash +# GENERATE MIGRATIONS + +source venv/bin/activate && \ +source .env && \ +case "$1" in + ("+") pw_migrate create --auto --auto-source=coriplus.models --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;; + ("@") pw_migrate migrate --directory=src/migrations --database="$DATABASE_URL" "${@:2}" ;; +esac \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index f4d5b53..76e1140 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,10 @@ dependencies = [ "Python-Dotenv>=1.0.0", "Flask", "Flask-Login", - "Peewee" + "Peewee", + "Flask-WTF", + "peewee-migrate", + "PsycoPG2" ] requires-python = ">=3.10" classifiers = [ diff --git a/src/coriplus/__init__.py b/src/coriplus/__init__.py index 4f01715..98b4d85 100644 --- a/src/coriplus/__init__.py +++ b/src/coriplus/__init__.py @@ -20,6 +20,7 @@ from flask import ( send_from_directory, __version__ as flask_version) import os, sys from flask_login import LoginManager +from flask_wtf import CSRFProtect import dotenv import logging @@ -44,6 +45,8 @@ app.secret_key = os.environ['SECRET_KEY'] login_manager = LoginManager(app) +CSRFProtect(app) + from .models import * from .utils import * @@ -64,7 +67,10 @@ def before_request(): @app.after_request def after_request(response): - g.db.close() + try: + g.db.close() + except Exception: + logger.error('database closed twice') return response @app.context_processor @@ -79,6 +85,10 @@ def _inject_variables(): def _inject_user(userid): return User[userid] +@app.errorhandler(403) +def error_403(body): + return render_template('403.html'), 403 + @app.errorhandler(404) def error_404(body): return render_template('404.html'), 404 diff --git a/src/coriplus/admin.py b/src/coriplus/admin.py index b8fb4b9..a78cf1b 100644 --- a/src/coriplus/admin.py +++ b/src/coriplus/admin.py @@ -4,7 +4,8 @@ Management of reports and the entire site. New in 0.8. ''' -from flask import Blueprint, redirect, render_template, request, url_for +from flask import Blueprint, abort, redirect, render_template, request, url_for +from flask_login import current_user from .models import User, Message, Report, report_reasons, REPORT_STATUS_ACCEPTED, \ REPORT_MEDIA_USER, REPORT_MEDIA_MESSAGE from .utils import pwdhash, object_list @@ -12,21 +13,18 @@ from functools import wraps bp = Blueprint('admin', __name__, url_prefix='/admin') -def check_auth(username, password): +def _check_auth(username, password) -> bool: try: - return User.get((User.username == username) & (User.password == pwdhash(password)) - ).is_admin + return User.select().where((User.username == username) & (User.password == pwdhash(password)) & (User.is_admin) + ).exists() except User.DoesNotExist: return False def admin_required(f): @wraps(f) def wrapped_view(**kwargs): - auth = request.authorization - if not (auth and check_auth(auth.username, auth.password)): - return ('Unauthorized', 401, { - 'WWW-Authenticate': 'Basic realm="Login Required"' - }) + if not _check_auth(current_user.username, current_user.password): + abort(403) return f(**kwargs) return wrapped_view diff --git a/src/coriplus/models.py b/src/coriplus/models.py index 4cdb2b5..5a2ae50 100644 --- a/src/coriplus/models.py +++ b/src/coriplus/models.py @@ -13,11 +13,14 @@ The tables are: from flask import request from peewee import * +from playhouse.db_url import connect import os + +from . import BASEDIR # here should go `from .utils import get_current_user`, but it will cause # import errors. It's instead imported at function level. -database = SqliteDatabase('coriplus.sqlite') +database = connect(os.environ['DATABASE_URL']) class BaseModel(Model): class Meta: @@ -189,7 +192,7 @@ class Relationship(BaseModel): ) -UPLOAD_DIRECTORY = os.path.join(os.path.split(os.path.dirname(__file__))[0], 'uploads') +UPLOAD_DIRECTORY = os.path.join(BASEDIR, 'uploads') class Upload(BaseModel): # the extension of the media diff --git a/src/coriplus/static/style.css b/src/coriplus/static/style.css index 87152a8..c238285 100644 --- a/src/coriplus/static/style.css +++ b/src/coriplus/static/style.css @@ -1,19 +1,41 @@ -body,button,input,select,textarea{font-family:Roboto,'Segoe UI',Arial,Helvetica,sans-serif} +:root { + --accent: #f0372e; + --link: #3399ff; +} +body, button, input, select, textarea { + font-family: Inter, Roboto, sans-serif; + line-height: 1.6; + background-color: #eeeeee; +} +#site-name, h1, h2, h3, h4, h5, h6 { + font-family: 'Funnel Sans', Roboto, sans-serif; + line-height: 1.2; +} body{margin:0} +main{margin:auto; max-width: 960px;} a{text-decoration:none} a:hover{text-decoration:underline} +.mobile-collapse {font-size: smaller;} @media (max-width:640px){ .mobile-collapse{display:none} } -.header{padding:12px;color:white;background-color:#ff3018;box-shadow:0 0 3px 3px #ccc} +.header{ + padding:12px;color:white; background-color:var(--accent); box-shadow:0 0 3px 3px #ccc; display: flex; position: relative; +} +.centered{ + text-align: center; +} .content{padding:12px} -.header a{color:white} +.header a{color:white; } .header a svg{fill:white} -.content a{color:#3399ff} -.content a svg{fill:#3399ff} -.content a.plus{color:#ff3018} -.metanav{float:right} -.metanav-divider{width:1px;background:white;display:inline-block} +.content a{color:var(--link)} +.content a svg{fill:var(--link)} +.content a.plus{color:var(--accent)} +.leftnav, .rightnav{list-style: none; display: flex; padding: 0; margin: 0;position: absolute;} +.leftnav {left: 0} +.rightnav {right: 0} +.card {background-color: #fafafa; border-radius: 12px; padding: 12px; margin-bottom: 12px; border-bottom: 2px solid #999999;} +#site-name {text-align: center;flex: 1} .header h1{margin:0;display:inline-block} .flash{background-color:#ff9;border:yellow 1px solid} .infobox{padding:12px;border:#ccc 1px solid} @@ -23,15 +45,15 @@ a:hover{text-decoration:underline} .weak{opacity:.5} .field_desc{display:block} ul.timeline{padding:0;margin:auto;max-width:960px} -ul.timeline > li{list-style:none;border-bottom:#808080 1px solid} +ul.timeline > li{list-style:none;} .message-visual img{max-width:100%;margin:auto} .message-options-showhide::before{content:'\2026'} .message-options{display:none} .create_text{width:100%;height:8em} .biography_text{height:4em} .before-toggle:not(:checked) + input{display:none} -.follow_button,input[type="submit"]{background-color:#ff3018;color:white;border-radius:3px;border:1px solid #ff3018} -.follow_button.following{background-color:transparent;color:#ff3018;border-color:#ff3018} +.follow_button,input[type="submit"]{background-color:var(--accent);color:white;border-radius:3px;border:1px solid var(--accent)} +.follow_button.following{background-color:transparent;color:var(--accent);border-color:var(--accent)} .copyright{font-size:smaller;text-align:center;color:#808080} .copyright a:link,.copyright a:visited{color:#31559e} .copyright ul{list-style:none;padding:0} diff --git a/src/coriplus/templates/403.html b/src/coriplus/templates/403.html new file mode 100644 index 0000000..5cdbf3b --- /dev/null +++ b/src/coriplus/templates/403.html @@ -0,0 +1,9 @@ +{% extends "base.html" %} + +{% block body %} +
+

Forbidden

+ +

Back to homepage.

+
+{% endblock %} \ No newline at end of file diff --git a/src/coriplus/templates/404.html b/src/coriplus/templates/404.html index d434c9a..4acfb0b 100644 --- a/src/coriplus/templates/404.html +++ b/src/coriplus/templates/404.html @@ -1,7 +1,9 @@ {% extends "base.html" %} {% block body %} -

Not Found

+
+

Not Found

-

Back to homepage.

+

Back to homepage.

+
{% endblock %} \ No newline at end of file diff --git a/src/coriplus/templates/about.html b/src/coriplus/templates/about.html index e5691a8..905eb8d 100644 --- a/src/coriplus/templates/about.html +++ b/src/coriplus/templates/about.html @@ -1,34 +1,39 @@ {% extends "base.html" %} {% block body %} -

About {{ site_name }}

+
+

About {{ site_name }}

-

{{ site_name }} {{ version }} – Python {{ python_version }} – - Flask {{ flask_version }}

-

Copyright © 2019 Sakuragasaki46.

+ +

Copyright © 2019, 2025 Sakuragasaki46.

-

License

-

Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, - and to permit persons to whom the Software is furnished to do so, - subject to the following conditions:

+

License

+

Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions:

-

The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software.

+

The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software.

-

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

-

Source code for this site: - https://github.com/sakuragasaki46/coriplus/ +

Source code for this site: + https://github.com/sakuragasaki46/coriplus/ +

{% endblock %} diff --git a/src/coriplus/templates/admin_base.html b/src/coriplus/templates/admin_base.html index e59e813..c20c146 100644 --- a/src/coriplus/templates/admin_base.html +++ b/src/coriplus/templates/admin_base.html @@ -7,16 +7,19 @@
-

{{ site_name }} Admin

-
+

{{ site_name }}: Admin

+
+ {% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} +
{% block body %}{% endblock %} +
{% for message in get_flashed_messages() %}
{{ message }}
{% endfor %} +
{% block body %}{% endblock %} +