fediflax 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. fediflax/__init__.py +7 -0
  2. fediflax/admin.py +127 -0
  3. fediflax/auth.py +292 -0
  4. fediflax/core/__init__.py +1 -0
  5. fediflax/core/instance.py +104 -0
  6. fediflax/core/web/__init__.py +4 -0
  7. fediflax/core/web/templates/web/create_page.html +51 -0
  8. fediflax/core/web/templates/web/delete_page.html +28 -0
  9. fediflax/core/web/utils.py +16 -0
  10. fediflax/core/web/web_manager.py +251 -0
  11. fediflax/database.py +8 -0
  12. fediflax/extensions/__init__.py +1 -0
  13. fediflax/extensions/custom_pages/__init__.py +1 -0
  14. fediflax/extensions/custom_pages/custom_page.py +37 -0
  15. fediflax/extensions/custom_pages/extension.py +58 -0
  16. fediflax/extensions/custom_pages/templates/custom/dummy.html +10 -0
  17. fediflax/extensions/custom_pages/templates/custom_pages/edit.html +26 -0
  18. fediflax/extensions/custom_pages/templates/samples/privacy_policy.html +111 -0
  19. fediflax/extensions/custom_pages/templates/samples/privacy_policy.params.html +13 -0
  20. fediflax/extensions/documents/__init__.py +1 -0
  21. fediflax/extensions/documents/document_page.py +77 -0
  22. fediflax/extensions/documents/extension.py +109 -0
  23. fediflax/extensions/documents/templates/documents/show.html +6 -0
  24. fediflax/extensions/documents/templates/documents/update.html +31 -0
  25. fediflax/federation.py +520 -0
  26. fediflax/legacy/templates/admin/setup.html +77 -0
  27. fediflax/legacy/templates/auth/authorization.html +14 -0
  28. fediflax/legacy/templates/auth/login.html +15 -0
  29. fediflax/legacy/templates/auth/register.html +26 -0
  30. fediflax/legacy/templates/media/media_details.html +62 -0
  31. fediflax/legacy/templates/media/media_gallery.html +23 -0
  32. fediflax/legacy/templates/media/upload_media.html +28 -0
  33. fediflax/legacy/templates/settings/settings.html +37 -0
  34. fediflax/legacy/templates.py +6 -0
  35. fediflax/media.py +114 -0
  36. fediflax/migrations/1768149513_initial_database_schema.py +224 -0
  37. fediflax/migrations/script.py.mako +28 -0
  38. fediflax/models.py +487 -0
  39. fediflax/settings.py +74 -0
  40. fediflax/startup.py +53 -0
  41. fediflax/static/smiley.png +0 -0
  42. fediflax/templates/home.html +10 -0
  43. fediflax/themes/.gitignore +1 -0
  44. fediflax/themes/__init__.py +1 -0
  45. fediflax/themes/fiber/__init__.py +3 -0
  46. fediflax/themes/fiber/static/css/app.css +24 -0
  47. fediflax/themes/fiber/static/css/foundation.css +7753 -0
  48. fediflax/themes/fiber/static/css/foundation.min.css +1 -0
  49. fediflax/themes/fiber/static/favicon.ico +0 -0
  50. fediflax/themes/fiber/static/fontawesome/LICENSE.txt +165 -0
  51. fediflax/themes/fiber/static/fontawesome/css/all.css +10879 -0
  52. fediflax/themes/fiber/static/fontawesome/css/all.min.css +9 -0
  53. fediflax/themes/fiber/static/fontawesome/css/brands.css +2371 -0
  54. fediflax/themes/fiber/static/fontawesome/css/brands.min.css +6 -0
  55. fediflax/themes/fiber/static/fontawesome/css/fontawesome.css +8425 -0
  56. fediflax/themes/fiber/static/fontawesome/css/fontawesome.min.css +8 -0
  57. fediflax/themes/fiber/static/fontawesome/css/regular.css +31 -0
  58. fediflax/themes/fiber/static/fontawesome/css/regular.min.css +6 -0
  59. fediflax/themes/fiber/static/fontawesome/css/solid.css +31 -0
  60. fediflax/themes/fiber/static/fontawesome/css/solid.min.css +6 -0
  61. fediflax/themes/fiber/static/fontawesome/css/svg-with-js.css +557 -0
  62. fediflax/themes/fiber/static/fontawesome/css/svg-with-js.min.css +6 -0
  63. fediflax/themes/fiber/static/fontawesome/css/svg.css +183 -0
  64. fediflax/themes/fiber/static/fontawesome/css/svg.min.css +6 -0
  65. fediflax/themes/fiber/static/fontawesome/css/v4-font-face.css +27 -0
  66. fediflax/themes/fiber/static/fontawesome/css/v4-font-face.min.css +6 -0
  67. fediflax/themes/fiber/static/fontawesome/css/v4-shims.css +2818 -0
  68. fediflax/themes/fiber/static/fontawesome/css/v4-shims.min.css +6 -0
  69. fediflax/themes/fiber/static/fontawesome/css/v5-font-face.css +23 -0
  70. fediflax/themes/fiber/static/fontawesome/css/v5-font-face.min.css +6 -0
  71. fediflax/themes/fiber/static/fontawesome/js/all.js +8128 -0
  72. fediflax/themes/fiber/static/fontawesome/js/all.min.js +6 -0
  73. fediflax/themes/fiber/static/fontawesome/js/brands.js +1308 -0
  74. fediflax/themes/fiber/static/fontawesome/js/brands.min.js +6 -0
  75. fediflax/themes/fiber/static/fontawesome/js/conflict-detection.js +1519 -0
  76. fediflax/themes/fiber/static/fontawesome/js/conflict-detection.min.js +6 -0
  77. fediflax/themes/fiber/static/fontawesome/js/fontawesome.js +3728 -0
  78. fediflax/themes/fiber/static/fontawesome/js/fontawesome.min.js +6 -0
  79. fediflax/themes/fiber/static/fontawesome/js/regular.js +927 -0
  80. fediflax/themes/fiber/static/fontawesome/js/regular.min.js +6 -0
  81. fediflax/themes/fiber/static/fontawesome/js/solid.js +2180 -0
  82. fediflax/themes/fiber/static/fontawesome/js/solid.min.js +6 -0
  83. fediflax/themes/fiber/static/fontawesome/js/v4-shims.js +731 -0
  84. fediflax/themes/fiber/static/fontawesome/js/v4-shims.min.js +6 -0
  85. fediflax/themes/fiber/static/fontawesome/webfonts/fa-brands-400.woff2 +0 -0
  86. fediflax/themes/fiber/static/fontawesome/webfonts/fa-regular-400.woff2 +0 -0
  87. fediflax/themes/fiber/static/fontawesome/webfonts/fa-solid-900.woff2 +0 -0
  88. fediflax/themes/fiber/static/fontawesome/webfonts/fa-v4compatibility.woff2 +0 -0
  89. fediflax/themes/fiber/static/js/app.js +1 -0
  90. fediflax/themes/fiber/static/js/vendor/foundation.js +460 -0
  91. fediflax/themes/fiber/static/js/vendor/foundation.min.js +1 -0
  92. fediflax/themes/fiber/static/js/vendor/jquery.js +10716 -0
  93. fediflax/themes/fiber/static/js/vendor/what-input.js +517 -0
  94. fediflax/themes/fiber/static/logo.png +0 -0
  95. fediflax/themes/fiber/templates/base.html +136 -0
  96. fediflax/themes/fiber/theme.py +30 -0
  97. fediflax/types.py +44 -0
  98. fediflax/well_known.py +57 -0
  99. fediflax-1.4.0.dist-info/METADATA +63 -0
  100. fediflax-1.4.0.dist-info/RECORD +102 -0
  101. fediflax-1.4.0.dist-info/WHEEL +4 -0
  102. fediflax-1.4.0.dist-info/licenses/LICENSE +235 -0
