Merge remote-tracking branch 'origin/main'

This commit is contained in:
rimu 2024-12-12 08:13:36 +13:00
commit ab8df181ad
13 changed files with 300 additions and 140 deletions

View file

@ -454,16 +454,11 @@ def shared_inbox():
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'ActivityPub activity from a local actor')
return '', 200
actor.instance.last_seen = utcnow()
actor.instance.dormant = False
actor.instance.gone_forever = False
actor.instance.failures = 0
actor.instance.ip_address = ip_address()
db.session.commit()
bounced = False
try:
HttpSignature.verify_request(request, actor.public_key, skip_date=True)
except VerificationError as e:
bounced = True
if not 'signature' in request_json:
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify HTTP signature: ' + str(e))
return '', 400
@ -474,6 +469,13 @@ def shared_inbox():
log_incoming_ap(id, APLOG_NOTYPE, APLOG_FAILURE, request_json if store_ap_json else None, 'Could not verify LD signature: ' + str(e))
return '', 400
actor.instance.last_seen = utcnow()
actor.instance.dormant = False
actor.instance.gone_forever = False
actor.instance.failures = 0
actor.instance.ip_address = ip_address() if not bounced else ''
db.session.commit()
# When a user is deleted, the only way to be fairly sure they get deleted everywhere is to tell the whole fediverse.
# Earlier check means this is only for users that already exist, processing it here means that http signature will have been verified
if account_deletion == True:

View file

@ -404,5 +404,62 @@ def alpha_emoji():
return jsonify({"error": "not_yet_implemented"}), 400
# HTML routes
from flask import abort, render_template
from app.utils import current_theme
import os
@bp.route('/api/alpha/', methods=['GET'])
def get_alpha():
if not current_app.debug:
abort(404)
template_name = "index.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/auth/login', methods=['GET'])
def get_alpha_auth_login():
if not current_app.debug:
abort(404)
template_name = "auth/login.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/auth/logout', methods=['GET'])
def get_alpha_auth_logout():
if not current_app.debug:
abort(404)
template_name = "auth/logout.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)
@bp.route('/api/alpha/communities', methods=['GET'])
def get_alpha_communities():
if not current_app.debug:
abort(404)
template_name = "list_communities.html"
theme = current_theme()
if theme != '' and os.path.exists(f'app/templates/themes/{theme}/{template_name}'):
return render_template(f'themes/{theme}/{template_name}')
else:
return render_template(template_name)

View file

