web/api: jwt session token, clean up, move related modules to own dir

This commit is contained in:
wukko 2024-08-16 23:36:56 +06:00
parent 16acf62886
commit 4857030933
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
9 changed files with 255 additions and 145 deletions

View file

@ -1,7 +1,7 @@
<script lang="ts">
import "@fontsource-variable/noto-sans-mono";
import API from "$lib/api";
import API from "$lib/api/api";
import { t } from "$lib/i18n/translations";
import { createDialog } from "$lib/dialogs";
import { downloadFile } from "$lib/download";

View file

@ -1,140 +0,0 @@
import { get } from "svelte/store";
import turnstile from "$lib/turnstile";
import env, { apiURL } from "$lib/env";
import { t } from "$lib/i18n/translations";
import settings, { updateSetting } from "$lib/state/settings";
import { createDialog } from "$lib/dialogs";
import type { CobaltAPIResponse } from "$lib/types/api";
import type { Optional } from "$lib/types/generic";
const request = async (url: string) => {
const saveSettings = get(settings).save;
const request = {
url,
downloadMode: saveSettings.downloadMode,
audioFormat: saveSettings.audioFormat,
tiktokFullAudio: saveSettings.tiktokFullAudio,
youtubeDubBrowserLang: saveSettings.youtubeDubBrowserLang,
youtubeVideoCodec: saveSettings.youtubeVideoCodec,
videoQuality: saveSettings.videoQuality,
filenameStyle: saveSettings.filenameStyle,
disableMetadata: saveSettings.disableMetadata,
twitterGif: saveSettings.twitterGif,
tiktokH265: saveSettings.tiktokH265,
}
if (env.DEFAULT_API && !get(settings).processing.seenOverrideWarning) {
let _actions: {
resolve: () => void;
reject: () => void;
};
const promise = new Promise<void>(
(resolve, reject) => (_actions = { resolve, reject })
).catch(() => {
return {}
});
createDialog({
id: "security-api-override",
type: "small",
icon: "warn-red",
title: get(t)("dialog.api.override.title"),
bodyText: get(t)("dialog.api.override.body", { value: env.DEFAULT_API }),
dismissable: false,
buttons: [
{
text: get(t)("button.cancel"),
main: false,
action: () => {
_actions.reject();
updateSetting({
processing: {
seenOverrideWarning: true,
},
})
},
},
{
text: get(t)("button.continue"),
color: "red",
main: true,
timeout: 5000,
action: () => {
_actions.resolve();
updateSetting({
processing: {
allowDefaultOverride: true,
seenOverrideWarning: true,
},
})
},
},
],
})
await promise;
}
let api = apiURL;
if (env.DEFAULT_API && get(settings).processing.allowDefaultOverride) {
api = env.DEFAULT_API;
}
let turnstileHeader = {};
if (env.TURNSTILE_KEY) {
const turnstileResponse = turnstile.getResponse();
if (turnstileResponse) {
turnstileHeader = {
"cf-turnstile-response": turnstileResponse
};
}
}
const response: Optional<CobaltAPIResponse> = await fetch(api, {
method: "POST",
redirect: "manual",
signal: AbortSignal.timeout(10000),
body: JSON.stringify(request),
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...turnstileHeader,
},
})
.then(r => r.json())
.catch((e) => {
if (e?.message?.includes("timed out")) {
return {
status: "error",
error: {
code: "error.api.timed_out"
}
}
}
});
return response;
}
const probeCobaltStream = async (url: string) => {
const request = await fetch(`${url}&p=1`).catch(() => {});
if (request?.status === 200) {
return request?.status;
}
return 0;
}
export default {
request,
probeCobaltStream,
}

View file

@ -0,0 +1,11 @@
import { get } from "svelte/store";
import env, { apiURL } from "$lib/env";
import settings from "$lib/state/settings";
export const currentApiURL = () => {
if (env.DEFAULT_API && get(settings).processing.allowDefaultOverride) {
return env.DEFAULT_API;
}
return apiURL;
}

88
web/src/lib/api/api.ts Normal file
View file

@ -0,0 +1,88 @@
import { get } from "svelte/store";
import settings from "$lib/state/settings";
import { getSession } from "$lib/api/session";
import { currentApiURL } from "$lib/api/api-url";
import { apiOverrideWarning } from "$lib/api/override-warning";
import type { Optional } from "$lib/types/generic";
import type { CobaltAPIResponse, CobaltErrorResponse } from "$lib/types/api";
const request = async (url: string) => {
const saveSettings = get(settings).save;
const request = {
url,
downloadMode: saveSettings.downloadMode,
audioFormat: saveSettings.audioFormat,
tiktokFullAudio: saveSettings.tiktokFullAudio,
youtubeDubBrowserLang: saveSettings.youtubeDubBrowserLang,
youtubeVideoCodec: saveSettings.youtubeVideoCodec,
videoQuality: saveSettings.videoQuality,
filenameStyle: saveSettings.filenameStyle,
disableMetadata: saveSettings.disableMetadata,
twitterGif: saveSettings.twitterGif,
tiktokH265: saveSettings.tiktokH265,
}
await apiOverrideWarning();
const api = currentApiURL();
const session = await getSession();
let extraHeaders = {}
if (session) {
if ("error" in session) {
if (session.error.code !== "error.api.auth.not_configured") {
return session;
}
} else {
extraHeaders = {
"Authorization": `Bearer ${session.token}`,
};
}
}
const response: Optional<CobaltAPIResponse> = await fetch(api, {
method: "POST",
redirect: "manual",
signal: AbortSignal.timeout(10000),
body: JSON.stringify(request),
headers: {
"Accept": "application/json",
"Content-Type": "application/json",
...extraHeaders,
},
})
.then(r => r.json())
.catch((e) => {
if (e?.message?.includes("timed out")) {
return {
status: "error",
error: {
code: "error.api.timed_out"
}
} as CobaltErrorResponse;
}
});
return response;
}
const probeCobaltStream = async (url: string) => {
const request = await fetch(`${url}&p=1`).catch(() => {});
if (request?.status === 200) {
return request?.status;
}
return 0;
}
export default {
request,
probeCobaltStream,
}