fediflax/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ from flask_alembic import Alembic
2
+ from flask_talisman import Talisman
3
+
4
+ from fediflax.database import ModelBase
5
+
6
+ alembic = Alembic(metadatas=ModelBase.metadata)
7
+ talisman = Talisman()
fediflax/admin.py ADDED
@@ -0,0 +1,127 @@
1
+ import functools
2
+
3
+ import click
4
+ from flask import (
5
+ Blueprint,
6
+ current_app,
7
+ flash,
8
+ g,
9
+ redirect,
10
+ render_template,
11
+ request,
12
+ session,
13
+ url_for,
14
+ )
15
+ from sqlalchemy import select
16
+
17
+ from fediflax import alembic, auth
18
+ from fediflax.database import db
19
+ from fediflax.federation import create_actor
20
+ from fediflax.models import Domain, FediActor, User
21
+
22
+ bp = Blueprint("admin", __name__, url_prefix="/admin")
23
+
24
+
25
+ def add_domain(domainname, owned):
26
+ # create actor and add it to the database
27
+ domain = Domain(name=domainname, owned_by_server=owned)
28
+ db.session.add(domain)
29
+ db.session.commit()
30
+
31
+
32
+ @bp.cli.command("add_domain")
33
+ @click.argument("domainname")
34
+ @click.option("--owned/--no-owned", default=False, prompt=True)
35
+ def add_domain_cli(domainname, owned):
36
+ add_domain(domainname, owned)
37
+ print("ok")
38
+
39
+
40
+ @bp.get("/setup")
41
+ def setup():
42
+ current_database_revision = alembic.migration_context.get_current_revision()
43
+ print(current_app.config.get("ADMIN_USER"))
44
+ # is_database_current = alembic.needs_upgrade()
45
+ if current_app.config.get("ADMIN_USER"):
46
+ pass
47
+
48
+ else:
49
+ pass
50
+
51
+ return render_template(
52
+ "admin/setup.html", current_database_revision=current_database_revision
53
+ )
54
+
55
+
56
+ @bp.post("/upgrade_database")
57
+ def upgrade_database():
58
+ admin_user = current_app.config.get("ADMIN_USER")
59
+ if admin_user is None or admin_user == session.get("username"):
60
+ alembic.upgrade()
61
+ flash(f"Database updated", "success")
62
+ else:
63
+ flash(f"Please login to update database", "warning")
64
+
65
+ return redirect(url_for("admin.setup"))
66
+
67
+
68
+ @bp.post("/enable_admin")
69
+ def enable_admin():
70
+ admin_user = current_app.config.get("ADMIN_USER")
71
+ if admin_user is not None:
72
+ auth.enable_user(admin_user)
73
+ flash(f"Admin enabled.", "success")
74
+ else:
75
+ flash(f"No admin in config.", "warning")
76
+
77
+ return redirect(url_for("admin.setup"))
78
+
79
+
80
+ @bp.post("/add_domain")
81
+ def add_domain_post():
82
+ admin_user = current_app.config.get("ADMIN_USER")
83
+ if admin_user is not None and admin_user == session.get("username"):
84
+ add_domain(request.form.get("domain_name"), request.form.get("owned") == "on")
85
+ flash(f"Domain added.", "success")
86
+ else:
87
+ flash(f"Login as admin to add a domain", "warning")
88
+
89
+ return redirect(url_for("admin.setup"))
90
+
91
+
92
+ @bp.post("/add_actor")
93
+ def add_actor():
94
+ admin_user = current_app.config.get("ADMIN_USER")
95
+ if admin_user is not None and admin_user == session.get("username"):
96
+ subject = (
97
+ request.form.get("actor_prefix") + "@" + request.form.get("domain_name")
98
+ )
99
+ actor: FediActor = db.session.execute(
100
+ select(FediActor).filter_by(subject=subject)
101
+ ).scalar()
102
+ if actor is None:
103
+ actor = create_actor(
104
+ request.form.get("actor_prefix"), request.form.get("domain_name")
105
+ )
106
+
107
+ user: User = db.session.execute(
108
+ select(User).filter_by(username=request.form.get("user"))
109
+ ).scalar()
110
+ if user is not None:
111
+ if request.form.get("user_default") == "on":
112
+ user.default_actor_uri = actor.uri
113
+ user.enabled = request.form.get("enable_user") == "on"
114
+
115
+ domain: Domain = db.session.execute(
116
+ select(Domain).filter_by(name=request.form.get("domain_name"))
117
+ ).scalar()
118
+ if domain is not None and request.form.get("domain_default") == "on":
119
+ domain.main_actor_uri = actor.uri
120
+
121
+ db.session.commit()
122
+
123
+ flash(f"Actor added.", "success")
124
+ else:
125
+ flash(f"Login as admin to add a actor", "warning")
126
+
127
+ return redirect(url_for("admin.setup"))
fediflax/auth.py ADDED
@@ -0,0 +1,292 @@
1
+ import functools
2
+ import logging
3
+ import secrets
4
+ from datetime import datetime, timedelta
5
+
6
+ import click
7
+ from authlib.integrations.flask_oauth2 import (
8
+ AuthorizationServer,
9
+ ResourceProtector,
10
+ current_token,
11
+ )
12
+ from authlib.integrations.sqla_oauth2 import OAuth2ClientMixin
13
+ from authlib.oauth2 import OAuth2Request
14
+ from authlib.oauth2.rfc6749 import grants
15
+ from authlib.oauth2.rfc6750 import BearerTokenValidator
16
+ from flask import (
17
+ Blueprint,
18
+ current_app,
19
+ flash,
20
+ g,
21
+ redirect,
22
+ render_template,
23
+ request,
24
+ session,
25
+ url_for,
26
+ )
27
+ from sqlalchemy import func, select
28
+ from sqlalchemy.exc import IntegrityError
29
+ from werkzeug.security import check_password_hash, generate_password_hash
30
+
31
+ from fediflax.database import db
32
+ from fediflax.models import Client, FediActor, Grant, Token, User
33
+
34
+ bp = Blueprint("auth", __name__, url_prefix="/auth")
35
+ oauth_server = AuthorizationServer()
36
+ require_oauth = ResourceProtector()
37
+
38
+
39
+ class AuthorizationCodeGrant(grants.AuthorizationCodeGrant):
40
+ def save_authorization_code(self, code, request):
41
+ print("save authorization code")
42
+ expires = datetime.utcnow() + timedelta(seconds=100)
43
+ grant = Grant(
44
+ client_id=request.client.client_id,
45
+ code=code,
46
+ user_name=request.user.get_user_id(),
47
+ redirect_uri=request.redirect_uri,
48
+ _scopes=request.client._default_scopes,
49
+ )
50
+ db.session.add(grant)
51
+ db.session.commit()
52
+ return grant
53
+
54
+ def query_authorization_code(self, code, client):
55
+ print("query authorization code")
56
+ grant = db.session.execute(
57
+ select(Grant).filter_by(client_id=client.client_id, code=code)
58
+ ).scalar_one()
59
+ return grant
60
+
61
+ def delete_authorization_code(self, authorization_code):
62
+ db.session.delete(authorization_code)
63
+ db.session.commit()
64
+
65
+ def authenticate_user(self, authorization_code):
66
+ return authorization_code.user
67
+
68
+ def create_token_response(self):
69
+ (status_code, body, headers) = super().create_token_response()
70
+ if self.client.default_actor_uri:
71
+ body["me"] = self.client.default_actor_uri
72
+ return status_code, body, headers
73
+
74
+
75
+ class MyBearerTokenValidator(BearerTokenValidator):
76
+ def authenticate_token(self, token_string):
77
+ token = db.session.execute(
78
+ select(Token).filter_by(access_token=token_string)
79
+ ).scalar_one()
80
+ return token
81
+
82
+
83
+ def init_oauth(app):
84
+
85
+ def get_client(client_id):
86
+ print("get client")
87
+ client = db.session.execute(
88
+ select(Client).filter_by(client_id=client_id)
89
+ ).scalar_one()
90
+ return client
91
+
92
+ def save_token(token_data, request):
93
+
94
+ expires = datetime.utcnow() + timedelta(seconds=token_data.get("expires_in"))
95
+
96
+ tok = Token(
97
+ access_token=token_data["access_token"],
98
+ refresh_token=token_data.get("refresh_token"),
99
+ token_type=token_data["token_type"],
100
+ _scopes=token_data["scope"],
101
+ client_id=request.client.client_id,
102
+ user_name=request.user.username,
103
+ expires=expires,
104
+ )
105
+ db.session.add(tok)
106
+ db.session.commit()
107
+ return tok
108
+
109
+ oauth_server.init_app(app, query_client=get_client, save_token=save_token)
110
+ oauth_server.register_grant(AuthorizationCodeGrant)
111
+ require_oauth.register_token_validator(MyBearerTokenValidator())
112
+
113
+
114
+ @bp.before_app_request
115
+ def load_logged_in_user():
116
+ username = session.get("username")
117
+
118
+ if username is None:
119
+ g.user = None
120
+ else:
121
+ try:
122
+ g.user = db.session.execute(
123
+ select(User).filter_by(username=username)
124
+ ).scalar_one()
125
+ except Exception as e:
126
+ g.user = None
127
+
128
+
129
+ def enable_user(username):
130
+ user = db.session.execute(select(User).filter_by(username=username)).scalar_one()
131
+ user.enabled = True
132
+ db.session.commit()
133
+
134
+
135
+ @bp.cli.command("enable_user")
136
+ @click.argument("username")
137
+ def enable_user_cli(username):
138
+ enable_user(username)
139
+
140
+
141
+ @bp.route("/login", methods=("GET", "POST"))
142
+ def login():
143
+ if request.method == "POST":
144
+ user = db.session.execute(
145
+ select(User).filter_by(username=request.form["username"])
146
+ ).scalar()
147
+ if user is None:
148
+ flash(f"Login failed.", "warning")
149
+ return render_template("auth/login.html")
150
+ password_okay = check_password_hash(user.password, request.form["password"])
151
+ if user.enabled and password_okay:
152
+ session["username"] = user.username
153
+ actor = db.session.execute(
154
+ select(FediActor).filter_by(object_uri=user.default_actor_uri)
155
+ ).scalar()
156
+ if actor is not None:
157
+ session["actor"] = actor.subject
158
+ flash(f"User {user.username} successful logged in.", "success")
159
+ current_app.logger.log(logging.INFO, user.username + "logged in.")
160
+ if session.get("login_redirect"):
161
+ return redirect(session["login_redirect"])
162
+ else:
163
+ return redirect(url_for("web.home"))
164
+ else:
165
+ flash(f"Login failed.", "warning")
166
+ return render_template("auth/login.html")
167
+
168
+
169
+ @bp.route("/register", methods=("GET", "POST"))
170
+ def register():
171
+ if request.method == "POST":
172
+ error = None
173
+ try:
174
+ username = request.form["username"]
175
+ user = User(
176
+ id=username,
177
+ username=username,
178
+ remarks=request.form["remarks"],
179
+ email=request.form["email"],
180
+ password=generate_password_hash(request.form["password"]),
181
+ )
182
+ db.session.add(user)
183
+ db.session.commit()
184
+ except IntegrityError:
185
+ flash(f"User {username} already exists", "warning")
186
+ else:
187
+ flash(f"User {username} successful created.", "success")
188
+ return redirect(url_for("auth.login"))
189
+ return render_template("auth/register.html")
190
+
191
+
192
+ @bp.route("/logout", methods=("GET", "POST"))
193
+ def logout():
194
+ session.clear()
195
+ return redirect(url_for("auth.login"))
196
+
197
+
198
+ @bp.post("client_registration")
199
+ def client_registration():
200
+ client_id = secrets.token_hex(16)
201
+ client_secret = secrets.token_urlsafe(32)
202
+
203
+ data = request.json
204
+
205
+ description = (
206
+ "Created on "
207
+ + datetime.today().strftime("%Y-%m-%d %H:%M:%S")
208
+ + "\n"
209
+ + request.user_agent.string
210
+ )
211
+ if data.get("client_uri"):
212
+ description = description + "\n" + data["client_uri"]
213
+
214
+ client = Client(
215
+ name="",
216
+ description=description,
217
+ id=client_id,
218
+ client_id=client_id,
219
+ client_secret=generate_password_hash(client_secret),
220
+ is_confidential=True,
221
+ _redirect_uris=" ".join(data["redirect_uris"]),
222
+ )
223
+ if data.get("client_name"):
224
+ client.name = data["client_name"]
225
+ if data.get("token_endpoint_auth_method") == "none":
226
+ client.is_confidential = False
227
+ if data.get("scope"):
228
+ client._default_scopes = data["scope"]
229
+
230
+ db.session.add(client)
231
+ db.session.commit()
232
+
233
+ return {
234
+ "client_id": client_id,
235
+ "client_secret": client_secret,
236
+ "client_secret_expires_at": 0,
237
+ "scope": data["scope"],
238
+ }
239
+
240
+
241
+ @bp.route("/authorization", methods=("GET", "POST"))
242
+ def authorize(*args, **kwargs):
243
+ if not g.user:
244
+ session["login_redirect"] = request.url
245
+ return redirect(url_for("auth.login"))
246
+
247
+ client = db.session.execute(
248
+ select(Client).filter_by(client_id=request.args.get("client_id"))
249
+ ).scalar_one()
250
+ if client.owner and client.default_actor_uri:
251
+ return oauth_server.create_authorization_response(grant_user=client.owner)
252
+
253
+ if request.method == "GET":
254
+ return render_template("auth/authorization.html")
255
+
256
+ if request.method == "POST":
257
+ confirm = request.form.get("confirm", "no")
258
+ if confirm == "yes":
259
+ client.enabled = True
260
+ client.owner_id = g.user.username
261
+ actor = db.session.execute(
262
+ select(FediActor).filter_by(subject=request.form.get("actor"))
263
+ ).scalar_one()
264
+ client.default_actor_uri = actor.object_uri
265
+ db.session.commit()
266
+ return oauth_server.create_authorization_response(grant_user=g.user)
267
+ else:
268
+ return oauth_server.create_authorization_response(grant_user=None)
269
+
270
+
271
+ @bp.route("/token", methods=["POST"])
272
+ def issue_token():
273
+ return oauth_server.create_token_response()
274
+
275
+
276
+ @bp.route("/hello")
277
+ @require_oauth("read")
278
+ def hello():
279
+ return {"text": "Hello World!"}
280
+
281
+
282
+ def require_permission():
283
+ def permission_decorator(view):
284
+ @functools.wraps(view)
285
+ def wrapper(*args, **kwargs):
286
+ if g.user is None:
287
+ return redirect(url_for("auth.login"))
288
+ return view(*args, **kwargs)
289
+
290
+ return wrapper
291
+
292
+ return permission_decorator
@@ -0,0 +1 @@
1
+ """This folder contains the Fediflax modules required for core functionality."""
@@ -0,0 +1,104 @@
1
+ from os import PathLike
2
+ from typing import override
3
+
4
+ import yaml
5
+ from flask import Flask
6
+ from flask_sqlalchemy_lite import SQLAlchemy
7
+
8
+ from fediflax import alembic, talisman
9
+ from fediflax.database import db
10
+
11
+ from ..types import Core, Extension, PageType
12
+ from .web import WebManager
13
+
14
+
15
+ class FediflaxInstance(Core):
16
+ """ """
17
+
18
+ def __init__(self, config_file: str | PathLike[str]):
19
+ self.extensions: list[Extension] = []
20
+ self.config_file: str | PathLike[str] = config_file
21
+ self.web: WebManager = WebManager()
22
+ self._db: SQLAlchemy = db
23
+
24
+ @property
25
+ @override
26
+ def database(self):
27
+ return self._db
28
+
29
+ @override
30
+ def register_page_type(self, page_type: PageType):
31
+ self.web.register_page_type(page_type)
32
+
33
+ def add_extension(self, extension: Extension):
34
+ self.extensions.append(extension)
35
+ extension.init_core(self)
36
+
37
+ def add_default_extensions(self, custom_pages: bool = True, documents: bool = True):
38
+ if custom_pages:
39
+ from ..extensions.custom_pages import CustomPageExtension
40
+
41
+ self.add_extension(CustomPageExtension())
42
+ if documents:
43
+ from ..extensions.documents import DocumentPageExtension
44
+
45
+ self.add_extension(DocumentPageExtension())
46
+
47
+ def init_app(self, app: Flask):
48
+ self._init_config(app)
49
+ db.init_app(app)
50
+ alembic.init_app(app)
51
+ self._init_talisman(app)
52
+ self._register_core(app)
53
+ for extension in self.extensions:
54
+ extension.init_app(app, self)
55
+
56
+ def _init_config(self, app: Flask):
57
+ app.config.from_mapping(
58
+ SECRET_KEY="dev",
59
+ SQLALCHEMY_ENGINES={"default": "sqlite:///default.sqlite"},
60
+ INSTANCE_NAME="Instanz-Infos",
61
+ )
62
+ app.config.from_file(self.config_file, load=yaml.safe_load, text=False)
63
+
64
+ def _init_talisman(self, app: Flask):
65
+ csp = {
66
+ "default-src": "'self'",
67
+ "script-src": ["'strict-dynamic'", "'unsafe-eval'"],
68
+ "img-src": ["'self'", "data:"],
69
+ }
70
+
71
+ talisman.init_app(
72
+ app,
73
+ content_security_policy=csp,
74
+ content_security_policy_nonce_in=["script-src"],
75
+ )
76
+ pass
77
+
78
+ def _register_core(self, app: Flask):
79
+
80
+ self.web.init_app(app, self)
81
+
82
+ from ..legacy import templates as legacy_templates
83
+
84
+ legacy_templates.init_app(app)
85
+
86
+ from .. import (
87
+ admin,
88
+ auth,
89
+ federation,
90
+ media,
91
+ settings,
92
+ well_known,
93
+ )
94
+
95
+ app.register_blueprint(admin.bp)
96
+ app.register_blueprint(auth.bp)
97
+ auth.init_oauth(app)
98
+ app.register_blueprint(federation.bp)
99
+ federation.register_hooks()
100
+ app.register_blueprint(media.bp)
101
+ app.register_blueprint(settings.bp)
102
+ # app.register_blueprint(web.bp)
103
+ app.register_blueprint(well_known.bp)
104
+ app.add_url_rule("/", endpoint="web.home")
@@ -0,0 +1,4 @@
1
+ """This folder provides the functionality for the content management system."""
2
+
3
+ from .utils import url_for_page as url_for_page
4
+ from .web_manager import WebManager as WebManager
@@ -0,0 +1,51 @@
1
+ {% extends 'base.html' %}
2
+
3
+ {% block header %}
4
+ <h1>{% block title %}Create Page{% endblock %}</h1>
5
+ {% endblock %}
6
+
7
+ {% block content %}
8
+ <form method="post">
9
+
10
+ <label>Actor
11
+ <input type="text" name="actor" id="nav_actor" value="{{actor}}" required readonly>
12
+ </label>
13
+
14
+ <label>Navigation Menu
15
+
16
+ <select name="nav_section" required>
17
+ <option value="home">Home</option>
18
+ <option value="main">Main</option>
19
+ <option value="profile_page">Profile Page</option>
20
+ <option value="domain_footer">Domain Footer</option>
21
+ <option value="instance_footer">Instance Footer</option>
22
+
23
+ </select>
24
+ </label>
25
+
26
+ <label>Name
27
+ <input type="text" name="nav_name" id="nav_name" required />
28
+ </label>
29
+
30
+ <label>Order hint
31
+ <input type="text" name="nav_order" id="nav_order" />
32
+ </label>
33
+
34
+ <label>Path
35
+ <input type="text" name="nav_path" id="nav_path" required />
36
+ </label>
37
+
38
+ <label>Page Type
39
+ <select name="page_type" required>
40
+ <option value="document">Markdown Document</option>
41
+ <option value="custom_page">Angepasste Seite</option>
42
+ </select>
43
+ </label>
44
+
45
+ <!-- <label> Draft
46
+ <input type="checkbox" name="draft" checked />
47
+ </label> -->
48
+
49
+ <button type="submit" name="action" class="button" value="create">Create page</button>
50
+ </form>
51
+ {% endblock %}
@@ -0,0 +1,28 @@
1
+ {% extends 'base.html' %}
2
+
3
+ {% block header %}
4
+ <h1>{% block title %}Delete Page{% endblock %}</h1>
5
+ {% endblock %}
6
+
7
+ {% block content %}
8
+ Do you want to delete the following page?
9
+ <form method="post">
10
+
11
+ <input type="hidden" name="csrf-check" value="{{csrf_check}}" required/>
12
+
13
+ <label>ID
14
+ <input type="text" name="id" id="page_id" value="{{page.id}}" required readonly />
15
+ </label>
16
+
17
+ <label>Actor
18
+ <input type="text" name="actor" id="nav_actor" value="{{page.actor}}" readonly />
19
+ </label>
20
+
21
+ <label>Path
22
+ <input type="text" name="nav_path" id="nav_path" value="{{page.subpath}}"" readonly />
23
+ </label>
24
+
25
+
26
+ <button type="submit" name="action" value="delete" class="button">Delete Page</button>
27
+ </form>
28
+ {% endblock %}
@@ -0,0 +1,16 @@
1
+ from flask import (
2
+ request,
3
+ url_for,
4
+ )
5
+
6
+ from fediflax.models import WebPage
7
+
8
+
9
+ def url_for_page(page: WebPage) -> str:
10
+ actor, domain = page.actor.split("@")
11
+ return (
12
+ request.scheme
13
+ + "://"
14
+ + domain
15
+ + url_for("web.show_page", actor=actor, subpath=page.subpath)
16
+ )