@ -20,9 +20,16 @@ def get_site(auth):
user = None
logo = g.site.logo if g.site.logo else '/static/images/logo2.png'
logo_152 = g.site.logo_152 if g.site.logo_152 else '/static/images/apple-touch-icon.png'
logo_32 = g.site.logo_32 if g.site.logo_32 else '/static/images/favicon-32x32.png'
logo_16 = g.site.logo_16 if g.site.logo_16 else '/static/images/favicon-16x16.png'
site = {
"enable_downvotes": g.site.enable_downvotes,
"icon": f"https://{current_app.config['SERVER_NAME']}{logo}",
"icon_152": f"https://{current_app.config['SERVER_NAME']}{logo_152}",
"icon_32": f"https://{current_app.config['SERVER_NAME']}{logo_32}",
"icon_16": f"https://{current_app.config['SERVER_NAME']}{logo_16}",
"registration_mode": g.site.registration_mode,
"name": g.site.name,
"actor_id": f"https://{current_app.config['SERVER_NAME']}/",
"user_count": users_total(),

View file

@ -0,0 +1,38 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0">GET <code>/api/alpha/site</code></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<p class="mb-0">POST <code>/api/alpha/user/login</code></p>
<details><summary>JSON</summary><pre id="login_json"></pre></details>
<hr />
<form id="login_form">
<div class="form-floating">
<input type="text" class="form-control" id="username" placeholder="Username">
<label for="username">Username</label>
</div>
<div class="form-floating my-2">
<input type="password" class="form-control" id="password" placeholder="Password">
<label for="password">Password</label>
</div>
<div class="form-check text-start my-3">
<input class="form-check-input" type="checkbox" id="remember_me">
<label class="form-check-label" for="remember_me">Remember me</label>
</div>
<button class="btn btn-primary w-100 py-2" type="submit">Sign in</button>
</form>
<p class="mt-3">
{{ _('New User?') }} <a href="{{ url_for('auth.register') }}">{{ _('Register new account') }}</a>
</p>
<p class="mt-0">
{{ _('Forgot Your Password?') }} <a href="{{ url_for('auth.reset_password_request') }}">{{ _('Reset it') }}</a>
</p>
<script src="{{ '/static/themes/' + theme() + '/js/login.js' }}"></script>
{% endblock %}

View file

@ -0,0 +1,5 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<script src="{{ '/static/themes/' + theme() + '/js/logout.js' }}"></script>
{% endblock %}

View file

@ -1,9 +1,47 @@
{% macro render_username(user, add_domain=True) -%}
<span class="render_username">
{% if user.deleted -%}
[deleted]
{% else -%}
<a href="/u/{{ user.link() }}" title="{{ user.ap_id if user.ap_id != none else user.user_name }}" aria-label="{{ _('Author') }}">
{{ user.display_name() }}{% if not user.is_local() %}<span class="text-muted">@{{ user.ap_domain }}</span>{% endif %}
</a>
{% if user.bot -%}
<span class="fe fe-bot-account" title="Bot account"> </span>
{% endif -%}
{% endif -%}
</span>
{% endmacro -%}
{% macro render_communityname(community, add_domain=True) -%}
<span class="render_community">
<a href="/c/{{ community.link() }}" aria-label="{{ _('Go to community %(name)s', name=community.name) }}">
{{ community.title }}{% if not community.is_local() %}<span class="text-muted">@{{ community.ap_domain }}</span>{% endif %}
</a>
</span>
{% endmacro -%}
<!doctype html>
<html lang="en" data-bs-theme="auto">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title id="head-title">{% if not debug_mode %}{{ g.site.name }}{% endif %}</title>
<title id="head_title"></title>
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="mobile-web-app-capable" content="yes">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<link rel="manifest" href="/static/manifest.json">
<link id="icon_152" rel="apple-touch-icon" sizes="152x152" href="">
<link id="icon_32" rel="icon" type="image/png" sizes="32x32" href="">
<link id="icon_16" rel="icon" type="image/png" sizes="16x16" href="">
<link id="icon_shortcut" rel="shortcut icon" type="image/png" href="">
<link id="favicon" rel='icon' type="image/x-icon" href="">
{{ bootstrap.load_css() }}
<link href="{{ '/static/themes/' + theme() + '/css/navbars.css' }}" rel="stylesheet">
<link href="{{ '/static/themes/' + theme() + '/css/color-modes.css' }}" rel="stylesheet">
@ -44,99 +82,12 @@
<nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary">
<div class="container-fluid">
<a class="navbar-brand" href="/" id="navbar-title">{% if not debug_mode %}{{ g.site.name }}{% endif %}</a>
<a class="navbar-brand" href="/api/alpha" id="navbar_title"></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
{% if current_user.is_anonymous %}
<li class="nav-item"><a class="nav-link" href="/auth/login">{{ _('Log in') }}</a></li>
<li class="nav-item"><a class="nav-link" href="/auth/register">{{ _('Register') }}</a></li>
<li class="nav-item"><a class="nav-link" href="/donate">{{ _('Donate') }}</a></li>
{% else %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ _('Communities') }}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/communities">{{ _('All communities') }}</a></li>
{% if moderating_communities %}
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">{{ _('Moderating') }}</h6></li>
{% for mc in moderating_communities %}
<li>
<a class="dropdown-item" href="/c/{{ mc.link() }}">{{ mc.title }}<span class="text-body-secondary"> ({{ mc.ap_domain }})</span></a>
</li>
{% endfor %}
{% endif %}
{% if joined_communities %}
<li><hr class="dropdown-divider"></li>
<li><h6 class="dropdown-header">{{ _('Joined communities') }}</h6></li>
{% for jc in joined_communities %}
<li>
<a class="dropdown-item" href="/c/{{ jc.link() }}">{{ jc.title }}<span class="text-body-secondary"> ({{ jc.ap_domain }})</span></a>
</li>
{% endfor %}
{% endif %}
</ul>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ _('Account') }}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/u/{{ current_user.link() }}">{{ _('View profile') }}</a></li>
<li><a class="dropdown-item" href="/user/settings">{{ _('Edit profile & settings') }}</a></li>
<li><a class="dropdown-item" href="/chat">{{ _('Chats') }}</a></li>
<li><a class="dropdown-item" href="/bookmarks">{{ _('Bookmarks') }}</a></li>
<li><a class="dropdown-item" href="/alerts">{{ _('Activity Alerts') }}</a></li>
{% if current_user.hide_read_posts %}
<li><a class="dropdown-item" href="/read-posts">{{ _('Read Posts') }}</a></li>
{% endif %}
</ul>
</li>
<li class="nav-item"><a class="nav-link" href="/donate">{{ _('Donate') }}</a></li>
{% if user_access('change instance settings', current_user.id) %}
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
{{ _('Admin') }}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/site") }}">{{ _('Site profile') }}</a></li>
<li><a class="dropdown-item" href="/admin/misc">{{ _('Misc settings') }}</a></li>
<li><a class="dropdown-item" href="/admin/communities">{{ _('Communities') }}</a></li>
<li><a class="dropdown-item" href="/admin/topics">{{ _('Topics') }}</a></li>
<li><a class="dropdown-item" href="/admin/users?local_remote=local">{{ _('Users') }}</a></li>
<li><a class="dropdown-item" href="/admin/users/trash?local_remote=local">{{ _('Monitoring - users') }}</a></li>
<li><a class="dropdown-item" href="/admin/content/trash">{{ _('Monitoring - content') }}</a></li>
<li><a class="dropdown-item" href="/admin/content/spam">{{ _('Monitoring - spammy content') }}</a></li>
<li><a class="dropdown-item" href="/admin/content/deleted">{{ _('Deleted content') }}</a></li>
{% if g.site.registration_mode == 'RequireApplication' %}
<li><a class="dropdown-item" href="/admin/approve_registrations">{{ _('Registration applications') }}</a></li>
{% endif %}
<li><a class="dropdown-item" href="/admin/reports">{{ _('Moderation') }}</a></li>
<li><a class="dropdown-item" href="/admin/federation">{{ _('Federation') }}</a></li>
<li><a class="dropdown-item" href="/admin/instances">{{ _('Instances') }}</a></li>
<li><a class="dropdown-item" href="/admin/newsletter">{{ _('Newsletter') }}</a></li>
<li><a class="dropdown-item" href="/admin/activities">{{ _('Activities') }}</a></li>
<li><a class="dropdown-item" href="/admin/permissions">{{ _('Permissions') }}</a></li>
{% if debug_mode %}
<li><a class="dropdown-item" href="/dev/tools">{{ _('Dev Tools') }}</a></li>
{% endif %}
</ul>
</li>
{% endif %}
<li class="nav-item"><a class="nav-link" href="/notifications">{{ _('Inbox') }}</a></li>
<li class="nav-item"><a class="nav-link" href="/auth/logout">{{ _('Log out') }}</a></li>
{% endif %}
<ul class="navbar-nav me-auto mb-2 mb-lg-0" id="navbar_items">
</ul>
</div>
</div>
@ -152,14 +103,12 @@
<a href="/donate">{{ _('Donate') }}</a><br />
<a href="/about">{{ _('About') }}</a><br />
<a href="/keyboard_shortcuts">{{ _('Keyboard shortcuts') }}</a><br/>
<a href="https://codeberg.org/rimu/pyfedi">PieFed</a> is free and open source. Please <a href="https://codeberg.org/rimu/pyfedi/issues">report bugs</a> or <a href="https://join.piefed.social/get-involved/">get involved</a>.<br />
<a href="https://codeberg.org/rimu/pyfedi">PieFed</a> is free and open source.<br />Please <a href="https://codeberg.org/rimu/pyfedi/issues">report bugs</a> or <a href="https://join.piefed.social/get-involved/">get involved</a>.<br />
<a href="/privacy">Privacy policy</a>
</small>
</footer>
{{ bootstrap.load_js() }}
{% if debug_mode %}
<script src="{{ '/static/themes/' + theme() + '/js/site.js' }}"></script>
{% endif %}
<script src="{{ '/static/themes/' + theme() + '/js/site.js' }}" type="module"></script>
</body>
</html>

