Add more stuff
This commit is contained in:
parent
9151f4cb6d
commit
0b15a44820
|
@ -13,7 +13,20 @@
|
|||
#
|
||||
# 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/>.
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
from ..database import User, AccessToken
|
||||
|
||||
routes = web.RouteTableDef()
|
||||
|
||||
|
||||
@routes.get("/whoami")
|
||||
async def whoami(req: web.Request) -> web.Response:
|
||||
user: User = req["user"]
|
||||
token: AccessToken = req["token"]
|
||||
return web.json_response({
|
||||
"id": user.id,
|
||||
"widget_secret": user.widget_secret,
|
||||
"homeserver_url": user.homeserver_url,
|
||||
"last_seen": int(token.last_seen_date.timestamp() / 60) * 60,
|
||||
})
|
||||
|
|
|
@ -37,7 +37,8 @@ class AccessToken(Base):
|
|||
|
||||
@classmethod
|
||||
async def get(cls, token_id: int) -> Optional['AccessToken']:
|
||||
q = "SELECT user_id, token_hash, last_seen_ip, last_seen_date FROM pack WHERE token_id=$1"
|
||||
q = ("SELECT user_id, token_hash, last_seen_ip, last_seen_date "
|
||||
"FROM access_token WHERE token_id=$1")
|
||||
row: asyncpg.Record = await cls.db.fetchrow(q, token_id)
|
||||
if row is None:
|
||||
return None
|
||||
|
@ -48,7 +49,7 @@ class AccessToken(Base):
|
|||
== datetime.now().replace(second=0, microsecond=0)):
|
||||
# Same IP and last seen on this minute, skip update
|
||||
return
|
||||
q = ("UPDATE access_token SET last_seen_ip=$3, last_seen_date=current_timestamp "
|
||||
q = ("UPDATE access_token SET last_seen_ip=$2, last_seen_date=current_timestamp "
|
||||
"WHERE token_id=$1 RETURNING last_seen_date")
|
||||
self.last_seen_date = await self.db.fetchval(q, self.token_id, ip)
|
||||
self.last_seen_ip = ip
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
// maunium-stickerpicker - A fast and simple Matrix sticker picker widget.
|
||||
// Copyright (C) 2020 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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/>.
|
||||
import { useEffect, useState } from "../../lib/preact/hooks.js"
|
||||
import { html } from "../../lib/htm/preact.js"
|
||||
|
||||
import LoginView from "./LoginView.js"
|
||||
import Spinner from "../Spinner.js"
|
||||
import * as matrix from "./matrix-api.js"
|
||||
import * as sticker from "./sticker-api.js"
|
||||
|
||||
const App = () => {
|
||||
const [loggedIn, setLoggedIn] = useState(Boolean(localStorage.mxAccessToken))
|
||||
const [widgetSecret, setWidgetSecret] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
|
||||
if (!loggedIn) {
|
||||
return html`
|
||||
<${LoginView}
|
||||
onLoggedIn=${() => setLoggedIn(Boolean(localStorage.mxAccessToken))}
|
||||
/>`
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (widgetSecret === null) {
|
||||
setLoading(true)
|
||||
const whoamiReceived = data => {
|
||||
setLoading(false)
|
||||
setWidgetSecret(data.widget_secret)
|
||||
}
|
||||
const reauth = async () => {
|
||||
const openIDToken = await matrix.requestOpenIDToken(
|
||||
localStorage.mxHomeserver, localStorage.mxUserID, localStorage.mxAccessToken)
|
||||
const integrationData = await matrix.requestIntegrationToken(openIDToken)
|
||||
localStorage.stickerSetupAccessToken = integrationData.token
|
||||
return await sticker.whoami()
|
||||
}
|
||||
const whoamiErrored = err => {
|
||||
console.error("Setup API whoami returned", err)
|
||||
if (err.code === "NET.MAUNIUM_TOKEN_EXPIRED" || err.code === "M_UNKNOWN_TOKEN") {
|
||||
return reauth().then(whoamiReceived)
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
sticker.whoami().then(whoamiReceived, whoamiErrored).catch(err => {
|
||||
setLoading(false)
|
||||
setError(err.message)
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return html`<${Spinner} size=80 green />`
|
||||
}
|
||||
|
||||
return html`${widgetSecret}`
|
||||
}
|
||||
|
||||
export default App
|
|
@ -16,13 +16,7 @@
|
|||
import { useEffect, useLayoutEffect, useRef, useState } from "../../lib/preact/hooks.js"
|
||||
import { html } from "../../lib/htm/preact.js"
|
||||
|
||||
import {
|
||||
getLoginFlows,
|
||||
loginMatrix,
|
||||
requestIntegrationToken,
|
||||
requestOpenIDToken,
|
||||
resolveWellKnown,
|
||||
} from "./matrix-api.js"
|
||||
import * as matrix from "./matrix-api.js"
|
||||
import Button from "../Button.js"
|
||||
import Spinner from "../Spinner.js"
|
||||
|
||||
|
@ -87,11 +81,11 @@ const LoginView = ({ onLoggedIn }) => {
|
|||
previousServerValue.current = server
|
||||
setSupportedFlows(null)
|
||||
setError(null)
|
||||
resolveWellKnown(server).then(url => {
|
||||
matrix.resolveWellKnown(server).then(url => {
|
||||
setServerURL(url)
|
||||
localStorage.mxServerName = server
|
||||
localStorage.mxHomeserver = url
|
||||
return getLoginFlows(url)
|
||||
return matrix.getLoginFlows(url)
|
||||
}).then(flows => {
|
||||
setSupportedFlows(flows)
|
||||
}).catch(err => {
|
||||
|
@ -135,15 +129,13 @@ const LoginView = ({ onLoggedIn }) => {
|
|||
}
|
||||
try {
|
||||
const actualServerURL = serverURLOverride || serverURL
|
||||
const [accessToken, userID, realURL] = await loginMatrix(actualServerURL, authInfo)
|
||||
console.log(userID, realURL)
|
||||
const openIDToken = await requestOpenIDToken(realURL, userID, accessToken)
|
||||
console.log(openIDToken)
|
||||
const integrationData = await requestIntegrationToken(openIDToken)
|
||||
console.log(integrationData)
|
||||
const [accessToken, userID, realURL] = await matrix.login(actualServerURL, authInfo)
|
||||
const openIDToken = await matrix.requestOpenIDToken(realURL, userID, accessToken)
|
||||
const integrationData = await matrix.requestIntegrationToken(openIDToken)
|
||||
localStorage.mxHomeserver = realURL
|
||||
localStorage.mxAccessToken = accessToken
|
||||
localStorage.mxUserID = userID
|
||||
localStorage.accessToken = integrationData.token
|
||||
localStorage.stickerSetupAccessToken = integrationData.token
|
||||
onLoggedIn()
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import { html, render } from "../../lib/htm/preact.js"
|
||||
|
||||
import LoginView from "./LoginView.js"
|
||||
import App from "./App.js"
|
||||
|
||||
render(html`<${LoginView} onLoggedIn=${() => console.log("Logged in")}/>`, document.body)
|
||||
render(html`<${App} />`, document.body)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
//
|
||||
// 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/>.
|
||||
|
||||
import { tryFetch, integrationPrefix } from "./tryGet.js"
|
||||
|
||||
export const resolveWellKnown = async (server) => {
|
||||
|
@ -41,7 +40,7 @@ export const getLoginFlows = async (address) => {
|
|||
return flows
|
||||
}
|
||||
|
||||
export const loginMatrix = async (address, authInfo) => {
|
||||
export const login = async (address, authInfo) => {
|
||||
const data = await tryFetch(`${address}/_matrix/client/r0/login`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
|
@ -67,6 +66,17 @@ export const loginMatrix = async (address, authInfo) => {
|
|||
return [data.access_token, data.user_id, address]
|
||||
}
|
||||
|
||||
export const whoami = (address, accessToken) => tryFetch(
|
||||
`${address}/_matrix/client/r0/account/whoami`,
|
||||
{
|
||||
headers: { Authorization: `Bearer ${accessToken}` },
|
||||
},
|
||||
{
|
||||
service: address,
|
||||
requestType: "whoami",
|
||||
},
|
||||
)
|
||||
|
||||
export const requestOpenIDToken = (address, userID, accessToken) => tryFetch(
|
||||
`${address}/_matrix/client/r0/user/${userID}/openid/request_token`,
|
||||
{
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
// maunium-stickerpicker - A fast and simple Matrix sticker picker widget.
|
||||
// Copyright (C) 2020 Tulir Asokan
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
import { tryFetch as tryFetchDefault, setupPrefix } from "./tryGet.js"
|
||||
|
||||
const service = "setup API"
|
||||
|
||||
const tryFetch = (url, options, reqInfo) => {
|
||||
if (!options.headers?.Authorization) {
|
||||
if (!options.headers) {
|
||||
options.headers = {}
|
||||
}
|
||||
options.headers.Authorization = `Bearer ${localStorage.stickerSetupAccessToken}`
|
||||
}
|
||||
return tryFetchDefault(url, options, reqInfo)
|
||||
}
|
||||
|
||||
export const whoami = () => tryFetch(
|
||||
`${setupPrefix}/whoami`,
|
||||
{}, { service, requestType: "whoami" },
|
||||
)
|
|
@ -13,8 +13,8 @@
|
|||
//
|
||||
// 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/>.
|
||||
|
||||
export const integrationPrefix = "../_matrix/integrations/v1"
|
||||
export const setupPrefix = "api"
|
||||
|
||||
export const queryToURL = (url, query) => {
|
||||
if (!Array.isArray(query)) {
|
||||
|
@ -26,15 +26,19 @@ export const queryToURL = (url, query) => {
|
|||
return url
|
||||
}
|
||||
|
||||
class MatrixError extends Error {
|
||||
constructor(data, status) {
|
||||
super(data.error)
|
||||
this.code = data.errcode
|
||||
this.httpStatus = status
|
||||
}
|
||||
}
|
||||
|
||||
export const tryFetch = async (url, options, reqInfo) => {
|
||||
if (options.query) {
|
||||
url = queryToURL(url, options.query)
|
||||
delete options.query
|
||||
}
|
||||
options.headers = {
|
||||
Authorization: `Bearer ${localStorage.accessToken}`,
|
||||
...options.headers,
|
||||
}
|
||||
const reqName = `${reqInfo.service} ${reqInfo.requestType}`
|
||||
let resp
|
||||
try {
|
||||
|
@ -59,7 +63,9 @@ export const tryFetch = async (url, options, reqInfo) => {
|
|||
console.error(reqName, "request JSON parse failed:", err)
|
||||
throw new Error(`Invalid response from ${reqInfo.service}`)
|
||||
}
|
||||
if (resp.status >= 400) {
|
||||
if (data.error && data.errcode) {
|
||||
throw new MatrixError(data, resp.status)
|
||||
} else if (resp.status >= 400) {
|
||||
console.error("Unexpected", reqName, "request status:", resp.status, data)
|
||||
throw new Error(data.error || data.message || `Invalid response from ${reqInfo.service}`)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue