add auth, new password per session
This commit is contained in:
+11
-34
@@ -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
@@ -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
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -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
@@ -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>';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user