View file

@ -0,0 +1,12 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<h1>{{ _('Donate') }}</h1>
<p>PieFed is free and open-source software while operating without any advertising, monetization, or reliance on
venture capital. Your contributions are vital in supporting the PieFed development effort,
allowing us to expand and enhance PieFed with new features.</p>
<div class="btn-group btn-group-lg" role="group" aria-label="Donation options">
<a type="button" class="btn btn-outline-primary" href="https://www.patreon.com/PieFed">Donate using Patreon</a>
</div>
{% endblock %}

View file

@ -1,21 +1,6 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<h3>Site Info from API</h3>
{% if not debug_mode %}
<p>(API only available in debug mode)</p>
{% else %}
<ul>
<li><span class="text-body-secondary">version: </span><span id="site_version"></span></li>
<li><span class="text-body-secondary">actor_id: </span><span id="site_actor_id"></span></li>
<li><span class="text-body-secondary">description: </span><span id="site_description"></span></li>
<li><span class="text-body-secondary">enable_downvotes: </span><span id="site_enable_downvotes"></span></li>
<li><span class="text-body-secondary">icon: </span><span id="site_icon"></span></li>
<li><span class="text-body-secondary">name: </span><span id="site_name"></span></li>
<li><span class="text-body-secondary">sidebar: </span><span id="site_sidebar"></span></li>
<li><span class="text-body-secondary">user_count: </span><span id="site_user_count"></span></li>
<li><span class="text-body-secondary">all languages: </span><span id="site_all_languages"></span></li>
</ul>
{% endif %}
<p class="mb-0">GET <code>/api/alpha/site</code></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
{% endblock%}

