fix csrf_token and GuildSelect
This commit is contained in:
parent
58d6a248c7
commit
54cafaa1da
3 changed files with 71 additions and 19 deletions
|
|
@ -25,7 +25,7 @@ let { children, title = null } = $props();
|
||||||
position: relative;
|
position: relative;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
|
|
||||||
a {
|
:global(a) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,46 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { GuildEntry } from "./backend";
|
import { backend, type GuildEntry } from "./backend";
|
||||||
|
|
||||||
|
|
||||||
let value = $state("");
|
let value = $state("");
|
||||||
|
|
||||||
let suggestions: GuildEntry[] = $state([]);
|
let suggestions: Promise<GuildEntry[]> = $derived(getSuggestions(value));
|
||||||
|
|
||||||
|
async function getSuggestions (query: string): Promise<GuildEntry[]> {
|
||||||
|
if (!query.trim()) return [];
|
||||||
|
let readyBackend = await backend.withEvent(null).oath();
|
||||||
|
let result = await readyBackend.submitJson("suggest/guild", { query });
|
||||||
|
if (result.status !== 200) return [];
|
||||||
|
let resultJ = await result.json();
|
||||||
|
return resultJ?.has || [];
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<div class="inset-suggestion">
|
<div class="inset-suggestion">
|
||||||
<input type="text" name="to" bind:value placeholder="your user page or +guild name" autocomplete="off" />
|
<input type="text" name="to" bind:value placeholder="your user page or +guild name" autocomplete="off" />
|
||||||
<ul class="column">
|
<ul class="column">
|
||||||
{#each suggestions as sug}
|
{#await suggestions}
|
||||||
<li><button onclick={(ev)=>{
|
<!-- loading... -->
|
||||||
|
{:then suggs}
|
||||||
|
{#each suggs as sug}
|
||||||
|
<li><button class="inline" onclick={(ev)=>{
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
value = sug.name;
|
value = sug.name;
|
||||||
}}>
|
}}>
|
||||||
<faint>+</faint>{sug}
|
<faint>+</faint>{sug.name}
|
||||||
</button></li>
|
</button></li>
|
||||||
{:else}
|
{:else}
|
||||||
<li>
|
<li>
|
||||||
<faint>+{value}</faint>
|
<faint>+{value}</faint>
|
||||||
</li>
|
</li>
|
||||||
{/each}
|
{/each}
|
||||||
|
{:catch ex}
|
||||||
|
<li>
|
||||||
|
<faint>+{value} ({ex})</faint>
|
||||||
|
</li>
|
||||||
|
{/await}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -46,7 +63,7 @@ let suggestions: GuildEntry[] = $state([]);
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
:not(:focus) + ul {
|
:not(:focus) + ul:not(:active) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,32 @@ export type ServerHealth = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// caches an oath (CSRF token)
|
||||||
|
class OathKeeper {
|
||||||
|
lastOath : number;
|
||||||
|
csrfToken: string | null;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this.lastOath = 0;
|
||||||
|
this.csrfToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
set (tok: string) {
|
||||||
|
this.lastOath = +new Date;
|
||||||
|
this.csrfToken = tok;
|
||||||
|
}
|
||||||
|
|
||||||
|
get () {
|
||||||
|
if (this.lastOath === 0 || +new Date() - this.lastOath > 1800000) {
|
||||||
|
// 30 minutes decay
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this.csrfToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const oathKeeper = new OathKeeper();
|
||||||
|
|
||||||
export class Backend {
|
export class Backend {
|
||||||
static ENDPOINT_BASE = '/v1';
|
static ENDPOINT_BASE = '/v1';
|
||||||
|
|
||||||
|
|
@ -60,33 +86,42 @@ export class Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EventBackend extends Backend {
|
class EventBackend extends Backend {
|
||||||
event: any;
|
event: any | null;
|
||||||
|
|
||||||
constructor (event: any) {
|
constructor (event: any | null) {
|
||||||
super();
|
super();
|
||||||
this.event = event;
|
this.event = event;
|
||||||
}
|
}
|
||||||
|
|
||||||
async oath (): Promise<CsrfEventBackend> {
|
async oath (): Promise<CsrfEventBackend> {
|
||||||
const resp = await this.fetch("oath");
|
let csrfToken = oathKeeper.get();
|
||||||
if (resp.status !== 200) {
|
if (!csrfToken) {
|
||||||
throw new Error();
|
const resp = await this.fetch("oath");
|
||||||
|
if (resp.status !== 200) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
const respJ = await resp.json();
|
||||||
|
csrfToken = respJ.csrf_token;
|
||||||
|
}
|
||||||
|
if (!csrfToken) {
|
||||||
|
console.warn("csrf_token is null");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
oathKeeper.set(csrfToken);
|
||||||
}
|
}
|
||||||
const respJ = await resp.json();
|
|
||||||
const { csrfToken } = respJ;
|
|
||||||
return new CsrfEventBackend(this.event, csrfToken);
|
return new CsrfEventBackend(this.event, csrfToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetch(url: string, options?: RequestInit): Promise<Response> {
|
async fetch(url: string, options?: RequestInit): Promise<Response> {
|
||||||
console.debug(`fetch ${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`);
|
console.debug(`fetch ${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`);
|
||||||
return await this.event.fetch(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
|
return await (this.event?.fetch||fetch)(`${Backend.ENDPOINT_BASE}/${url.replace(/^\/+/, '')}`, options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CsrfEventBackend extends EventBackend {
|
class CsrfEventBackend extends EventBackend {
|
||||||
csrfToken: string;
|
csrfToken: string | null;
|
||||||
|
|
||||||
constructor(event:EventBackend, csrfToken: string) {
|
constructor(event: EventBackend | null, csrfToken: string | null) {
|
||||||
super(event);
|
super(event);
|
||||||
this.csrfToken = csrfToken;
|
this.csrfToken = csrfToken;
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +132,7 @@ class CsrfEventBackend extends EventBackend {
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
headers: {
|
headers: {
|
||||||
'content-type': 'application/json',
|
'content-type': 'application/json',
|
||||||
'x-csrftoken': this.csrfToken
|
'x-csrftoken': this.csrfToken || ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue