API: login and out when using x-api theme

This commit is contained in:
freamon 2024-12-11 00:45:17 +00:00
parent a7e2175a55
commit 111d726de7
11 changed files with 269 additions and 133 deletions

View file

@ -404,5 +404,62 @@ def alpha_emoji():
return jsonify({"error": "not_yet_implemented"}), 400 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

@ -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,46 @@
{% 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> <!doctype html>
<html lang="en" data-bs-theme="auto"> <html lang="en" data-bs-theme="auto">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <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="">
{{ bootstrap.load_css() }} {{ bootstrap.load_css() }}
<link href="{{ '/static/themes/' + theme() + '/css/navbars.css' }}" rel="stylesheet"> <link href="{{ '/static/themes/' + theme() + '/css/navbars.css' }}" rel="stylesheet">
<link href="{{ '/static/themes/' + theme() + '/css/color-modes.css' }}" rel="stylesheet"> <link href="{{ '/static/themes/' + theme() + '/css/color-modes.css' }}" rel="stylesheet">
@ -44,99 +81,12 @@
<nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary"> <nav class="navbar navbar-expand-lg sticky-top bg-body-tertiary">
<div class="container-fluid"> <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"> <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> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0"> <ul class="navbar-nav me-auto mb-2 mb-lg-0" id="navbar_items">
{% 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> </ul>
</div> </div>
</div> </div>
@ -152,14 +102,12 @@
<a href="/donate">{{ _('Donate') }}</a><br /> <a href="/donate">{{ _('Donate') }}</a><br />
<a href="/about">{{ _('About') }}</a><br /> <a href="/about">{{ _('About') }}</a><br />
<a href="/keyboard_shortcuts">{{ _('Keyboard shortcuts') }}</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> <a href="/privacy">Privacy policy</a>
</small> </small>
</footer> </footer>
{{ bootstrap.load_js() }} {{ bootstrap.load_js() }}
{% if debug_mode %} <script src="{{ '/static/themes/' + theme() + '/js/site.js' }}" type="module"></script>
<script src="{{ '/static/themes/' + theme() + '/js/site.js' }}"></script>
{% endif %}
</body> </body>
</html> </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' %} {% extends 'themes/' + theme() + '/base.html' %}
{% block app_content %} {% block app_content %}
<h3>Site Info from API</h3> <p class="mb-0">GET <code>/api/alpha/site</code></p>
<details><summary>JSON</summary><pre id="site_json"></pre></details>
{% 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 %}
{% endblock%} {% 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,52 @@
const url = new URL(window.location.href); const url = new URL(window.location.href);
const baseUrl = `${url.protocol}//${url.host}`; export const baseUrl = `${url.protocol}//${url.host}`;
const api = baseUrl + '/api/alpha/site' 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="/api/alpha/auth/logout">Logout</a></li>';
} else {
var request = {method: "GET"};
ul.innerHTML = '<li class="nav-item"><a class="nav-link" href="/api/alpha/auth/login">Log in</a></li>' +
'<li class="nav-item"><a class="nav-link" href="/donate">Donate</a></li>';
}
fetch(api, request)
.then(response => response.json()) .then(response => response.json())
.then(data => { .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;
// navbar // 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 // site info
document.querySelector('#site_version').textContent = data.version document.querySelector('#site_json').textContent = JSON.stringify(data, null, 2);
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
}) })

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 %}