Allow password entry for shared directories
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import TextBlock from "layout/TextBlock.svelte";
|
||||
import Button from "layout/Button.svelte";
|
||||
import { type FSNavigator } from "./FSNavigator";
|
||||
import { onMount } from "svelte";
|
||||
import { breadcrumbs_store } from "wrap/HeaderStore";
|
||||
@@ -17,6 +18,16 @@ onMount(() => {
|
||||
|
||||
|
||||
})
|
||||
|
||||
let password = $state("")
|
||||
let wrong_password = $state(false)
|
||||
const submit_password = async (e: SubmitEvent) => {
|
||||
e.preventDefault()
|
||||
await nav.navigate(nav.last_requested_path, false, password)
|
||||
|
||||
// This page is gone if the password was right
|
||||
wrong_password = true
|
||||
}
|
||||
</script>
|
||||
|
||||
{#snippet breadcrumbs()}
|
||||
@@ -29,6 +40,23 @@ onMount(() => {
|
||||
<p>
|
||||
This page could not be found.
|
||||
</p>
|
||||
{:else if $nav.navigation_error === "password_required"}
|
||||
<h1>Password required</h1>
|
||||
<p>
|
||||
This directory is protected with a password.
|
||||
</p>
|
||||
<form onsubmit={submit_password}>
|
||||
<!-- Password managers take the directory for the username. Without
|
||||
this they fill in the password of the visitor's Nova account. The
|
||||
hidden attribute does not work, the stylesheet overrides it -->
|
||||
<input type="text" autocomplete="username" value={$nav.last_requested_path.split("/")[1]} style="display: none;">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input type="password" bind:value={password} placeholder="Password" required autofocus>
|
||||
<Button type="submit" icon="lock_open" label="Open"/>
|
||||
</form>
|
||||
{#if wrong_password}
|
||||
<p>This password is not correct.</p>
|
||||
{/if}
|
||||
{:else if $nav.navigation_error === "permission_denied" || $nav.navigation_error === "forbidden"}
|
||||
<h1>Permission denied</h1>
|
||||
<p>
|
||||
|
||||
@@ -45,8 +45,8 @@ export class FSNavigator {
|
||||
|
||||
last_requested_path: string = ""
|
||||
navigation_error: string = ""
|
||||
navigate = async (path: string, push_history: boolean) => {
|
||||
if (path === this.last_requested_path) {
|
||||
navigate = async (path: string, push_history: boolean, password?: string) => {
|
||||
if (path === this.last_requested_path && password === undefined) {
|
||||
console.debug("FSNavigator: Requested path ", path, " is equal to current path. Debouncing")
|
||||
return
|
||||
}
|
||||
@@ -56,6 +56,18 @@ export class FSNavigator {
|
||||
path = "/" + path
|
||||
}
|
||||
|
||||
// The password of a shared directory is entered by the visitor, or it's
|
||||
// in the link they opened. It's sent in a cookie, which is the only way
|
||||
// to get it into the requests for images, videos and downloads. The
|
||||
// cookie's path is the directory, so each directory has its own password
|
||||
if (password === undefined && this.history_enabled) {
|
||||
password = new URLSearchParams(window.location.search).get("password")
|
||||
}
|
||||
if (password) {
|
||||
document.cookie = "nova_fs_password=" + encodeURIComponent(password) +
|
||||
"; path=" + fs_path_url(path.split("/")[1]) + "; samesite=lax"
|
||||
}
|
||||
|
||||
console.debug("FSNavigator: Navigating to path", path, push_history)
|
||||
|
||||
try {
|
||||
@@ -70,9 +82,9 @@ export class FSNavigator {
|
||||
this.open_node(resp, push_history)
|
||||
} catch (err: any) {
|
||||
if (err.value !== undefined && err.value === "path_not_found") {
|
||||
if (path !== this.path[0].path && path !== "/" && path !== "") {
|
||||
if (path !== this.path[0]?.path && path !== "/" && path !== "") {
|
||||
console.debug("Path", path, "was not found, trying to navigate to parent")
|
||||
this.navigate(fs_split_path(path).parent, push_history)
|
||||
this.navigate(fs_split_path(path).parent, push_history, password)
|
||||
}
|
||||
} else if (err.value !== undefined) {
|
||||
this.navigation_error = err.value
|
||||
|
||||
@@ -58,13 +58,7 @@ onMount(() => {
|
||||
const keydown = (e: KeyboardEvent) => {
|
||||
if (e.ctrlKey || e.altKey || e.metaKey) {
|
||||
return // prevent custom shortcuts from interfering with system shortcuts
|
||||
} else if (
|
||||
(document.activeElement as any).type !== undefined &&
|
||||
(
|
||||
(document.activeElement as any).type === "text" ||
|
||||
(document.activeElement as any).type === "textarea"
|
||||
)
|
||||
) {
|
||||
} else if (["text", "password", "textarea"].includes((document.activeElement as any).type)) {
|
||||
return // Prevent shortcuts from interfering with input fields
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { user } from "lib/UserStore";
|
||||
|
||||
let affiliate_link = $derived(
|
||||
window.location.protocol+"//"+window.location.host +
|
||||
"?ref=" + encodeURIComponent($user.username)
|
||||
"/user?ref=" + encodeURIComponent($user.username)
|
||||
)
|
||||
let affiliate_deny = $state(false)
|
||||
onMount(() => {
|
||||
|
||||
@@ -74,10 +74,10 @@ const deny = () => {
|
||||
<Modal bind:this={modal} title="Affiliate sponsoring request" width="700px">
|
||||
<section>
|
||||
<p>
|
||||
Hi! {referral} wants you to sponsor their Nova account. This
|
||||
will give them €0.50 every month in Nova prepaid credit. They
|
||||
can use this credit to get a discount on their file storage and
|
||||
sharing costs. Here is a short summary of what this entails:
|
||||
Hi! {referral} wants you to sponsor their Nova account. This will
|
||||
give them €0.01 every day in Nova prepaid credit. They can use this
|
||||
credit to get a discount on their file storage and sharing costs.
|
||||
Here is a short summary of what this entails:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
@@ -97,10 +97,6 @@ const deny = () => {
|
||||
You can change who you are sponsoring at any time on your <a
|
||||
href="/user/settings">account settings page</a>.
|
||||
</li>
|
||||
<li>
|
||||
If you want to know more about the affiliate program check out
|
||||
the <a href="/about#toc_12">Q&A page</a>.
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
If you click 'Accept' then the requested affiliate code will be
|
||||
|
||||
@@ -32,8 +32,13 @@ func (wc *WebController) serveDirectory(w http.ResponseWriter, r *http.Request,
|
||||
td.User = json.RawMessage("null")
|
||||
}
|
||||
|
||||
// A shared link can have the password of the directory in it
|
||||
var path = strings.TrimPrefix(p.ByName("path"), "/")
|
||||
var err = pdapi.GetJSON("filesystem/"+url.PathEscape(path)+"?stat", &td.Node)
|
||||
var err = pdapi.GetJSON(
|
||||
"filesystem/"+url.PathEscape(path)+"?stat&password="+
|
||||
url.QueryEscape(r.URL.Query().Get("password")),
|
||||
&td.Node,
|
||||
)
|
||||
if apiErr, ok := errors.AsType[pixelapi.Error](err); ok {
|
||||
// Set the proper response code for the error message
|
||||
switch apiErr.StatusCode {
|
||||
|
||||
@@ -124,12 +124,20 @@ func (wc *WebController) metadataFromFilesystem(r *http.Request, f api.Filesyste
|
||||
}
|
||||
}
|
||||
|
||||
// The services which make link previews don't keep cookies, so a password
|
||||
// in the link has to be in every URL they open
|
||||
var query, thumbnail = "", "?thumbnail"
|
||||
if password := r.URL.Query().Get("password"); password != "" {
|
||||
password = "password=" + url.QueryEscape(password)
|
||||
query, thumbnail = "?"+password, thumbnail+"&"+password
|
||||
}
|
||||
|
||||
return generateOGData(
|
||||
base.Name,
|
||||
base.FileType,
|
||||
addr+"/d"+filepath,
|
||||
addr+"/api/filesystem"+filepath,
|
||||
addr+"/api/filesystem"+filepath+"?thumbnail",
|
||||
addr+"/d"+filepath+query,
|
||||
addr+"/api/filesystem"+filepath+query,
|
||||
addr+"/api/filesystem"+filepath+thumbnail,
|
||||
colour,
|
||||
)
|
||||
}
|
||||
|
||||
24
webcontroller/opengraph_test.go
Normal file
24
webcontroller/opengraph_test.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package webcontroller
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fornaxian.tech/nova_server/api"
|
||||
)
|
||||
|
||||
// The services which make link previews have no cookies, so a password in the
|
||||
// link has to be in the URLs they open
|
||||
func TestMetadataPassword(t *testing.T) {
|
||||
var r = httptest.NewRequest("GET", "/d/abc/a%20b.png?password=p%26w+1", nil)
|
||||
var og = (&WebController{}).metadataFromFilesystem(r, api.FilesystemPath{
|
||||
Path: []api.FilesystemNode{{Path: "/abc/a b.png", FileType: "image/png"}},
|
||||
})
|
||||
for _, prop := range og.MetaPropRules {
|
||||
if (prop.Key == "og:url" || prop.Key == "og:image") &&
|
||||
!strings.HasSuffix(prop.Value, "/abc/a%20b.png?password=p%26w+1") {
|
||||
t.Errorf("%s is %s", prop.Key, prop.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user