diff --git a/svelte/src/filesystem/filemanager/FilePicker.svelte b/svelte/src/filesystem/filemanager/FilePicker.svelte index 6e44e5a..a75695e 100644 --- a/svelte/src/filesystem/filemanager/FilePicker.svelte +++ b/svelte/src/filesystem/filemanager/FilePicker.svelte @@ -16,10 +16,13 @@ let show_hidden = $state(false) let { callback, - select_multiple = false + select_multiple = false, + directories = false, }: { callback: (files: FSNode[]) => void select_multiple?: boolean + // Picks the directory which is open, instead of files in it + directories?: boolean } = $props(); export const open = (path: string) => { @@ -36,13 +39,13 @@ const file_event: FileActionHandler = (action: FileAction, index: number, orig: orig.preventDefault() if (nav.children[index].type === "dir") { nav.navigate(nav.children[index].path, true) - } else { + } else if (!directories) { select_node(index, orig) } break case FileAction.Context: // If this is a touch event we will select the item - if (navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { + if (!directories && navigator.maxTouchPoints && navigator.maxTouchPoints > 0) { orig.preventDefault() select_node(index, orig) } @@ -70,7 +73,9 @@ const select_node = (index: number, e: Event) => { } let done = () => { - if (nav.selection.size > 0) { + if (directories) { + callback([nav.base]) + } else if (nav.selection.size > 0) { callback([...nav.selection.values()]) } modal.hide() @@ -101,7 +106,11 @@ onMount(() => {
- Selected {$nav.selection.size} files + {#if directories} + Open the directory to pick + {:else} + Selected {$nav.selection.size} files + {/if}
+ + {/each} + + {/if} +

+ +

+ {/if} + +

+ You can take the access away again by deleting the key of the + app on the API keys page. +

+ + + + close Deny + + {/if} + + + + diff --git a/svelte/src/user_home/APIKeys.svelte b/svelte/src/user_home/APIKeys.svelte index 45df620..ef670ae 100644 --- a/svelte/src/user_home/APIKeys.svelte +++ b/svelte/src/user_home/APIKeys.svelte @@ -2,7 +2,8 @@ import { onMount } from "svelte"; import { loading_run } from "lib/Loading"; import { check_response, dict_to_form, get_endpoint, type UserSession } from "lib/NovaAPI"; -import { fs_get_node } from "lib/FilesystemAPI.svelte"; +import { fs_get_node, type FSNode } from "lib/FilesystemAPI.svelte"; +import FilePicker from "filesystem/filemanager/FilePicker.svelte"; import { formatDate } from "util/Formatting"; import CopyButton from "layout/CopyButton.svelte"; import NovaLogo from "util/NovaLogo.svelte"; @@ -26,6 +27,52 @@ let dir_names: {[id: string]: string} = $state({}) // The keys of which the whole user agent is shown let agent_shown: {[key: string]: boolean} = $state({}) +// The key which is being edited, and what its permissions and directories are +// going to be. Nothing changes until it is saved +let editing = $state("") +let edit_perms: {[name: string]: boolean} = $state({}) +let edit_dirs: string[] = $state([]) +let picker: FilePicker = $state() + +const edit_key = (key: UserSession) => { + editing = key.auth_key + edit_dirs = [...key.filesystem_dirs] + edit_perms = {} + for (const [, , read, write] of components) { + edit_perms[read] = key[read] as boolean + edit_perms[write] = key[write] as boolean + } +} + +const pick_dir = (nodes: FSNode[]) => { + const dir = nodes[0] + dir_names[dir.id] = dir.name + if (!edit_dirs.includes(dir.id)) { + edit_dirs.push(dir.id) + } +} + +const save_key = async () => { + try { + await loading_run(async () => check_response( + await fetch(get_endpoint() + "/user/session", { + method: "PUT", + body: dict_to_form({ + auth_key: editing, + ...edit_perms, + filesystem_dirs: edit_dirs, + }), + }) + )) + } catch (err) { + alert("Failed to change key: " + (err.message ?? err)) + return + } + + editing = "" + await load_keys() +} + // Intl.RelativeTimeFormat is the browser's formatter for "5 minutes ago", it // takes care of the plurals const relative = new Intl.RelativeTimeFormat("en", {numeric: "always", style: "short"}) @@ -70,10 +117,16 @@ const load_keys = async () => { const create_key = async () => { try { + // Keys are made to be put in other programs, where they can leak. With + // account write a key can change the password and the e-mail address, + // which is enough to take the account over, so these don't get it const key: UserSession = await loading_run(async () => check_response( await fetch(get_endpoint() + "/user/session", { method: "POST", - body: dict_to_form({app_name: "website keys page"}), + body: dict_to_form({ + app_name: "website keys page", + account_write: false, + }), }) )) shown[key.auth_key] = true @@ -149,6 +202,13 @@ onMount(load_keys) This session {/if}
+ + {#if !key.current && editing !== key.auth_key} + + {/if} @@ -168,13 +228,61 @@ onMount(load_keys) Copy + {#if editing === key.auth_key} +
+ {#each components as [name, icon, read, write]} +
+ {icon} {name} + + + {#if write !== "account_write" || key.account_write} + + {/if} +
+ {/each} + +
+ {#if edit_dirs.length === 0} + Files access is not limited to directories + {:else} + Files access is limited to + {#each edit_dirs as id, index (id)} + + folder{dir_names[id] ?? id} + + + {/each} + {/if} + +
+ +
+ + +
+
+ {:else}
{#each components as [name, icon, read, write]} {icon} {name}: {#if key[read] && key[write]} - read and write + read / write {:else if key[read]} read only {:else if key[write]} @@ -193,6 +301,7 @@ onMount(load_keys) {/each}
{/if} + {/if}
@@ -223,6 +332,8 @@ onMount(load_keys) {/each} + +