Frontend created and rewritten a few times, with some backend fixes #1
|
@ -3,9 +3,10 @@
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #000;
|
--primary-color: #000;
|
||||||
--primary-background: #ddd;
|
--primary-background: #ddd;
|
||||||
--header-background: #246;
|
--header-background: var(--soft-color);
|
||||||
--header-color: #ccc;
|
--header-color: #ccc;
|
||||||
--primary-color: #000;
|
--primary-color: #000;
|
||||||
|
--soft-color: #146;
|
||||||
--accent-color: #f80;
|
--accent-color: #f80;
|
||||||
--transition-time: 0.2s;
|
--transition-time: 0.2s;
|
||||||
/* The following are overridden by responsive layouts */
|
/* The following are overridden by responsive layouts */
|
||||||
|
@ -16,7 +17,7 @@
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #ddd;
|
--primary-color: #ddd;
|
||||||
--primary-background: #003;
|
--primary-background: var(--soft-color);
|
||||||
--header-background: #000;
|
--header-background: #000;
|
||||||
--header-color: #ccc;
|
--header-color: #ccc;
|
||||||
}
|
}
|
||||||
|
@ -235,3 +236,9 @@ main {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.error-message {
|
||||||
|
padding: .5em;
|
||||||
|
font-weight: bold;
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
|
@ -129,8 +129,8 @@
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<tr class="summary">
|
<tr class="summary" v-if="props.documents.length > 1">
|
||||||
<td colspan="3" class="right">{{props.documents.length}} items shown:</td>
|
<td colspan="3" class="right">{{props.documents.length}} items</td>
|
||||||
<td class="size right">{{ formatSize(props.documents.reduce((a, b) => a + b.size, 0)) }}</td>
|
<td class="size right">{{ formatSize(props.documents.reduce((a, b) => a + b.size, 0)) }}</td>
|
||||||
<td class="menu"></td>
|
<td class="menu"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -140,7 +140,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watchEffect, onBeforeUpdate } from 'vue'
|
import { ref, computed, watchEffect } from 'vue'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useDocumentStore } from '@/stores/documents'
|
||||||
import type { Document } from '@/repositories/Document'
|
import type { Document } from '@/repositories/Document'
|
||||||
import FileRenameInput from './FileRenameInput.vue'
|
import FileRenameInput from './FileRenameInput.vue'
|
||||||
|
@ -510,4 +510,7 @@ tbody .selection input {
|
||||||
.loc {
|
.loc {
|
||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
.summary {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<nav class="headermain">
|
<nav class="headermain">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
<template v-if="documentStore.error">
|
||||||
|
<div class="error-message" @click="documentStore.error = ''">{{ documentStore.error }}</div>
|
||||||
|
<div class="smallgap"></div>
|
||||||
|
</template>
|
||||||
<UploadButton />
|
<UploadButton />
|
||||||
<SvgButton
|
<SvgButton
|
||||||
name="create-folder"
|
name="create-folder"
|
||||||
|
@ -23,17 +27,6 @@
|
||||||
<SvgButton name="cog" @click="settingsMenu" />
|
<SvgButton name="cog" @click="settingsMenu" />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<context-menu v-model:show="showMenu">
|
|
||||||
<context-menu-item label="Simple item" @click="onMenuClick(1)" />
|
|
||||||
<context-menu-sperator /><!--use this to add sperator-->
|
|
||||||
<context-menu-group label="Menu with child">
|
|
||||||
<context-menu-item label="Item1" @click="onMenuClick(2)" />
|
|
||||||
<context-menu-item label="Item2" @click="onMenuClick(3)" />
|
|
||||||
<context-menu-group label="Child with v-for 50">
|
|
||||||
<context-menu-item v-for="index of 50" :key="index" :label="'Item3-'+index" @click="onLoopMenuClick(index)" />
|
|
||||||
</context-menu-group>
|
|
||||||
</context-menu-group>
|
|
||||||
</context-menu>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -64,12 +57,16 @@ const toggleSearchInput = () => {
|
||||||
|
|
||||||
const settingsMenu = (e: Event) => {
|
const settingsMenu = (e: Event) => {
|
||||||
// show the context menu
|
// show the context menu
|
||||||
|
const items = []
|
||||||
|
if (documentStore.user.isLoggedIn) {
|
||||||
|
items.push({ label: `Logout ${documentStore.user.username ?? ''}`, onClick: () => documentStore.logout() })
|
||||||
|
} else {
|
||||||
|
items.push({ label: 'Login', onClick: () => documentStore.loginDialog() })
|
||||||
|
}
|
||||||
ContextMenu.showContextMenu({
|
ContextMenu.showContextMenu({
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
x: e.target.getBoundingClientRect().right, y: e.target.getBoundingClientRect().bottom,
|
x: e.target.getBoundingClientRect().right, y: e.target.getBoundingClientRect().bottom,
|
||||||
items: [
|
items,
|
||||||
{ label: "Logout", onClick: () => { documentStore.logout() } },
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ModalDialog v-if="store.user.isOpenLoginModal" title="Authentication required">
|
<ModalDialog v-if="store.user.isOpenLoginModal" title="Authentication required" @blur="store.user.isOpenLoginModal = false">
|
||||||
<form @submit.prevent="login">
|
<form @submit.prevent="login">
|
||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
|
@ -7,6 +7,8 @@
|
||||||
id="username"
|
id="username"
|
||||||
name="username"
|
name="username"
|
||||||
autocomplete="username"
|
autocomplete="username"
|
||||||
|
spellcheck="false"
|
||||||
|
autocorrect="off"
|
||||||
required
|
required
|
||||||
v-model="loginForm.username"
|
v-model="loginForm.username"
|
||||||
/>
|
/>
|
||||||
|
@ -16,12 +18,14 @@
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
autocomplete="current-password"
|
autocomplete="current-password"
|
||||||
|
spellcheck="false"
|
||||||
|
autocorrect="off"
|
||||||
required
|
required
|
||||||
v-model="loginForm.password"
|
v-model="loginForm.password"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h3 v-if="loginForm.error.length > 0" class="error-text">
|
<h3 class="error-text">
|
||||||
{{ loginForm.error }}
|
{{ loginForm.error || '\u00A0' }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="dialog-buttons">
|
<div class="dialog-buttons">
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
|
@ -33,21 +37,13 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { reactive, ref } from 'vue'
|
import { reactive, ref } from 'vue'
|
||||||
import { loginUser, logoutUser } from '@/repositories/User'
|
import { loginUser } from '@/repositories/User'
|
||||||
import type { ISimpleError } from '@/repositories/Client'
|
import type { ISimpleError } from '@/repositories/Client'
|
||||||
import { useDocumentStore } from '@/stores/documents'
|
import { useDocumentStore } from '@/stores/documents'
|
||||||
|
|
||||||
const confirmLoading = ref<boolean>(false)
|
const confirmLoading = ref<boolean>(false)
|
||||||
const store = useDocumentStore()
|
const store = useDocumentStore()
|
||||||
|
|
||||||
const logout = async () => {
|
|
||||||
try {
|
|
||||||
await logoutUser()
|
|
||||||
} finally {
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginForm = reactive({
|
const loginForm = reactive({
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
|
@ -59,13 +55,10 @@ const login = async () => {
|
||||||
loginForm.error = ''
|
loginForm.error = ''
|
||||||
confirmLoading.value = true
|
confirmLoading.value = true
|
||||||
const msg = await loginUser(loginForm.username, loginForm.password)
|
const msg = await loginUser(loginForm.username, loginForm.password)
|
||||||
console.log('Logged in', msg)
|
store.login(msg.data.username, !!msg.data.privileged)
|
||||||
store.login(msg.username, !!msg.privileged)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const httpError = error as ISimpleError
|
const httpError = error as ISimpleError
|
||||||
if (httpError.name) {
|
loginForm.error = httpError.message || '🛑 Unknown error'
|
||||||
loginForm.error = httpError.message
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
confirmLoading.value = false
|
confirmLoading.value = false
|
||||||
}
|
}
|
||||||
|
@ -87,18 +80,22 @@ const login = async () => {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.button-login {
|
.button-login {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--soft-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
border: 0;
|
border: 0;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
padding: .5rem;
|
padding: .5rem 2rem;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
background-color: var(--accent-color);
|
transition: all var(--transition-time) linear;
|
||||||
color: var(--primary-color);
|
|
||||||
}
|
}
|
||||||
.ant-btn-primary:not(:disabled):hover {
|
.button-login:hover, .button-login:focus {
|
||||||
background-color: var(--blue-color);
|
background: var(--accent-color);
|
||||||
|
box-shadow: 0 0 .3rem #000;
|
||||||
}
|
}
|
||||||
.error-text {
|
.error-text {
|
||||||
color: var(--red-color);
|
color: var(--red-color);
|
||||||
|
height: 1em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -23,15 +23,18 @@ const props = withDefaults(
|
||||||
title: ''
|
title: ''
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
const show = () => {
|
||||||
onMounted(() => {
|
|
||||||
dialog.value!.showModal()
|
dialog.value!.showModal()
|
||||||
|
}
|
||||||
|
defineExpose({ show })
|
||||||
|
onMounted(() => {
|
||||||
|
show()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Style for the background */
|
/* Style for the background */
|
||||||
body:has(dialog[open])::before {
|
dialog::backdrop {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -40,7 +43,7 @@ body:has(dialog[open])::before {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background: #0008;
|
background: #0008;
|
||||||
backdrop-filter: blur(0.2em);
|
backdrop-filter: blur(0.4em);
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +53,7 @@ dialog[open] {
|
||||||
color: black;
|
color: black;
|
||||||
display: block;
|
display: block;
|
||||||
border: none;
|
border: none;
|
||||||
|
font-size: 1.2rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
box-shadow: 0.2rem 0.2rem 1rem #000;
|
box-shadow: 0.2rem 0.2rem 1rem #000;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
@ -58,11 +62,13 @@ dialog[open] {
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1001;
|
z-index: 1001;
|
||||||
}
|
}
|
||||||
|
input {
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
dialog[open] > h1 {
|
dialog[open] > h1 {
|
||||||
background: var(--accent-color);
|
background: var(--soft-color);
|
||||||
color: black;
|
color: #fff;
|
||||||
font-size: 1rem;
|
font-size: 1.2rem;
|
||||||
margin: -1rem -1rem 0 -1rem;
|
margin: -1rem -1rem 0 -1rem;
|
||||||
padding: 0.5rem 1rem 0.5rem 1rem;
|
padding: 0.5rem 1rem 0.5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ class ClientClass {
|
||||||
try {
|
try {
|
||||||
msg = await res.json()
|
msg = await res.json()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new SimpleError(res.status, `HTTP ${res.status} ${res.statusText}`)
|
throw new SimpleError(res.status, `🛑 ${res.status} ${res.statusText}`)
|
||||||
}
|
}
|
||||||
if ('error' in msg) throw new SimpleError(msg.error.code, msg.error.message)
|
if ('error' in msg) throw new SimpleError(msg.error.code, msg.error.message)
|
||||||
return msg
|
return msg
|
||||||
|
|
|
@ -15,41 +15,21 @@ export const connect = (path: string, handlers: Partial<Record<keyof WebSocketEv
|
||||||
return webSocket
|
return webSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
export const watchConnect = async () => {
|
export const watchConnect = () => {
|
||||||
wsWatch = connect(watchUrl, {
|
if (watchTimeout !== null) {
|
||||||
open() { console.log("Connected to", watchUrl)},
|
clearTimeout(watchTimeout)
|
||||||
message: handleWatchMessage,
|
watchTimeout = null
|
||||||
close: watchReconnect,
|
|
||||||
})
|
|
||||||
await wsWatch
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const watchDisconnect = () => {
|
|
||||||
if (!wsWatch) return
|
|
||||||
wsWatch.close()
|
|
||||||
wsWatch = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const watchReconnect = (event: MessageEvent) => {
|
|
||||||
const store = useDocumentStore()
|
const store = useDocumentStore()
|
||||||
if (store.connected) {
|
if (store.error !== 'Reconnecting...') store.error = 'Connecting...'
|
||||||
console.warn("Disconnected from server", event)
|
console.log(store.error)
|
||||||
store.connected = false
|
|
||||||
}
|
|
||||||
reconnectDuration = Math.min(5000, reconnectDuration + 500)
|
|
||||||
// The server closes the websocket after errors, so we need to reopen it
|
|
||||||
setTimeout(() => {
|
|
||||||
wsWatch = connect(watchUrl, {
|
wsWatch = connect(watchUrl, {
|
||||||
message: handleWatchMessage,
|
message: handleWatchMessage,
|
||||||
close: watchReconnect,
|
close: watchReconnect,
|
||||||
})
|
})
|
||||||
console.log("Attempting to reconnect...")
|
wsWatch.addEventListener("message", event => {
|
||||||
}, reconnectDuration)
|
if (store.connected) return
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const handleWatchMessage = (event: MessageEvent) => {
|
|
||||||
const store = useDocumentStore()
|
|
||||||
const msg = JSON.parse(event.data)
|
const msg = JSON.parse(event.data)
|
||||||
if ('error' in msg) {
|
if ('error' in msg) {
|
||||||
if (msg.error.code === 401) {
|
if (msg.error.code === 401) {
|
||||||
|
@ -58,7 +38,44 @@ const handleWatchMessage = (event: MessageEvent) => {
|
||||||
} else {
|
} else {
|
||||||
store.error = msg.error.message
|
store.error = msg.error.message
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
if ("server" in msg) {
|
||||||
|
console.log('Connected to backend', msg)
|
||||||
|
store.connected = true
|
||||||
|
reconnectDuration = 500
|
||||||
|
store.error = ''
|
||||||
|
if (msg.user) store.login(msg.user.username, msg.user.privileged)
|
||||||
|
else if (store.isUserLogged) store.logout()
|
||||||
|
if (!msg.server.public && !msg.user) store.user.isOpenLoginModal = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const watchDisconnect = () => {
|
||||||
|
if (!wsWatch) return
|
||||||
|
wsWatch.close()
|
||||||
|
wsWatch = null
|
||||||
|
}
|
||||||
|
|
||||||
|
let watchTimeout: any = null
|
||||||
|
|
||||||
|
const watchReconnect = (event: MessageEvent) => {
|
||||||
|
const store = useDocumentStore()
|
||||||
|
if (store.connected) {
|
||||||
|
console.warn("Disconnected from server", event)
|
||||||
|
store.connected = false
|
||||||
|
store.error = 'Reconnecting...'
|
||||||
|
}
|
||||||
|
reconnectDuration = Math.min(5000, reconnectDuration + 500)
|
||||||
|
// The server closes the websocket after errors, so we need to reopen it
|
||||||
|
if (watchTimeout !== null) clearTimeout(watchTimeout)
|
||||||
|
watchTimeout = setTimeout(watchConnect, reconnectDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const handleWatchMessage = (event: MessageEvent) => {
|
||||||
|
const msg = JSON.parse(event.data)
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case !!msg.root:
|
case !!msg.root:
|
||||||
handleRootMessage(msg)
|
handleRootMessage(msg)
|
||||||
|
@ -79,9 +96,6 @@ const handleWatchMessage = (event: MessageEvent) => {
|
||||||
function handleRootMessage({ root }: { root: DirEntry }) {
|
function handleRootMessage({ root }: { root: DirEntry }) {
|
||||||
const store = useDocumentStore()
|
const store = useDocumentStore()
|
||||||
console.log('Watch root', root)
|
console.log('Watch root', root)
|
||||||
reconnectDuration = 500
|
|
||||||
store.connected = true
|
|
||||||
store.user.isLoggedIn = true
|
|
||||||
store.updateRoot(root)
|
store.updateRoot(root)
|
||||||
tree = root
|
tree = root
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import type {
|
||||||
import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
|
import { formatSize, formatUnixDate, haystackFormat } from '@/utils'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { collator } from '@/utils'
|
import { collator } from '@/utils'
|
||||||
|
import { logoutUser } from '@/repositories/User'
|
||||||
|
import { watchConnect } from '@/repositories/WS'
|
||||||
|
|
||||||
type FileData = { id: string; mtime: number; size: number; dir: DirectoryData }
|
type FileData = { id: string; mtime: number; size: number; dir: DirectoryData }
|
||||||
type DirectoryData = {
|
type DirectoryData = {
|
||||||
|
@ -108,10 +110,14 @@ export const useDocumentStore = defineStore({
|
||||||
this.user.privileged = privileged
|
this.user.privileged = privileged
|
||||||
this.user.isLoggedIn = true
|
this.user.isLoggedIn = true
|
||||||
this.user.isOpenLoginModal = false
|
this.user.isOpenLoginModal = false
|
||||||
|
if (!this.connected) watchConnect()
|
||||||
|
},
|
||||||
|
loginDialog() {
|
||||||
|
this.user.isOpenLoginModal = true
|
||||||
},
|
},
|
||||||
async logout() {
|
async logout() {
|
||||||
const res = await fetch('/logout', { method: 'POST' })
|
console.log("Logout")
|
||||||
if (!res.ok) throw Error(`Logout failed: ${res.statusText}`)
|
await logoutUser()
|
||||||
this.$reset()
|
this.$reset()
|
||||||
history.go() // Reload page
|
history.go() // Reload page
|
||||||
}
|
}
|
||||||
|
|
19
cista/api.py
19
cista/api.py
|
@ -4,7 +4,7 @@ import typing
|
||||||
import msgspec
|
import msgspec
|
||||||
from sanic import Blueprint
|
from sanic import Blueprint
|
||||||
|
|
||||||
from cista import watching
|
from cista import __version__, config, watching
|
||||||
from cista.fileio import FileServer
|
from cista.fileio import FileServer
|
||||||
from cista.protocol import ControlTypes, FileRange, StatusMsg
|
from cista.protocol import ControlTypes, FileRange, StatusMsg
|
||||||
from cista.util.apphelpers import asend, websocket_wrapper
|
from cista.util.apphelpers import asend, websocket_wrapper
|
||||||
|
@ -83,6 +83,23 @@ async def control(req, ws):
|
||||||
@bp.websocket("watch")
|
@bp.websocket("watch")
|
||||||
@websocket_wrapper
|
@websocket_wrapper
|
||||||
async def watch(req, ws):
|
async def watch(req, ws):
|
||||||
|
await ws.send(
|
||||||
|
msgspec.json.encode(
|
||||||
|
{
|
||||||
|
"server": {
|
||||||
|
"name": "Cista", # Should be configurable
|
||||||
|
"version": __version__,
|
||||||
|
"public": config.config.public,
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"username": req.ctx.username,
|
||||||
|
"privileged": req.ctx.user.privileged,
|
||||||
|
}
|
||||||
|
if req.ctx.user
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
).decode()
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
with watching.tree_lock:
|
with watching.tree_lock:
|
||||||
q = watching.pubsub[ws] = asyncio.Queue()
|
q = watching.pubsub[ws] = asyncio.Queue()
|
||||||
|
|
|
@ -38,8 +38,10 @@ async def main_stop(app, loop):
|
||||||
async def use_session(req):
|
async def use_session(req):
|
||||||
req.ctx.session = session.get(req)
|
req.ctx.session = session.get(req)
|
||||||
try:
|
try:
|
||||||
|
req.ctx.username = req.ctx.session["username"]
|
||||||
req.ctx.user = config.config.users[req.ctx.session["username"]] # type: ignore
|
req.ctx.user = config.config.users[req.ctx.session["username"]] # type: ignore
|
||||||
except (AttributeError, KeyError, TypeError):
|
except (AttributeError, KeyError, TypeError):
|
||||||
|
req.ctx.username = None
|
||||||
req.ctx.user = None
|
req.ctx.user = None
|
||||||
# CSRF protection
|
# CSRF protection
|
||||||
if req.method == "GET" and req.headers.upgrade != "websocket":
|
if req.method == "GET" and req.headers.upgrade != "websocket":
|
||||||
|
|
Loading…
Reference in New Issue
Block a user