mirror of
https://git.pleroma.social/pleroma/relay.git
synced 2024-11-23 23:17:58 +00:00
Compare commits
No commits in common. "e6831f04eb545ef662f2ca560755380b5ad6daf5" and "a271cf22b49452aedb3e97c5e3c2306d81391861" have entirely different histories.
e6831f04eb
...
a271cf22b4
|
@ -23,7 +23,6 @@ from .misc import check_open_port, get_resource
|
||||||
from .template import Template
|
from .template import Template
|
||||||
from .views import VIEWS
|
from .views import VIEWS
|
||||||
from .views.api import handle_api_path
|
from .views.api import handle_api_path
|
||||||
from .views.frontend import handle_frontend_path
|
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from tinysql import Database, Row
|
from tinysql import Database, Row
|
||||||
|
@ -39,8 +38,7 @@ class Application(web.Application):
|
||||||
def __init__(self, cfgpath: str | None):
|
def __init__(self, cfgpath: str | None):
|
||||||
web.Application.__init__(self,
|
web.Application.__init__(self,
|
||||||
middlewares = [
|
middlewares = [
|
||||||
handle_api_path,
|
handle_api_path
|
||||||
handle_frontend_path
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -22,10 +22,7 @@ THEMES = {
|
||||||
'border': '#444',
|
'border': '#444',
|
||||||
'message-text': '#DDD',
|
'message-text': '#DDD',
|
||||||
'message-background': '#335',
|
'message-background': '#335',
|
||||||
'message-border': '#446',
|
'message-border': '#446'
|
||||||
'error-text': '#DDD',
|
|
||||||
'error-background': '#533',
|
|
||||||
'error-border': '#644'
|
|
||||||
},
|
},
|
||||||
'pink': {
|
'pink': {
|
||||||
'text': '#DDD',
|
'text': '#DDD',
|
||||||
|
@ -37,10 +34,7 @@ THEMES = {
|
||||||
'border': '#444',
|
'border': '#444',
|
||||||
'message-text': '#DDD',
|
'message-text': '#DDD',
|
||||||
'message-background': '#335',
|
'message-background': '#335',
|
||||||
'message-border': '#446',
|
'message-border': '#446'
|
||||||
'error-text': '#DDD',
|
|
||||||
'error-background': '#533',
|
|
||||||
'error-border': '#644'
|
|
||||||
},
|
},
|
||||||
'blue': {
|
'blue': {
|
||||||
'text': '#DDD',
|
'text': '#DDD',
|
||||||
|
@ -52,10 +46,7 @@ THEMES = {
|
||||||
'border': '#444',
|
'border': '#444',
|
||||||
'message-text': '#DDD',
|
'message-text': '#DDD',
|
||||||
'message-background': '#335',
|
'message-background': '#335',
|
||||||
'message-border': '#446',
|
'message-border': '#446'
|
||||||
'error-text': '#DDD',
|
|
||||||
'error-background': '#533',
|
|
||||||
'error-border': '#644'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,80 +1,22 @@
|
||||||
-macro menu_item(name, path)
|
|
||||||
-if view.request.path == path
|
|
||||||
%a.button(active="true") -> =name
|
|
||||||
|
|
||||||
-else
|
|
||||||
%a.button(href="{{path}}") -> =name
|
|
||||||
|
|
||||||
!!!
|
!!!
|
||||||
%html
|
%html
|
||||||
%head
|
%head
|
||||||
%title << {{config.name}}: {{page}}
|
%title << {{config.name}}: {{page}}
|
||||||
%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")
|
||||||
%link(rel="stylesheet" type="text/css" href="/style.css?page={{page}}")
|
%link(rel="stylesheet" type="text/css" href="/style.css")
|
||||||
-block head
|
-block head
|
||||||
|
|
||||||
%body
|
%body
|
||||||
#menu.section(visible="false")
|
|
||||||
.menu-head
|
|
||||||
%span.menu-title << Menu
|
|
||||||
%span#menu-close.button << ✖
|
|
||||||
|
|
||||||
{{menu_item("Home", "/")}}
|
|
||||||
|
|
||||||
-if view.request["user"]
|
|
||||||
{{menu_item("Instances", "/admin/instances")}}
|
|
||||||
{{menu_item("Whitelist", "/admin/whitelist")}}
|
|
||||||
{{menu_item("Domain Bans", "/admin/domain_bans")}}
|
|
||||||
{{menu_item("Software Bans", "/admin/software_bans")}}
|
|
||||||
{{menu_item("Config", "/admin/config")}}
|
|
||||||
{{menu_item("Logout", "/logout")}}
|
|
||||||
|
|
||||||
-else
|
|
||||||
{{menu_item("Login", "/login")}}
|
|
||||||
|
|
||||||
#container
|
#container
|
||||||
#header.section
|
#header.section
|
||||||
%span#menu-open.button << ⁞
|
|
||||||
%a(href="https://{{domain}}/") -> =config.name
|
%a(href="https://{{domain}}/") -> =config.name
|
||||||
.empty
|
|
||||||
|
|
||||||
-if error
|
|
||||||
.error.section -> =error
|
|
||||||
|
|
||||||
-if message
|
|
||||||
.message.section -> =message
|
|
||||||
|
|
||||||
#content
|
#content
|
||||||
-block content
|
-block content
|
||||||
|
|
||||||
#footer.section
|
#footer.section
|
||||||
.col1
|
.col1
|
||||||
-if not view.request["user"]
|
|
||||||
%a(href="/login") << Login
|
|
||||||
|
|
||||||
-else
|
|
||||||
=view.request["user"]["username"]
|
|
||||||
(
|
|
||||||
%a(href="/logout") << Logout
|
|
||||||
)
|
|
||||||
|
|
||||||
.version
|
.version
|
||||||
%a(href="https://git.pleroma.social/pleroma/relay")
|
%a(href="https://git.pleroma.social/pleroma/relay")
|
||||||
ActivityRelay/{{version}}
|
ActivityRelay/{{version}}
|
||||||
|
|
||||||
%script(type="application/javascript")
|
|
||||||
const body = document.getElementById("container")
|
|
||||||
const menu = document.getElementById("menu");
|
|
||||||
const menu_open = document.getElementById("menu-open");
|
|
||||||
const menu_close = document.getElementById("menu-close");
|
|
||||||
|
|
||||||
menu_open.addEventListener("click", (event) => {menu.attributes.visible.nodeValue = "true"});
|
|
||||||
menu_close.addEventListener("click", (event) => {menu.attributes.visible.nodeValue = "false"});
|
|
||||||
body.addEventListener("click", (event) => {
|
|
||||||
if (event.target === menu_open) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.attributes.visible.nodeValue = "false";
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Config"
|
|
||||||
-block content
|
|
||||||
.section
|
|
||||||
UvU
|
|
|
@ -1,5 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Domain Bans"
|
|
||||||
-block content
|
|
||||||
.section
|
|
||||||
UvU
|
|
|
@ -1,41 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Instances"
|
|
||||||
-block content
|
|
||||||
%details.section
|
|
||||||
%summary << Add Instance
|
|
||||||
%form(target="/admin/instances", method="POST")
|
|
||||||
#add-instance
|
|
||||||
%label(for="domain") << Domain
|
|
||||||
%input(type="domain", name="domain", placeholder="Domain")
|
|
||||||
%label(for="actor") << Actor URL
|
|
||||||
%input(type="url", name="actor", placeholder="Actor URL")
|
|
||||||
%label(for="inbox") << Inbox URL
|
|
||||||
%input(type="url", name="inbox", placeholder="Inbox URL")
|
|
||||||
%label(for="software") << Software
|
|
||||||
%input(name="software", placeholder="software")
|
|
||||||
|
|
||||||
%input(type="submit" value="Add Instance")
|
|
||||||
|
|
||||||
#instances.section
|
|
||||||
%table
|
|
||||||
%thead
|
|
||||||
%tr
|
|
||||||
%td.instance << Instance
|
|
||||||
%td.software << Software
|
|
||||||
%td.date << Joined
|
|
||||||
%td.remove
|
|
||||||
|
|
||||||
%tbody
|
|
||||||
-for instance in instances
|
|
||||||
%tr
|
|
||||||
%td.instance
|
|
||||||
%a(href="https://{{instance.domain}}/" target="_new") -> =instance.domain
|
|
||||||
|
|
||||||
%td.software
|
|
||||||
=instance.software or "n/a"
|
|
||||||
|
|
||||||
%td.date
|
|
||||||
=instance.created.strftime("%Y-%m-%d")
|
|
||||||
|
|
||||||
%td.remove
|
|
||||||
%a(href="/admin/instances/delete/{{instance.domain}}" title="Remove Instance") << ✖
|
|
|
@ -1,5 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Software Bans"
|
|
||||||
-block content
|
|
||||||
.section
|
|
||||||
UvU
|
|
|
@ -1,5 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Whitelist"
|
|
||||||
-block content
|
|
||||||
.section
|
|
||||||
UvU
|
|
|
@ -1,9 +0,0 @@
|
||||||
-extends "base.haml"
|
|
||||||
-set page="Login"
|
|
||||||
-block content
|
|
||||||
%form.section(action="/login" method="post")
|
|
||||||
%label(for="username") << Username
|
|
||||||
%input(id="username" name="username" placeholder="Username" value="{{username or ''}}")
|
|
||||||
%label(for="password") << Password
|
|
||||||
%input(id="password" name="password" placeholder="Password" type="password")
|
|
||||||
%input(type="submit" value="Login")
|
|
|
@ -9,9 +9,6 @@
|
||||||
--message-text: {{theme["message-text"]}};
|
--message-text: {{theme["message-text"]}};
|
||||||
--message-background: {{theme["message-background"]}};
|
--message-background: {{theme["message-background"]}};
|
||||||
--message-border: {{theme["message-border"]}};
|
--message-border: {{theme["message-border"]}};
|
||||||
--error-text: {{theme["error-text"]}};
|
|
||||||
--error-background: {{theme["error-background"]}};
|
|
||||||
--error-border: {{theme["error-border"]}};
|
|
||||||
--spacing: 10px;
|
--spacing: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +29,6 @@ a:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
details summary {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
line-height: 1em;
|
line-height: 1em;
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
@ -91,78 +84,33 @@ table tbody td {
|
||||||
#container {
|
#container {
|
||||||
width: 1024px;
|
width: 1024px;
|
||||||
margin: 0px auto;
|
margin: 0px auto;
|
||||||
height: 100vh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 50px auto 50px;
|
|
||||||
justify-items: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header > * {
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#header > *:nth-child(2) {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu {
|
|
||||||
padding: 0px;
|
|
||||||
position: fixed;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
height: 100%;
|
|
||||||
width: max-content;
|
|
||||||
z-index: 1;
|
|
||||||
font-size: 1.5em;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu[visible="false"] {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu > a {
|
|
||||||
margin: var(--spacing);
|
|
||||||
display: block;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu > a[active="true"] {
|
|
||||||
cursor: default;
|
|
||||||
background-color: var(--background);
|
|
||||||
color: var(--primary);
|
|
||||||
border-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu .menu-head {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: auto max-content;
|
|
||||||
margin: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu .menu-head > * {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu .menu-title {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu-open, #menu-close {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu-close, #menu-open {
|
|
||||||
min-width: 35px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header a {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances .instance {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances .date {
|
||||||
|
width: max-content;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
#instances thead td {
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
#footer {
|
#footer {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto;
|
grid-template-columns: auto auto;
|
||||||
|
@ -173,34 +121,6 @@ table tbody td {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.button {
|
|
||||||
background-color: var(--primary);
|
|
||||||
border: 1px solid var(--primary);
|
|
||||||
color: var(--background);
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
background-color: var(--background);
|
|
||||||
color: var(--primary);
|
|
||||||
text-decoration: None;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error, .message {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error{
|
|
||||||
color: var(--error-text) !important;
|
|
||||||
background-color: var(--error-background) !important;
|
|
||||||
border: 1px solid var(--error-border) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
color: var(--message-text) !important;
|
color: var(--message-text) !important;
|
||||||
background-color: var(--message-background) !important;
|
background-color: var(--message-background) !important;
|
||||||
|
@ -223,37 +143,16 @@ table tbody td {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{% if page %}
|
|
||||||
{% include "style/" + page.lower().replace(" ", "_") + ".css" %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
@media (max-width: 1026px) {
|
@media (max-width: 1026px) {
|
||||||
body {
|
body {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#menu {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#menu > a {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#container {
|
#container {
|
||||||
width: unset;
|
width: unset;
|
||||||
margin: unset;
|
margin: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
|
||||||
grid-template-columns: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
grid-template-columns: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
border-width: 0px;
|
border-width: 0px;
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
#instances table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .instance {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .date {
|
|
||||||
width: max-content;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances thead td {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
form input[type="submit"] {
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .instance {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .software {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances .date {
|
|
||||||
width: max-content;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#instances thead td {
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
#add-instance {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: max-content auto;
|
|
||||||
grid-gap: var(--spacing);
|
|
||||||
margin-top: var(--spacing);
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
label, input {
|
|
||||||
margin: 0 auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
label, input:not([type="submit"]) {
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:not([type="submit"]) {
|
|
||||||
margin-bottom: var(--spacing);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1026px) {
|
|
||||||
label, input:not([type="submit"]) {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ from . import logger as logging
|
||||||
from .application import Application
|
from .application import Application
|
||||||
from .compat import RelayConfig, RelayDatabase
|
from .compat import RelayConfig, RelayDatabase
|
||||||
from .database import RELAY_SOFTWARE, get_database
|
from .database import RELAY_SOFTWARE, get_database
|
||||||
from .misc import ACTOR_FORMATS, SOFTWARE, IS_DOCKER, Message
|
from .misc import IS_DOCKER, Message
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from tinysql import Row
|
from tinysql import Row
|
||||||
|
@ -33,6 +33,23 @@ CONFIG_IGNORE = (
|
||||||
'private-key'
|
'private-key'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ACTOR_FORMATS = {
|
||||||
|
'mastodon': 'https://{domain}/actor',
|
||||||
|
'akkoma': 'https://{domain}/relay',
|
||||||
|
'pleroma': 'https://{domain}/relay'
|
||||||
|
}
|
||||||
|
|
||||||
|
SOFTWARE = (
|
||||||
|
'mastodon',
|
||||||
|
'akkoma',
|
||||||
|
'pleroma',
|
||||||
|
'misskey',
|
||||||
|
'friendica',
|
||||||
|
'hubzilla',
|
||||||
|
'firefish',
|
||||||
|
'gotosocial'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_alphanumeric(text: str) -> str:
|
def check_alphanumeric(text: str) -> str:
|
||||||
if not text.isalnum():
|
if not text.isalnum():
|
||||||
|
|
|
@ -36,23 +36,6 @@ NODEINFO_NS = {
|
||||||
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
|
'21': 'http://nodeinfo.diaspora.software/ns/schema/2.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
ACTOR_FORMATS = {
|
|
||||||
'mastodon': 'https://{domain}/actor',
|
|
||||||
'akkoma': 'https://{domain}/relay',
|
|
||||||
'pleroma': 'https://{domain}/relay'
|
|
||||||
}
|
|
||||||
|
|
||||||
SOFTWARE = (
|
|
||||||
'mastodon',
|
|
||||||
'akkoma',
|
|
||||||
'pleroma',
|
|
||||||
'misskey',
|
|
||||||
'friendica',
|
|
||||||
'hubzilla',
|
|
||||||
'firefish',
|
|
||||||
'gotosocial'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def boolean(value: Any) -> bool:
|
def boolean(value: Any) -> bool:
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
|
@ -242,12 +225,6 @@ class Response(AiohttpResponse):
|
||||||
return cls.new(body=body, status=status, ctype=ctype)
|
return cls.new(body=body, status=status, ctype=ctype)
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def new_redir(cls: type[Response], path: str) -> Response:
|
|
||||||
body = f'Redirect to <a href="{path}">{path}</a>'
|
|
||||||
return cls.new(body, 302, {'Location': path})
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def location(self) -> str:
|
def location(self) -> str:
|
||||||
return self.headers.get('Location')
|
return self.headers.get('Location')
|
||||||
|
|
|
@ -2,211 +2,35 @@ from __future__ import annotations
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
from argon2.exceptions import VerifyMismatchError
|
|
||||||
|
|
||||||
from .base import View, register_route
|
from .base import View, register_route
|
||||||
|
|
||||||
from ..misc import ACTOR_FORMATS, Message, Response
|
from ..misc import Response
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from aiohttp.web import Request
|
from aiohttp.web import Request
|
||||||
|
|
||||||
|
|
||||||
UNAUTH_ROUTES = {
|
|
||||||
'/',
|
|
||||||
'/login'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@web.middleware
|
|
||||||
async def handle_frontend_path(request: web.Request, handler: Coroutine) -> Response:
|
|
||||||
if request.path in UNAUTH_ROUTES or request.path.startswith('/admin'):
|
|
||||||
request['token'] = request.cookies.get('user-token')
|
|
||||||
request['user'] = None
|
|
||||||
|
|
||||||
if request['token']:
|
|
||||||
with request.app.database.session(False) as conn:
|
|
||||||
request['user'] = conn.get_user_by_token(request['token'])
|
|
||||||
|
|
||||||
if request['user'] and request.path == '/login':
|
|
||||||
return Response.new('', 302, {'Location': '/'})
|
|
||||||
|
|
||||||
if not request['user'] and request.path.startswith('/admin'):
|
|
||||||
return Response.new('', 302, {'Location': f'/login?redir={request.path}'})
|
|
||||||
|
|
||||||
return await handler(request)
|
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
|
|
||||||
@register_route('/')
|
@register_route('/')
|
||||||
class HomeView(View):
|
class HomeView(View):
|
||||||
async def get(self, request: Request) -> Response:
|
async def get(self, request: Request) -> Response:
|
||||||
with self.database.session() as conn:
|
with self.database.session() as conn:
|
||||||
context = {
|
instances = tuple(conn.execute('SELECT * FROM inboxes').all())
|
||||||
'instances': tuple(conn.execute('SELECT * FROM inboxes').all())
|
|
||||||
}
|
|
||||||
|
|
||||||
data = self.template.render('page/home.haml', self, **context)
|
# text = HOME_TEMPLATE.format(
|
||||||
return Response.new(data, ctype='html')
|
# host = self.config.domain,
|
||||||
|
# note = config['note'],
|
||||||
|
# count = len(inboxes),
|
||||||
|
# targets = '<br>'.join(inbox['domain'] for inbox in inboxes)
|
||||||
|
# )
|
||||||
|
|
||||||
|
data = self.template.render('page/home.haml', instances = instances)
|
||||||
@register_route('/login')
|
|
||||||
class Login(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('page/login.haml', self)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
|
||||||
form = await request.post()
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
if not (user := conn.get_user(form['username'])):
|
|
||||||
params = {
|
|
||||||
'username': form['username'],
|
|
||||||
'error': 'User not found'
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
conn.hasher.verify(user['hash'], form['password'])
|
|
||||||
|
|
||||||
except VerifyMismatchError:
|
|
||||||
params = {
|
|
||||||
'username': form['username'],
|
|
||||||
'error': 'Invalid password'
|
|
||||||
}
|
|
||||||
|
|
||||||
if params:
|
|
||||||
data = self.template.render('page/login.haml', self, **params)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
token = conn.put_token(user['username'])
|
|
||||||
resp = Response.new_redir(request.query.getone('redir', '/'))
|
|
||||||
resp.set_cookie(
|
|
||||||
'user-token',
|
|
||||||
token['code'],
|
|
||||||
max_age = 60 * 60 * 24 * 365,
|
|
||||||
domain = self.config.domain,
|
|
||||||
path = '/',
|
|
||||||
secure = True,
|
|
||||||
httponly = True,
|
|
||||||
samesite = 'Strict'
|
|
||||||
)
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/logout')
|
|
||||||
class Logout(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
conn.del_token(request['token'])
|
|
||||||
|
|
||||||
resp = Response.new_redir('/')
|
|
||||||
resp.del_cookie('user-token', domain = self.config.domain, path = '/')
|
|
||||||
return resp
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin')
|
|
||||||
class Admin(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
return Response.new('', 302, {'Location': '/admin/instances'})
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/instances')
|
|
||||||
class AdminInstances(View):
|
|
||||||
async def get(self,
|
|
||||||
request: Request,
|
|
||||||
error: str | None = None,
|
|
||||||
message: str | None = None) -> Response:
|
|
||||||
|
|
||||||
with self.database.session() as conn:
|
|
||||||
context = {
|
|
||||||
'instances': tuple(conn.execute('SELECT * FROM inboxes').all())
|
|
||||||
}
|
|
||||||
|
|
||||||
if error:
|
|
||||||
context['error'] = error
|
|
||||||
|
|
||||||
if message:
|
|
||||||
context['message'] = message
|
|
||||||
|
|
||||||
data = self.template.render('page/admin-instances.haml', self, **context)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
async def post(self, request: Request) -> Response:
|
|
||||||
data = {key: value for key, value in (await request.post()).items()}
|
|
||||||
|
|
||||||
if not data['actor'] and not data['domain']:
|
|
||||||
return await self.get(request, error = 'Missing actor and/or domain')
|
|
||||||
|
|
||||||
if not data['domain']:
|
|
||||||
data['domain'] = urlparse(data['actor']).netloc
|
|
||||||
|
|
||||||
if not data['software']:
|
|
||||||
nodeinfo = await self.client.fetch_nodeinfo(data['domain'])
|
|
||||||
data['software'] = nodeinfo.sw_name
|
|
||||||
|
|
||||||
if not data['actor'] and data['software'] in ACTOR_FORMATS:
|
|
||||||
data['actor'] = ACTOR_FORMATS[data['software']].format(domain = data['domain'])
|
|
||||||
|
|
||||||
if not data['inbox'] and data['actor']:
|
|
||||||
actor = await self.client.get(data['actor'], sign_headers = True, loads = Message.parse)
|
|
||||||
data['inbox'] = actor.shared_inbox
|
|
||||||
|
|
||||||
with self.database.session(True) as conn:
|
|
||||||
conn.put_inbox(**data)
|
|
||||||
|
|
||||||
return await self.get(request, message = "Added new inbox")
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/instances/delete/{domain}')
|
|
||||||
class AdminInstancesDelete(View):
|
|
||||||
async def get(self, request: Request, domain: str) -> Response:
|
|
||||||
with self.database.session() as conn:
|
|
||||||
if not (conn.get_inbox(domain)):
|
|
||||||
return await AdminInstances(request).get(request, message = 'Instance not found')
|
|
||||||
|
|
||||||
conn.del_inbox(domain)
|
|
||||||
|
|
||||||
return await AdminInstances(request).get(request, message = 'Removed instance')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/whitelist')
|
|
||||||
class AdminWhitelist(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('page/admin-whitelist.haml', self)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/domain_bans')
|
|
||||||
class AdminDomainBans(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('page/admin-domain_bans.haml', self)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/software_bans')
|
|
||||||
class AdminSoftwareBans(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('page/admin-software_bans.haml', self)
|
|
||||||
return Response.new(data, ctype = 'html')
|
|
||||||
|
|
||||||
|
|
||||||
@register_route('/admin/config')
|
|
||||||
class AdminConfig(View):
|
|
||||||
async def get(self, request: Request) -> Response:
|
|
||||||
data = self.template.render('page/admin-config.haml', self)
|
|
||||||
return Response.new(data, ctype='html')
|
return Response.new(data, ctype='html')
|
||||||
|
|
||||||
|
|
||||||
@register_route('/style.css')
|
@register_route('/style.css')
|
||||||
class StyleCss(View):
|
class StyleCss(View):
|
||||||
async def get(self, request: Request) -> Response:
|
async def get(self, request: Request) -> Response:
|
||||||
data = self.template.render('style.css', self, page = request.query.getone('page', ""))
|
data = self.template.render('style.css')
|
||||||
return Response.new(data, ctype = 'css')
|
return Response.new(data, ctype = 'css')
|
||||||
|
|
Loading…
Reference in a new issue