View file

@ -0,0 +1,62 @@
import { get } from "svelte/store";
import env from "$lib/env";
import { t } from "$lib/i18n/translations";
import settings, { updateSetting } from "$lib/state/settings";
import { createDialog } from "$lib/dialogs";
export const apiOverrideWarning = async () => {
if (env.DEFAULT_API && !get(settings).processing.seenOverrideWarning) {
let _actions: {
resolve: () => void;
reject: () => void;
};
const promise = new Promise<void>(
(resolve, reject) => (_actions = { resolve, reject })
).catch(() => {
return {}
});
createDialog({
id: "security-api-override",
type: "small",
icon: "warn-red",
title: get(t)("dialog.api.override.title"),
bodyText: get(t)("dialog.api.override.body", { value: env.DEFAULT_API }),
dismissable: false,
buttons: [
{
text: get(t)("button.cancel"),
main: false,
action: () => {
_actions.reject();
updateSetting({
processing: {
seenOverrideWarning: true,
},
})
},
},
{
text: get(t)("button.continue"),
color: "red",
main: true,
timeout: 5000,
action: () => {
_actions.resolve();
updateSetting({
processing: {
allowDefaultOverride: true,
seenOverrideWarning: true,
},
})
},
},
],
})
await promise;
}
}

View file

@ -0,0 +1,65 @@
import { get } from "svelte/store";
import turnstile from "$lib/api/turnstile";
import { currentApiURL } from "$lib/api/api-url";
import { cachedSession } from "$lib/state/session";
import type { CobaltSessionResponse, CobaltErrorResponse } from "$lib/types/api";
export const requestSession = async() => {
const apiEndpoint = `${currentApiURL()}session`;
let requestHeaders = {};
const turnstileResponse = turnstile.getResponse();
if (turnstileResponse) {
requestHeaders = {
"cf-turnstile-response": turnstileResponse
};
}
const response: CobaltSessionResponse = await fetch(apiEndpoint, {
method: "POST",
redirect: "manual",
signal: AbortSignal.timeout(10000),
headers: requestHeaders,
})
.then(r => r.json())
.catch((e) => {
if (e?.message?.includes("timed out")) {
return {
status: "error",
error: {
code: "error.api.timed_out"
}
} as CobaltErrorResponse
}
});
turnstile.update();
return response;
}
export const getSession = async () => {
const currentTime = new Date().getTime();
const cache = get(cachedSession);
if (cache?.token && cache?.exp > currentTime) {
return cache;
}
const newSession = await requestSession();
if (!newSession) return {
status: "error",
error: {
code: "error.api.generic"
}
} as CobaltErrorResponse
if (!("status" in newSession)) {
cachedSession.set(newSession);
}
return newSession;
}

View file

@ -8,6 +8,17 @@ const getResponse = () => {
return null;
}
export default {
getResponse
const update = () => {
const turnstileElement = document.getElementById("turnstile-widget");
if (turnstileElement) {
return window?.turnstile?.reset(turnstileElement);
}
return null;
}
export default {
getResponse,
update,
}

View file

@ -0,0 +1,6 @@
import { writable } from "svelte/store";
import type { Writable } from "svelte/store";
import type { CobaltSession } from "$lib/types/api";
export const cachedSession: Writable<CobaltSession | null> = writable();

View file

@ -5,7 +5,7 @@ enum CobaltResponseType {
Stream = 'stream',
}
type CobaltErrorResponse = {
export type CobaltErrorResponse = {
status: CobaltResponseType.Error,
error: {
code: string,
@ -13,7 +13,7 @@ type CobaltErrorResponse = {
service?: string,
limit?: number,
}
}
},
};
type CobaltPartialURLResponse = {
@ -38,6 +38,13 @@ type CobaltStreamResponse = {
status: CobaltResponseType.Stream,
} & CobaltPartialURLResponse;
export type CobaltSession = {
token: string,
exp: number,
}
export type CobaltSessionResponse = CobaltSession | CobaltErrorResponse;
export type CobaltAPIResponse = CobaltErrorResponse
| CobaltPickerResponse
| CobaltRedirectResponse