Proxy avatar requests through server and improve css
This commit is contained in:
parent
2aac4fbee1
commit
53d2264351
@ -13,6 +13,7 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
from typing import Optional
|
||||||
from time import time
|
from time import time
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -39,13 +40,31 @@ def create_token(user: UserID) -> str:
|
|||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
@routes.post("/auth/ping")
|
def get_token(request: web.Request) -> str:
|
||||||
async def ping(request: web.Request) -> web.Response:
|
|
||||||
token = request.headers.get("Authorization", "")
|
token = request.headers.get("Authorization", "")
|
||||||
if not token or not token.startswith("Bearer "):
|
if not token or not token.startswith("Bearer "):
|
||||||
|
token = request.query.get("access_token", None)
|
||||||
|
else:
|
||||||
|
token = token[len("Bearer "):]
|
||||||
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def check_token(request: web.Request) -> Optional[web.Response]:
|
||||||
|
token = get_token(request)
|
||||||
|
if not token:
|
||||||
|
return resp.no_token
|
||||||
|
elif not is_valid_token(token):
|
||||||
|
return resp.invalid_token
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/auth/ping")
|
||||||
|
async def ping(request: web.Request) -> web.Response:
|
||||||
|
token = get_token(request)
|
||||||
|
if not token:
|
||||||
return resp.no_token
|
return resp.no_token
|
||||||
|
|
||||||
data = verify_token(get_config()["server.unshared_secret"], token[len("Bearer "):])
|
data = verify_token(get_config()["server.unshared_secret"], token)
|
||||||
if not data:
|
if not data:
|
||||||
return resp.invalid_token
|
return resp.invalid_token
|
||||||
user = data.get("user_id", None)
|
user = data.get("user_id", None)
|
||||||
|
@ -83,6 +83,8 @@ async def _update_client(client: Client, data: dict) -> web.Response:
|
|||||||
return resp.bad_client_access_token
|
return resp.bad_client_access_token
|
||||||
except MatrixRequestError:
|
except MatrixRequestError:
|
||||||
return resp.bad_client_access_details
|
return resp.bad_client_access_details
|
||||||
|
except MatrixConnectionError:
|
||||||
|
return resp.bad_client_connection_details
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return resp.mxid_mismatch(str(e)[len("MXID mismatch: "):])
|
return resp.mxid_mismatch(str(e)[len("MXID mismatch: "):])
|
||||||
await client.update_avatar_url(data.get("avatar_url", None))
|
await client.update_avatar_url(data.get("avatar_url", None))
|
||||||
@ -142,3 +144,14 @@ async def upload_avatar(request: web.Request) -> web.Response:
|
|||||||
"content_uri": await client.client.upload_media(
|
"content_uri": await client.client.upload_media(
|
||||||
content, request.headers.get("Content-Type", None)),
|
content, request.headers.get("Content-Type", None)),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/client/{id}/avatar")
|
||||||
|
async def download_avatar(request: web.Request) -> web.Response:
|
||||||
|
user_id = request.match_info.get("id", None)
|
||||||
|
client = Client.get(user_id, None)
|
||||||
|
if not client:
|
||||||
|
return resp.client_not_found
|
||||||
|
if not client.avatar_url or client.avatar_url == "disable":
|
||||||
|
return web.Response()
|
||||||
|
return web.Response(body=await client.client.download_media(client.avatar_url))
|
||||||
|
@ -19,7 +19,7 @@ import logging
|
|||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
|
|
||||||
from .responses import resp
|
from .responses import resp
|
||||||
from .auth import is_valid_token
|
from .auth import check_token
|
||||||
|
|
||||||
Handler = Callable[[web.Request], Awaitable[web.Response]]
|
Handler = Callable[[web.Request], Awaitable[web.Response]]
|
||||||
|
|
||||||
@ -28,12 +28,7 @@ Handler = Callable[[web.Request], Awaitable[web.Response]]
|
|||||||
async def auth(request: web.Request, handler: Handler) -> web.Response:
|
async def auth(request: web.Request, handler: Handler) -> web.Response:
|
||||||
if "/auth/" in request.path:
|
if "/auth/" in request.path:
|
||||||
return await handler(request)
|
return await handler(request)
|
||||||
token = request.headers.get("Authorization", "")
|
return check_token(request) or await handler(request)
|
||||||
if not token or not token.startswith("Bearer "):
|
|
||||||
return resp.no_token
|
|
||||||
if not is_valid_token(token[len("Bearer "):]):
|
|
||||||
return resp.invalid_token
|
|
||||||
return await handler(request)
|
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger("maubot.server")
|
log = logging.getLogger("maubot.server")
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
const BASE_PATH = "/_matrix/maubot/v1"
|
export const BASE_PATH = "/_matrix/maubot/v1"
|
||||||
|
|
||||||
export async function login(username, password) {
|
export async function login(username, password) {
|
||||||
const resp = await fetch(`${BASE_PATH}/auth/login`, {
|
const resp = await fetch(`${BASE_PATH}/auth/login`, {
|
||||||
@ -105,6 +105,10 @@ export async function uploadAvatar(id, data, mime) {
|
|||||||
return await resp.json()
|
return await resp.json()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAvatarURL(id) {
|
||||||
|
return `${BASE_PATH}/client/${id}/avatar?access_token=${localStorage.accessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
export async function putClient(client) {
|
export async function putClient(client) {
|
||||||
const resp = await fetch(`${BASE_PATH}/client/${client.id}`, {
|
const resp = await fetch(`${BASE_PATH}/client/${client.id}`, {
|
||||||
headers: getHeaders(),
|
headers: getHeaders(),
|
||||||
@ -128,8 +132,9 @@ export async function deleteClient(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
BASE_PATH,
|
||||||
login, ping,
|
login, ping,
|
||||||
getInstances, getInstance,
|
getInstances, getInstance,
|
||||||
getPlugins, getPlugin, uploadPlugin,
|
getPlugins, getPlugin, uploadPlugin,
|
||||||
getClients, getClient, uploadAvatar, putClient, deleteClient,
|
getClients, getClient, uploadAvatar, getAvatarURL, putClient, deleteClient,
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,12 @@ class Switch extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleKeyboard = evt => (evt.key === " " || evt.key === "Enter") && this.toggle()
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="switch" data-active={this.state.active} onClick={this.toggle}>
|
<div className="switch" data-active={this.state.active} onClick={this.toggle}
|
||||||
|
tabIndex="0" onKeyPress={this.toggleKeyboard}>
|
||||||
<div className="box">
|
<div className="box">
|
||||||
<span className="text">
|
<span className="text">
|
||||||
<span className="on">{this.props.onText || "On"}</span>
|
<span className="on">{this.props.onText || "On"}</span>
|
||||||
|
@ -44,7 +44,7 @@ class Main extends Component {
|
|||||||
localStorage.username = username
|
localStorage.username = username
|
||||||
this.setState({ authed: true })
|
this.setState({ authed: true })
|
||||||
} else {
|
} else {
|
||||||
localStorage.accessToken = undefined
|
delete localStorage.accessToken
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
|
@ -21,14 +21,6 @@ import { PrefTable, PrefSwitch, PrefInput } from "../../components/PreferenceTab
|
|||||||
import Spinner from "../../components/Spinner"
|
import Spinner from "../../components/Spinner"
|
||||||
import api from "../../api"
|
import api from "../../api"
|
||||||
|
|
||||||
function getAvatarURL(client) {
|
|
||||||
if (!client.avatar_url) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
const id = client.avatar_url.substr("mxc://".length)
|
|
||||||
return `${client.homeserver}/_matrix/media/r0/download/${id}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const ClientListEntry = ({ client }) => {
|
const ClientListEntry = ({ client }) => {
|
||||||
const classes = ["client", "entry"]
|
const classes = ["client", "entry"]
|
||||||
if (!client.enabled) {
|
if (!client.enabled) {
|
||||||
@ -38,7 +30,7 @@ const ClientListEntry = ({ client }) => {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<NavLink className={classes.join(" ")} to={`/client/${client.id}`}>
|
<NavLink className={classes.join(" ")} to={`/client/${client.id}`}>
|
||||||
<img className="avatar" src={getAvatarURL(client)} alt={client.id.substr(1, 1)}/>
|
<img className="avatar" src={api.getAvatarURL(client.id)} alt=""/>
|
||||||
<span className="displayname">{client.displayname || client.id}</span>
|
<span className="displayname">{client.displayname || client.id}</span>
|
||||||
<ChevronRight/>
|
<ChevronRight/>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@ -153,8 +145,8 @@ class Client extends Component {
|
|||||||
startOrStop = async () => {
|
startOrStop = async () => {
|
||||||
this.setState({ startingOrStopping: true })
|
this.setState({ startingOrStopping: true })
|
||||||
const resp = await api.putClient({
|
const resp = await api.putClient({
|
||||||
id: this.state.id,
|
id: this.props.client.id,
|
||||||
started: !this.state.started,
|
started: !this.props.client.started,
|
||||||
})
|
})
|
||||||
if (resp.id) {
|
if (resp.id) {
|
||||||
this.props.onChange(resp)
|
this.props.onChange(resp)
|
||||||
@ -172,11 +164,11 @@ class Client extends Component {
|
|||||||
return !Boolean(this.props.client)
|
return !Boolean(this.props.client)
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSidebar = () => (
|
renderSidebar = () => !this.isNew && (
|
||||||
<div className="sidebar">
|
<div className="sidebar">
|
||||||
<div className={`avatar-container ${this.state.avatar_url ? "" : "no-avatar"}
|
<div className={`avatar-container ${this.state.avatar_url ? "" : "no-avatar"}
|
||||||
${this.state.uploadingAvatar ? "uploading" : ""}`}>
|
${this.state.uploadingAvatar ? "uploading" : ""}`}>
|
||||||
<img className="avatar" src={getAvatarURL(this.state)} alt="Avatar"/>
|
<img className="avatar" src={api.getAvatarURL(this.state.id)} alt="Avatar"/>
|
||||||
<UploadButton className="upload"/>
|
<UploadButton className="upload"/>
|
||||||
<input className="file-selector" type="file" accept="image/png, image/jpeg"
|
<input className="file-selector" type="file" accept="image/png, image/jpeg"
|
||||||
onChange={this.avatarUpload} disabled={this.state.uploadingAvatar}
|
onChange={this.avatarUpload} disabled={this.state.uploadingAvatar}
|
||||||
@ -230,20 +222,7 @@ class Client extends Component {
|
|||||||
</PrefTable>
|
</PrefTable>
|
||||||
)
|
)
|
||||||
|
|
||||||
renderInstances = () => (
|
renderPrefButtons = () => <>
|
||||||
<div className="instances">
|
|
||||||
<h3>Instances</h3>
|
|
||||||
{this.state.instances.map(instance => (
|
|
||||||
<Link className="instance" key={instance.id} to={`/instance/${instance.id}`}>
|
|
||||||
{instance.id}
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
renderInfoContainer = () => (
|
|
||||||
<div className="info">
|
|
||||||
{this.renderPreferences()}
|
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
{!this.isNew && (
|
{!this.isNew && (
|
||||||
<button className="delete" onClick={this.delete} disabled={this.loading}>
|
<button className="delete" onClick={this.delete} disabled={this.loading}>
|
||||||
@ -255,14 +234,27 @@ class Client extends Component {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="error">{this.state.error}</div>
|
<div className="error">{this.state.error}</div>
|
||||||
{this.renderInstances()}
|
</>
|
||||||
|
|
||||||
|
renderInstances = () => !this.isNew && (
|
||||||
|
<div className="instances">
|
||||||
|
<h3>{this.state.instances.length > 0 ? "Instances" : "No instances :("}</h3>
|
||||||
|
{this.state.instances.map(instance => (
|
||||||
|
<Link className="instance" key={instance.id} to={`/instance/${instance.id}`}>
|
||||||
|
{instance.id}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return <div className="client">
|
return <div className="client">
|
||||||
{!this.isNew && this.renderSidebar()}
|
{this.renderSidebar()}
|
||||||
{this.renderInfoContainer()}
|
<div className="info">
|
||||||
|
{this.renderPreferences()}
|
||||||
|
{this.renderPrefButtons()}
|
||||||
|
{this.renderInstances()}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,34 +34,6 @@ body
|
|||||||
right: 0
|
right: 0
|
||||||
background-color: $background-dark
|
background-color: $background-dark
|
||||||
|
|
||||||
> *
|
|
||||||
background-color: $background
|
|
||||||
|
|
||||||
.maubot-loading
|
.maubot-loading
|
||||||
margin-top: 10rem
|
margin-top: 10rem
|
||||||
width: 10rem
|
width: 10rem
|
||||||
|
|
||||||
//.lindeb
|
|
||||||
> header
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
height: $header-height
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
|
|
||||||
> main
|
|
||||||
position: absolute
|
|
||||||
top: $header-height
|
|
||||||
bottom: 0
|
|
||||||
left: 0
|
|
||||||
right: 0
|
|
||||||
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
> .lindeb-content
|
|
||||||
text-align: left
|
|
||||||
display: inline-block
|
|
||||||
width: 100%
|
|
||||||
max-width: $max-width
|
|
||||||
box-sizing: border-box
|
|
||||||
padding: 0 1rem
|
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
background-color: $background
|
background-color: $background
|
||||||
border: none
|
border: none
|
||||||
border-radius: .25rem
|
border-radius: .25rem
|
||||||
color: $inverted-text-color
|
color: $text-color
|
||||||
box-sizing: border-box
|
box-sizing: border-box
|
||||||
font-size: 1rem
|
font-size: 1rem
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
transition: .5s
|
transition: .5s
|
||||||
text-align: center
|
text-align: center
|
||||||
|
|
||||||
color: $inverted-text-color
|
color: $text-color
|
||||||
border-radius: .15rem 0 0 .15rem
|
border-radius: .15rem 0 0 .15rem
|
||||||
background-color: $primary
|
background-color: $primary
|
||||||
|
|
||||||
@ -50,7 +50,7 @@
|
|||||||
text-align: center
|
text-align: center
|
||||||
vertical-align: middle
|
vertical-align: middle
|
||||||
|
|
||||||
color: $inverted-text-color
|
color: $text-color
|
||||||
font-size: 1rem
|
font-size: 1rem
|
||||||
|
|
||||||
user-select: none
|
user-select: none
|
||||||
@ -67,14 +67,9 @@
|
|||||||
transform: translateX(100%)
|
transform: translateX(100%)
|
||||||
|
|
||||||
border-radius: 0 .15rem .15rem 0
|
border-radius: 0 .15rem .15rem 0
|
||||||
background-color: $primary
|
|
||||||
|
|
||||||
.on
|
.on
|
||||||
display: inline
|
display: inline
|
||||||
|
|
||||||
.off
|
.off
|
||||||
display: none
|
display: none
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,5 +32,4 @@
|
|||||||
border: 1px solid $primary
|
border: 1px solid $primary
|
||||||
|
|
||||||
&:hover
|
&:hover
|
||||||
color: $inverted-text-color
|
|
||||||
background-color: $primary
|
background-color: $primary
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
max-width: 60rem
|
max-width: 60rem
|
||||||
margin: auto
|
margin: auto
|
||||||
box-shadow: 0 .5rem .5rem rgba(0, 0, 0, 0.5)
|
box-shadow: 0 .5rem .5rem rgba(0, 0, 0, 0.5)
|
||||||
|
background-color: $background
|
||||||
|
|
||||||
> a.title
|
> a.title
|
||||||
grid-area: title
|
grid-area: title
|
||||||
|
@ -29,9 +29,8 @@
|
|||||||
|
|
||||||
div.title
|
div.title
|
||||||
h2
|
h2
|
||||||
margin: 0
|
|
||||||
display: inline-block
|
display: inline-block
|
||||||
|
margin: 0 0 .25rem 0
|
||||||
font-size: 1.25rem
|
font-size: 1.25rem
|
||||||
|
|
||||||
a
|
a
|
||||||
|
@ -13,16 +13,25 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Affero General Public License
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
from aiohttp import web
|
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
from aiohttp import web
|
||||||
|
from aiohttp.abc import AbstractAccessLogger
|
||||||
|
|
||||||
from mautrix.api import PathBuilder, Method
|
from mautrix.api import PathBuilder, Method
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .__meta__ import __version__
|
from .__meta__ import __version__
|
||||||
|
|
||||||
|
|
||||||
|
class AccessLogger(AbstractAccessLogger):
|
||||||
|
def log(self, request: web.Request, response: web.Response, time: int):
|
||||||
|
self.logger.info(f'{request.remote} "{request.method} {request.path} '
|
||||||
|
f'{response.status} {response.body_length} '
|
||||||
|
f'in {round(time, 4)}s"')
|
||||||
|
|
||||||
|
|
||||||
class MaubotServer:
|
class MaubotServer:
|
||||||
log: logging.Logger = logging.getLogger("maubot.server")
|
log: logging.Logger = logging.getLogger("maubot.server")
|
||||||
|
|
||||||
@ -39,7 +48,7 @@ class MaubotServer:
|
|||||||
as_path = PathBuilder(config["server.appservice_base_path"])
|
as_path = PathBuilder(config["server.appservice_base_path"])
|
||||||
self.add_route(Method.PUT, as_path.transactions, self.handle_transaction)
|
self.add_route(Method.PUT, as_path.transactions, self.handle_transaction)
|
||||||
|
|
||||||
self.runner = web.AppRunner(self.app)
|
self.runner = web.AppRunner(self.app, access_log_class=AccessLogger)
|
||||||
|
|
||||||
def add_route(self, method: Method, path: PathBuilder, handler) -> None:
|
def add_route(self, method: Method, path: PathBuilder, handler) -> None:
|
||||||
self.app.router.add_route(method.value, str(path), handler)
|
self.app.router.add_route(method.value, str(path), handler)
|
||||||
|
Loading…
Reference in New Issue
Block a user