add auth, new password per session

This commit is contained in:
FTCHD
2025-01-28 13:24:55 +02:00
parent f587983405
commit 3b850d6467
5 changed files with 298 additions and 299 deletions
+11 -34
View File
@@ -5,8 +5,8 @@ import { sendNotification } from '@tauri-apps/plugin-notification'
import { revealItemInDir } from '@tauri-apps/plugin-opener'
import { exit } from '@tauri-apps/plugin-process'
import { isDirectoryEmpty } from './fs'
import { deleteRemote, mountRemote, unmountRemote } from './rclone/api'
import { dialogGetMountPlugin, needsMountPlugin } from './rclone/mount'
import { deleteRemote, mountRemote } from './rclone/api'
import { dialogGetMountPlugin, needsMountPlugin, unmountRemote } from './rclone/mount'
import { usePersistedStore, useStore } from './store'
import { getLoadingTray, getMainTray, rebuildTrayMenu } from './tray'
import { lockWindows, openFullWindow, openTrayWindow, openWindow, unlockWindows } from './window'
@@ -21,6 +21,14 @@ export async function buildMenu() {
const menuItems: (MenuItem | Submenu | PredefinedMenuItem)[] = []
if (remotes.length === 0) {
const noRemotesMenuItem = await MenuItem.new({
id: 'no-remotes',
text: 'No Remotes',
enabled: false,
})
menuItems.push(noRemotesMenuItem)
} else {
// Add remote submenus
for (const remote of remotes) {
const remoteConfig = persistedStoreState.remoteConfigList?.[remote]
@@ -205,7 +213,6 @@ export async function buildMenu() {
// await openBrowser(`http://localhost:5572/[${remote}:]/`)
await openFullWindow({
name: 'Browse',
// url: 'browse.html?url=https%3A%2F%2Fwww.google.com%2F',
url:
'browse.html?url=' +
encodeURIComponent(`http://localhost:5572/[${remote}:]/`),
@@ -243,6 +250,7 @@ export async function buildMenu() {
menuItems.push(sub)
}
}
await PredefinedMenuItem.new({
item: 'Separator',
@@ -320,7 +328,6 @@ export async function buildMenu() {
})
},
})
menuItems.push(settingsItem)
const quitItem = await MenuItem.new({
@@ -330,40 +337,10 @@ export async function buildMenu() {
await exit(0)
},
})
menuItems.push(quitItem)
const testItem = await MenuItem.new({
id: 'test',
text: 'Test',
action: async () => {
await openWindow({
name: 'Test',
url: '/test',
width: 400,
height: 400,
})
},
})
menuItems.push(testItem)
const test2Item = await MenuItem.new({
id: 'test2',
text: 'Test2',
action: async () => {
await openWindow({
name: 'Test2',
url: '/test',
width: 400,
height: 400,
})
},
})
menuItems.push(test2Item)
return await Menu.new({
id: 'main-menu',
items: menuItems,
})
}
+41 -36
View File
@@ -1,12 +1,29 @@
import { ask } from '@tauri-apps/plugin-dialog'
import { fetch } from '@tauri-apps/plugin-http'
import { Command } from '@tauri-apps/plugin-shell'
import { platform } from '@tauri-apps/plugin-os'
import { useStore } from '../store'
/* UTILS */
const SUPPORRTED_BACKENDS = ['sftp', 's3', 'b2', 'drive']
function getAuthHeader() {
if (platform() === 'macos') {
return
}
const state = useStore.getState()
if (!state.rcloneAuthHeader) {
throw new Error('Rclone auth header is not set')
}
return { Authorization: state.rcloneAuthHeader }
}
/* DATA */
export async function listRemotes() {
const r = await fetch('http://localhost:5572/config/listremotes', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<{ remotes: string[] }>)
// .catch((e) => {
// console.log("error", e);
@@ -23,6 +40,7 @@ export async function listRemotes() {
export async function getRemote(remote: string) {
const r = await fetch(`http://localhost:5572/config/get?name=${remote}`, {
method: 'POST',
headers: getAuthHeader(),
}).then(
(res) => res.json() as Promise<{ type: string } & Record<string, string | number | boolean>>
)
@@ -48,6 +66,7 @@ export async function updateRemote(
await fetch(`http://localhost:5572/config/update?${options.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
})
// console.log(JSON.stringify(r, null, 2))
@@ -67,6 +86,7 @@ export async function createRemote(
await fetch(`http://localhost:5572/config/create?${options.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
})
// console.log(JSON.stringify(r, null, 2))
@@ -75,6 +95,7 @@ export async function createRemote(
export async function deleteRemote(remote: string) {
await fetch(`http://localhost:5572/config/delete?name=${remote}`, {
method: 'POST',
headers: getAuthHeader(),
})
// console.log(JSON.stringify(r, null, 2))
@@ -83,6 +104,7 @@ export async function deleteRemote(remote: string) {
export async function getMountPoints() {
const r = await fetch('http://localhost:5572/mount/listmounts', {
method: 'POST',
headers: getAuthHeader(),
}).then(
(res) =>
res.json() as Promise<{
@@ -99,11 +121,10 @@ export async function getMountPoints() {
return r.mountPoints
}
const SUPPORRTED_BACKENDS = ['sftp', 's3', 'b2', 'drive']
export async function getBackends() {
const providers = await fetch('http://localhost:5572/config/providers', {
method: 'POST',
headers: getAuthHeader(),
})
.then((res) => res.json() as Promise<any>)
.then((r) => r.providers)
@@ -142,6 +163,7 @@ export async function listPath(remote: string, path: string = '', options: ListO
const response = await fetch(`http://localhost:5572/operations/list?${params.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
}).then(
(res) =>
res.json() as Promise<{
@@ -170,12 +192,14 @@ export async function listPath(remote: string, path: string = '', options: ListO
export async function listJobs() {
const allStats = await fetch('http://localhost:5572/core/stats', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as any)
const transferring = allStats?.transferring
const transferredStats = await fetch('http://localhost:5572/core/transferred', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as any)
const transferred = transferredStats?.transferred
@@ -195,6 +219,7 @@ export async function listJobs() {
for (const jobId of activeJobIds) {
const job = await fetch(`http://localhost:5572/core/stats?group=job/${jobId}`, {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
jobs.active.push({
@@ -223,6 +248,7 @@ export async function listJobs() {
for (const jobId of inactiveJobIds) {
const job = await fetch(`http://localhost:5572/core/stats?group=job/${jobId}`, {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
jobs.inactive.push({
@@ -246,11 +272,11 @@ export async function listJobs() {
export async function stopJob(jobId: number) {
await fetch(`http://localhost:5572/job/stopgroup?group=job/${jobId}`, {
method: 'POST',
headers: getAuthHeader(),
})
}
/* OPERATIONS */
export async function mountRemote({
remotePath,
mountPoint,
@@ -276,6 +302,7 @@ export async function mountRemote({
const r = await fetch(`http://localhost:5572/mount/mount?${options.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
})
.then((res) => res.json() as Promise<{ remotes: string[] } | Promise<{ error: string }>>)
.catch((e) => {
@@ -290,36 +317,6 @@ export async function mountRemote({
return
}
export async function unmountRemote(mountPoint: string, force = false) {
const command = Command.create('umount', [force ? '-f' : '', mountPoint])
const output = await command.execute()
// console.log('output')
// console.log(JSON.stringify(output, null, 2))
if (output.code !== 0) {
if (output.stderr.toLowerCase().includes('busy')) {
const answer = await ask('This resource is busy, do you want to force unmount?', {
title: 'Could not unmount',
kind: 'warning',
})
if (answer) {
return await unmountRemote(mountPoint, true)
}
throw new Error(output.stderr)
}
if (output.stderr.toLowerCase().includes('not currently mounted')) {
return
}
throw new Error(output.stderr)
}
return
}
export async function startCopy({
source,
dest,
@@ -351,6 +348,7 @@ export async function startCopy({
const r = await fetch(`http://localhost:5572/sync/copy?${params.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<{ jobid: string }>)
console.log('Copy operation started:', r)
@@ -409,6 +407,7 @@ export async function startSync({
const r = await fetch(`http://localhost:5572/sync/sync?${params.toString()}`, {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<{ jobid: string }>)
console.log('Sync operation started:', r)
@@ -441,6 +440,7 @@ export async function startSync({
export async function getGlobalFlags() {
const r = await fetch('http://localhost:5572/options/get', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
return r
@@ -449,6 +449,7 @@ export async function getGlobalFlags() {
export async function getCopyFlags() {
const r = await fetch('http://localhost:5572/options/info', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
const mainFlags = r.main
@@ -463,6 +464,7 @@ export async function getCopyFlags() {
export async function getSyncFlags() {
const r = await fetch('http://localhost:5572/options/info', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
const mainFlags = r.main
@@ -480,6 +482,7 @@ export async function getSyncFlags() {
export async function getFilterFlags() {
const r = await fetch('http://localhost:5572/options/info', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
const filterFlags = r.filter
@@ -493,6 +496,7 @@ export async function getFilterFlags() {
export async function getVfsFlags() {
const r = await fetch('http://localhost:5572/options/info', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
const vfsFlags = r.vfs
@@ -507,6 +511,7 @@ export async function getVfsFlags() {
export async function getMountFlags() {
const r = await fetch('http://localhost:5572/options/info', {
method: 'POST',
headers: getAuthHeader(),
}).then((res) => res.json() as Promise<any>)
const mountFlags = r.mount
+4
View File
@@ -28,6 +28,8 @@ interface State {
increment: () => void
rcloneLoaded: boolean
rcloneAuth: string
rcloneAuthHeader: string
mountedRemotes: Record<string, string>
serveList: { pid: number; protocol: string; remote: string }[]
@@ -73,6 +75,8 @@ export const useStore = create<State>()(
increment: () => set((state) => ({ count: state.count + 1 })),
rcloneLoaded: false,
rcloneAuth: '',
rcloneAuthHeader: '',
mountedRemotes: {},
serveList: [],
+10 -7
View File
@@ -2,13 +2,14 @@ import { getCurrentWindow } from '@tauri-apps/api/window'
import { confirm, message } from '@tauri-apps/plugin-dialog'
import {} from '@tauri-apps/plugin-fs'
import { debug, error, info, trace, warn } from '@tauri-apps/plugin-log'
import { platform } from '@tauri-apps/plugin-os'
import { exit } from '@tauri-apps/plugin-process'
import { listRemotes } from './lib/rclone/api'
import { initRclone } from './lib/rclone/init'
import { useStore } from './lib/store'
import { initLoadingTray, initTray, rebuildTrayMenu } from './lib/tray'
// forward console logs in webviews to the tauri logger, so they show up in the console
// forward console logs in webviews to the tauri logger, so they show up in terminal
function forwardConsole(
fnName: 'log' | 'debug' | 'info' | 'warn' | 'error',
logger: (message: string) => Promise<void>
@@ -28,9 +29,6 @@ forwardConsole('info', info)
forwardConsole('warn', warn)
forwardConsole('error', error)
console.log('main')
console.error('main')
async function startRclone() {
try {
const remotes = await listRemotes()
@@ -52,12 +50,19 @@ async function startRclone() {
return await exit(0)
}
const sessionPassword = Math.random().toString(36).substring(2, 15)
useStore.setState({ rcloneAuth: sessionPassword })
useStore.setState({ rcloneAuthHeader: 'Basic ' + btoa(`admin:${sessionPassword}`) })
const rcloneCommandFn = rclone.system || rclone.internal
const command = await rcloneCommandFn([
'rcd',
'--rc-no-auth',
...(platform() === 'macos'
? ['--rc-no-auth'] // webkit doesn't allow for credentials in the url
: ['--rc-user', 'admin', '--rc-pass', sessionPassword]),
'--rc-serve',
// defaults
// '-rc-addr',
// ':5572',
])
@@ -97,8 +102,6 @@ async function startRclone() {
useStore.setState({ remotes: remotes })
// console.log('childProcess', JSON.stringify(childProcess)) // prints `pid`
// console.log('command', JSON.stringify(command))
}
getCurrentWindow().listen('tauri://close-requested', async (e) => {
+14 -4
View File
@@ -267,16 +267,25 @@ It's not pretty, but at the same time Adobe's .PSD implementation exists.
}
// Function to fetch and modify webpage content
async function fetchAndInjectContent(url, isInitialLoad = false) {
async function fetchAndInjectContent(url, pass, isInitialLoad = false) {
try {
// Only show navigation spinner for non-initial loads
if (!isInitialLoad) {
toggleNavSpinner(true);
}
let newUrl = url
if (pass) {
newUrl = url.replace('localhost:5572', 'admin:' + pass + '@localhost:5572')
} else {
console.log('no pass')
}
console.log('newUrl', newUrl)
const fetch = window.__TAURI__.http.fetch
// Fetch the webpage content
const response = await fetch(url);
const response = await fetch(newUrl);
const html = await response.text();
// Create a temporary DOM parser
@@ -322,7 +331,7 @@ It's not pretty, but at the same time Adobe's .PSD implementation exists.
document.body.appendChild(navSpinner);
// Update the URL in the address bar without reloading
window.history.pushState({}, '', `?url=${encodeURIComponent(url)}`);
window.history.pushState({}, '', `?url=${encodeURIComponent(url)}` + (pass ? `&pass=${encodeURIComponent(pass)}` : ''));
// Re-add our event handlers
setupEventHandlers();
@@ -469,9 +478,10 @@ It's not pretty, but at the same time Adobe's .PSD implementation exists.
// Initial setup
const urlParams = new URLSearchParams(window.location.search);
const url = urlParams.get('url');
const pass = urlParams.get('pass');
if (url) {
// Pass true to indicate this is the initial load
fetchAndInjectContent(url, true);
fetchAndInjectContent(url, pass, true);
} else {
document.body.innerHTML = '<div>Error: No URL provided. Use ?url= parameter.</div>';
}