View file

@ -0,0 +1,15 @@
import { baseUrl } from './site.js';
const api = baseUrl + '/api/alpha/community/list';
import { jwt } from './site.js';
if (jwt != null) {
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
} else {
var request = {method: "GET"};
}
fetch(api, request)
.then(response => response.json())
.then(data => {
document.querySelector('#community_list_json').textContent = JSON.stringify(data, null, 2);
})

View file

@ -0,0 +1,38 @@
document.querySelector('#login_json').textContent = '{"username_or_email": "", "password": ""}'
const login_form = document.getElementById('login_form');
const username = document.getElementById('username');
const password = document.getElementById('password');
const remember_me = document.getElementById('remember_me');
login_form.addEventListener('submit', async event => {
event.preventDefault();
json_string = JSON.stringify({ username_or_email: username.value, password: password.value })
const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`;
const api = baseUrl + '/api/alpha/user/login';
try {
const response = await fetch(api, {method: 'POST', body: json_string});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const response_json = await response.json();
if (remember_me.checked == true) {
localStorage.setItem('jwt', response_json['jwt']);
} else {
sessionStorage.setItem('jwt', response_json['jwt']);
}
window.location.href = baseUrl;
} catch (error) {
console.error(error.message);
}
});

View file

@ -0,0 +1,6 @@
localStorage.removeItem('jwt');
sessionStorage.removeItem('jwt');
const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`;
window.location.href = baseUrl;

View file

@ -1,30 +1,66 @@
const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`;
const api = baseUrl + '/api/alpha/site'
export const baseUrl = `${url.protocol}//${url.host}`;
const api = baseUrl + '/api/alpha/site';
fetch(api)
let jwt = null;
let session_jwt = sessionStorage.getItem('jwt');
if (session_jwt != null) {
jwt = session_jwt;
} else {
let local_jwt = localStorage.getItem('jwt');
if (local_jwt != null) {
jwt = local_jwt;
}
}
export { jwt };
const ul = document.getElementById('navbar_items');
if (jwt != null) {
var request = {method: "GET", headers: {Authorization: `Bearer ${jwt}`}};
ul.innerHTML = '<li class="nav-item dropdown">' +
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'Communities' +
'</a>' +
'<ul class="dropdown-menu">' +
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
'</ul>' +
'</li>' +
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>' +
'<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/logout">Logout (via API)</a></li>';
} else {
var request = {method: "GET"};
ul.innerHTML = '<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/login">Log in (via API)</a></li>' +
'<li class="nav-item dropdown">' +
'<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">' +
'Communities' +
'</a>' +
'<ul class="dropdown-menu">' +
'<li><a class="dropdown-item" href="/api/alpha/communities">All communities</a></li>' +
'</ul>' +
'</li>' +
'<li class="nav-item"><a class="nav-link" href="/user/settings">User settings</a></li>' +
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>';
}
fetch(api, request)
.then(response => response.json())
.then(data => {
// head
document.querySelector('#head_title').textContent = data.site.name;
document.querySelector('#icon_152').href = data.site.icon_152;
document.querySelector('#icon_32').href = data.site.icon_32;
document.querySelector('#icon_16').href = data.site.icon_16;
document.querySelector('#icon_shortcut').href = data.site.icon_32;
document.querySelector('#favicon').href = baseUrl + '/static/images/favicon.ico';
// navbar
document.querySelector('#head-title').textContent = data.site.name
document.querySelector('#navbar-title').innerHTML = '<img src="' + data.site.icon + '" alt="Logo" width="36" height="36" />' + ' ' + data.site.name
document.querySelector('#navbar_title').innerHTML = '<img src="' + data.site.icon + '" alt="Logo" width="36" height="36" />' + ' ' + data.site.name;
// site info
document.querySelector('#site_version').textContent = data.version
document.querySelector('#site_actor_id').textContent = data.site.actor_id
document.querySelector('#site_description').textContent = data.site.description
document.querySelector('#site_enable_downvotes').textContent = data.site.enable_downvotes
document.querySelector('#site_icon').textContent = data.site.icon
document.querySelector('#site_name').textContent = data.site.name
document.querySelector('#site_sidebar').textContent = data.site.sidebar
document.querySelector('#site_user_count').textContent = data.site.user_count
let lang_names = data.site.all_languages[0].name;
let lang_count = data.site.all_languages.length;
for (let i = 1; i < lang_count; i++) {
lang_names += ", " + data.site.all_languages[i].name;
}
document.querySelector('#site_all_languages').textContent = lang_names
document.querySelector('#site_json').textContent = JSON.stringify(data, null, 2);
})

View file

@ -0,0 +1,10 @@
{% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %}
<p class="mb-0">GET <code>/api/alpha/site</code></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
<p class="mb-0">GET <code>/api/alpha/community/list</code></p>
<details><summary>JSON</summary><pre id="community_list_json"></pre></details>
<script src="{{ '/static/themes/' + theme() + '/js/list_communities.js' }}" type="module"></script>
{% endblock %}