web: add support for custom api keys & improve turnstile states

This commit is contained in:
wukko 2024-11-23 19:13:23 +06:00
parent 7c7cefe89b
commit 601597eb15
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
6 changed files with 101 additions and 40 deletions

View file

@ -114,9 +114,10 @@
"advanced.data": "data management", "advanced.data": "data management",
"processing.community": "community instances", "processing.community": "community instances",
"processing.enable_custom.title": "use a custom processing server", "processing.enable_custom.title": "use a custom processing server",
"processing.enable_custom.description": "cobalt will use a custom processing server if you choose to. even though cobalt has some security measures in place, we are not responsible for any damages done via a community instance, as we have no control over them.\n\nplease be mindful of what instances you use and make sure they're hosted by people you trust.", "processing.enable_custom.description": "cobalt will use a custom processing server if you choose to. even though cobalt has some security measures in place, we are not responsible for any damages done via a community instance, as we have no control over them.\n\nplease be mindful of what instances you use and make sure they're hosted by people you trust.",
"processing.custom.placeholder": "custom instance domain" "processing.access_key": "instance access key",
"processing.access_key.title": "use an instance access key",
"processing.access_key.description": "cobalt will use this key to make requests to the api instance."
} }

View file

@ -7,12 +7,11 @@
import { SvelteComponent, tick } from "svelte"; import { SvelteComponent, tick } from "svelte";
import { t } from "$lib/i18n/translations"; import { t } from "$lib/i18n/translations";
import { cachedInfo } from "$lib/api/server-info";
import dialogs from "$lib/state/dialogs"; import dialogs from "$lib/state/dialogs";
import { link } from "$lib/state/omnibox"; import { link } from "$lib/state/omnibox";
import { updateSetting } from "$lib/state/settings"; import { updateSetting } from "$lib/state/settings";
import { turnstileSolved } from "$lib/state/turnstile"; import { turnstileEnabled, turnstileSolved } from "$lib/state/turnstile";
import type { Optional } from "$lib/types/generic"; import type { Optional } from "$lib/types/generic";
import type { DownloadModeOption } from "$lib/types/settings"; import type { DownloadModeOption } from "$lib/types/settings";
@ -37,8 +36,8 @@
let isDisabled = false; let isDisabled = false;
let isLoading = false; let isLoading = false;
$: isBotCheckOngoing =
!!$cachedInfo?.info?.cobalt?.turnstileSitekey && !$turnstileSolved; $: isBotCheckOngoing = $turnstileEnabled && !$turnstileSolved;
const validLink = (url: string) => { const validLink = (url: string) => {
try { try {

View file

@ -5,12 +5,45 @@ import lazySettingGetter from "$lib/settings/lazy-get";
import { getSession } from "$lib/api/session"; import { getSession } from "$lib/api/session";
import { currentApiURL } from "$lib/api/api-url"; import { currentApiURL } from "$lib/api/api-url";
import { turnstileSolved } from "$lib/state/turnstile"; import { turnstileEnabled, turnstileSolved } from "$lib/state/turnstile";
import { cachedInfo, getServerInfo } from "$lib/api/server-info"; import { cachedInfo, getServerInfo } from "$lib/api/server-info";
import type { Optional } from "$lib/types/generic"; import type { Optional } from "$lib/types/generic";
import type { CobaltAPIResponse, CobaltErrorResponse } from "$lib/types/api"; import type { CobaltAPIResponse, CobaltErrorResponse } from "$lib/types/api";
const getAuthorization = async () => {
const processing = get(settings).processing;
if (get(turnstileEnabled)) {
if (!get(turnstileSolved)) {
return {
status: "error",
error: {
code: "error.captcha_ongoing"
}
} as CobaltErrorResponse;
}
const session = await getSession();
if (session) {
if ("error" in session) {
if (session.error.code !== "error.api.auth.not_configured") {
return session;
}
} else {
return `Bearer ${session.token}`;
}
}
}
if (processing.enableCustomApiKey && processing.customApiKey.length > 0) {
return `Api-Key ${processing.customApiKey}`;
}
return false;
}
const request = async (url: string) => { const request = async (url: string) => {
const getSetting = lazySettingGetter(get(settings)); const getSetting = lazySettingGetter(get(settings));
@ -49,31 +82,14 @@ const request = async (url: string) => {
} as CobaltErrorResponse; } as CobaltErrorResponse;
} }
if (getCachedInfo?.info?.cobalt?.turnstileSitekey && !get(turnstileSolved)) {
return {
status: "error",
error: {
code: "error.captcha_ongoing"
}
} as CobaltErrorResponse;
}
const api = currentApiURL(); const api = currentApiURL();
const authorization = await getAuthorization();
const session = getCachedInfo?.info?.cobalt?.turnstileSitekey
? await getSession() : undefined;
let extraHeaders = {} let extraHeaders = {}
if (session) { if (authorization) {
if ("error" in session) { extraHeaders = {
if (session.error.code !== "error.api.auth.not_configured") { "Authorization": authorization
return session;
}
} else {
extraHeaders = {
"Authorization": `Bearer ${session.token}`,
};
} }
} }

View file

@ -1,4 +1,17 @@
import { writable } from "svelte/store"; import settings from "$lib/state/settings";
import { cachedInfo } from "$lib/api/server-info";
import { derived, writable } from "svelte/store";
export const turnstileSolved = writable(false); export const turnstileSolved = writable(false);
export const turnstileCreated = writable(false); export const turnstileCreated = writable(false);
export const turnstileEnabled = derived(
[settings, cachedInfo],
([$settings, $cachedInfo]) => {
return !!$cachedInfo?.info?.cobalt?.turnstileSitekey &&
!(
$settings.processing.enableCustomApiKey &&
$settings.processing.customApiKey.length > 0
)
}
)

View file

@ -7,18 +7,18 @@
import { updated } from "$app/stores"; import { updated } from "$app/stores";
import { browser } from "$app/environment"; import { browser } from "$app/environment";
import { afterNavigate } from "$app/navigation"; import { afterNavigate } from "$app/navigation";
import { getServerInfo, cachedInfo } from "$lib/api/server-info"; import { getServerInfo } from "$lib/api/server-info";
import "$lib/polyfills"; import "$lib/polyfills";
import env from "$lib/env"; import env from "$lib/env";
import settings from "$lib/state/settings";
import locale from "$lib/i18n/locale"; import locale from "$lib/i18n/locale";
import settings from "$lib/state/settings";
import { t } from "$lib/i18n/translations"; import { t } from "$lib/i18n/translations";
import { device, app } from "$lib/device"; import { device, app } from "$lib/device";
import { turnstileCreated } from "$lib/state/turnstile";
import currentTheme, { statusBarColors } from "$lib/state/theme"; import currentTheme, { statusBarColors } from "$lib/state/theme";
import { turnstileCreated, turnstileEnabled } from "$lib/state/turnstile";
import Sidebar from "$components/sidebar/Sidebar.svelte"; import Sidebar from "$components/sidebar/Sidebar.svelte";
import Turnstile from "$components/misc/Turnstile.svelte"; import Turnstile from "$components/misc/Turnstile.svelte";
@ -28,13 +28,12 @@
$: reduceMotion = $: reduceMotion =
$settings.appearance.reduceMotion || device.prefers.reducedMotion; $settings.appearance.reduceMotion || device.prefers.reducedMotion;
$: reduceTransparency = $: reduceTransparency =
$settings.appearance.reduceTransparency || $settings.appearance.reduceTransparency ||
device.prefers.reducedTransparency; device.prefers.reducedTransparency;
$: spawnTurnstile = !!$cachedInfo?.info?.cobalt?.turnstileSitekey; afterNavigate(async () => {
afterNavigate(async() => {
const to_focus: HTMLElement | null = const to_focus: HTMLElement | null =
document.querySelector("[data-first-focus]"); document.querySelector("[data-first-focus]");
to_focus?.focus(); to_focus?.focus();
@ -46,11 +45,14 @@
</script> </script>
<svelte:head> <svelte:head>
<meta name="description" content={$t("general.embed.description")}> <meta name="description" content={$t("general.embed.description")} />
<meta property="og:description" content={$t("general.embed.description")}> <meta property="og:description" content={$t("general.embed.description")} />
{#if env.HOST} {#if env.HOST}
<meta property="og:url" content="https://{env.HOST}{$page.url.pathname}"> <meta
property="og:url"
content="https://{env.HOST}{$page.url.pathname}"
/>
{/if} {/if}
{#if device.is.mobile} {#if device.is.mobile}
@ -67,7 +69,11 @@
{/if} {/if}
</svelte:head> </svelte:head>
<div style="display: contents" data-theme={browser ? $currentTheme : undefined} lang={$locale}> <div
style="display: contents"
data-theme={browser ? $currentTheme : undefined}
lang={$locale}
>
<div <div
id="cobalt" id="cobalt"
class:loaded={browser} class:loaded={browser}
@ -84,7 +90,7 @@
<DialogHolder /> <DialogHolder />
<Sidebar /> <Sidebar />
<div id="content"> <div id="content">
{#if (spawnTurnstile && $page.url.pathname === "/") || $turnstileCreated} {#if ($turnstileEnabled && $page.url.pathname === "/") || $turnstileCreated}
<Turnstile /> <Turnstile />
{/if} {/if}
<slot></slot> <slot></slot>

View file

@ -31,6 +31,32 @@
</div> </div>
</SettingsCategory> </SettingsCategory>
{#if $settings.processing.enableCustomInstances}
<SettingsCategory
sectionId="community"
title={$t("settings.processing.access_key")}
>
<div class="category-inside-group">
<SettingsToggle
settingContext="processing"
settingId="enableCustomApiKey"
title={$t("settings.processing.access_key.title")}
/>
{#if $settings.processing.enableCustomApiKey}
<SettingsInput
settingContext="processing"
settingId="customApiKey"
placeholder="00000000-0000-0000-0000-000000000000"
type="uuid"
/>
{/if}
</div>
<div class="subtext">
{$t("settings.processing.access_key.description")}
</div>
</SettingsCategory>
{/if}
<style> <style>
.category-inside-group { .category-inside-group {
display: flex; display: flex;