Translate all user-facing output to English
- Scripts: start-webdav.cmd, stop-webdav.cmd (echo messages, REM comments) - Server: server.js (console.log, HTTP error messages) - Token tools: token-test.js, token-refresh.js - Other: auth-poc.js, debug-name-decrypt.js, internxt-client.js, upload.js - Docs: README, .env.example, docs/*.md Made-with: Cursor
This commit is contained in:
@@ -21,7 +21,7 @@ const password = process.env.INXT_PASSWORD;
|
||||
const twoFactorCode = process.env.INXT_2FA || '';
|
||||
|
||||
if (!email || !password) {
|
||||
console.error('Fehler: INXT_EMAIL und INXT_PASSWORD müssen gesetzt sein.');
|
||||
console.error('Error: INXT_EMAIL and INXT_PASSWORD must be set.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ const apiSecurity = {
|
||||
};
|
||||
|
||||
async function main() {
|
||||
console.log('Internxt Auth PoC - Login mit clientName "drive-web"');
|
||||
console.log('Internxt Auth PoC - Login with clientName "drive-web"');
|
||||
console.log('API:', DRIVE_API_URL);
|
||||
console.log('E-Mail:', email);
|
||||
console.log('2FA:', twoFactorCode ? '***' + twoFactorCode.slice(-2) : '(nicht gesetzt)');
|
||||
console.log('Email:', email);
|
||||
console.log('2FA:', twoFactorCode ? '***' + twoFactorCode.slice(-2) : '(not set)');
|
||||
console.log('');
|
||||
|
||||
const authClient = Auth.client(DRIVE_API_URL, appDetails, apiSecurity);
|
||||
@@ -84,9 +84,9 @@ async function main() {
|
||||
const details = await authClient.securityDetails(email.toLowerCase());
|
||||
const salt = decryptText(details.encryptedSalt);
|
||||
const isHex = /^[0-9a-f]+$/i.test(salt);
|
||||
console.log('DEBUG: Salt-Decryption OK, Format:', isHex ? 'Hex' : 'anderes');
|
||||
console.log('DEBUG: Salt decryption OK, format:', isHex ? 'Hex' : 'other');
|
||||
} catch (e) {
|
||||
console.error('DEBUG: Salt-Decryption fehlgeschlagen - CRYPTO_SECRET evtl. falsch:', e.message);
|
||||
console.error('DEBUG: Salt decryption failed - CRYPTO_SECRET may be wrong:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,27 +100,27 @@ async function main() {
|
||||
cryptoProvider
|
||||
);
|
||||
|
||||
console.log('Login erfolgreich!');
|
||||
console.log('Login successful!');
|
||||
console.log('Token:', result.newToken?.substring(0, 20) + '...');
|
||||
console.log('User:', result.user?.email);
|
||||
console.log('');
|
||||
console.log('Der WebDAV-Wrapper kann mit dieser Auth gebaut werden.');
|
||||
console.log('The WebDAV wrapper can be built with this auth.');
|
||||
} catch (err) {
|
||||
console.error('Login fehlgeschlagen:', err.message);
|
||||
console.error('Login failed:', err.message);
|
||||
if (err.response?.data) {
|
||||
console.error('Response:', JSON.stringify(err.response.data, null, 2));
|
||||
}
|
||||
if (err.message?.includes('cli access not allowed') || err.message?.includes('rclone access not allowed')) {
|
||||
console.error('');
|
||||
console.error('Hinweis: Dieser Fehler sollte mit clientName "drive-web" NICHT auftreten.');
|
||||
console.error('Note: This error should NOT occur with clientName "drive-web".');
|
||||
}
|
||||
if (err.message?.includes('Wrong login credentials')) {
|
||||
console.error('');
|
||||
console.error('Mögliche Ursachen:');
|
||||
console.error('1. CRYPTO_SECRET falsch - drive-web nutzt REACT_APP_CRYPTO_SECRET (evtl. anderer Wert)');
|
||||
console.error(' -> DEBUG=1 setzen und erneut ausführen, um Salt-Decryption zu prüfen');
|
||||
console.error('2. 2FA-Code abgelaufen (30s gültig) - neuen Code eingeben');
|
||||
console.error('3. Passwort/E-Mail falsch');
|
||||
console.error('Possible causes:');
|
||||
console.error('1. CRYPTO_SECRET wrong - drive-web uses REACT_APP_CRYPTO_SECRET (possibly different value)');
|
||||
console.error(' -> Set DEBUG=1 and run again to verify salt decryption');
|
||||
console.error('2. 2FA code expired (valid 30s) - enter new code');
|
||||
console.error('3. Password/email incorrect');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -15,13 +15,13 @@ const DRIVE_API_URL = process.env.DRIVE_API_URL || 'https://gateway.internxt.com
|
||||
const token = process.env.INXT_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
console.error('INXT_TOKEN fehlt');
|
||||
console.error('INXT_TOKEN missing');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const secret = process.env.CRYPTO_SECRET2 || process.env.CRYPTO_SECRET;
|
||||
if (!secret) {
|
||||
console.error('CRYPTO_SECRET oder CRYPTO_SECRET2 fehlt in .env');
|
||||
console.error('CRYPTO_SECRET or CRYPTO_SECRET2 missing in .env');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ const appDetails = { clientName: 'drive-web', clientVersion: '1.0' };
|
||||
const apiSecurity = {
|
||||
token,
|
||||
unauthorizedCallback: () => {
|
||||
throw new Error('Token ungültig');
|
||||
throw new Error('Token invalid');
|
||||
},
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ async function main() {
|
||||
const rootUuid = user?.rootFolderUuid || user?.rootFolderId;
|
||||
|
||||
if (!rootUuid) {
|
||||
console.error('Root-Ordner nicht gefunden');
|
||||
console.error('Root folder not found');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -58,15 +58,15 @@ async function main() {
|
||||
const folders = content?.children || [];
|
||||
const files = content?.files || [];
|
||||
|
||||
console.log('=== Namensentschlüsselung in', path, '===');
|
||||
console.log('CRYPTO_SECRET2/CRYPTO_SECRET:', secret ? secret.substring(0, 4) + '***' : '(nicht gesetzt)');
|
||||
console.log('=== Name decryption in', path, '===');
|
||||
console.log('CRYPTO_SECRET2/CRYPTO_SECRET:', secret ? secret.substring(0, 4) + '***' : '(not set)');
|
||||
console.log('');
|
||||
|
||||
for (const c of folders) {
|
||||
const plain = getPlainName(c.name, c.plain_name ?? c.plainName, c.parent_id ?? c.parentId, null);
|
||||
const ok = plain !== c.name && plain.length > 0 && !/^[A-Za-z0-9+/=]{20,}$/.test(plain);
|
||||
console.log('Ordner:', ok ? '✓' : '✗', plain);
|
||||
console.log(' verschlüsselt:', c.name?.substring(0, 50) + '...');
|
||||
console.log('Folder:', ok ? '✓' : '✗', plain);
|
||||
console.log(' encrypted:', c.name?.substring(0, 50) + '...');
|
||||
console.log(' parent_id:', c.parent_id ?? c.parentId);
|
||||
console.log('');
|
||||
}
|
||||
@@ -74,14 +74,14 @@ async function main() {
|
||||
for (const f of files) {
|
||||
const plain = getPlainName(f.name, f.plain_name ?? f.plainName, null, f.folder_id ?? f.folderId);
|
||||
const ok = plain !== f.name && plain.length > 0 && !/^[A-Za-z0-9+/=]{20,}$/.test(plain);
|
||||
console.log('Datei:', ok ? '✓' : '✗', plain);
|
||||
console.log(' verschlüsselt:', f.name?.substring(0, 50) + '...');
|
||||
console.log('File:', ok ? '✓' : '✗', plain);
|
||||
console.log(' encrypted:', f.name?.substring(0, 50) + '...');
|
||||
console.log(' folder_id:', f.folder_id ?? f.folderId);
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (folders.length === 0 && files.length === 0) {
|
||||
console.log('(Leerer Ordner)');
|
||||
console.log('(Empty folder)');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ export function createClients(token) {
|
||||
const apiSecurity = {
|
||||
token,
|
||||
unauthorizedCallback: () => {
|
||||
throw new Error('Token abgelaufen oder ungültig');
|
||||
throw new Error('Token expired or invalid');
|
||||
},
|
||||
};
|
||||
return {
|
||||
|
||||
118
src/server.js
118
src/server.js
@@ -20,7 +20,7 @@ const token = process.env.INXT_TOKEN;
|
||||
const mnemonic = process.env.INXT_MNEMONIC;
|
||||
|
||||
if (!token) {
|
||||
console.error('Fehler: INXT_TOKEN muss gesetzt sein. Siehe docs/browser-token-auth.md');
|
||||
console.error('Error: INXT_TOKEN must be set. See docs/browser-token-auth.md');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ function basicAuth(req, res, next) {
|
||||
const auth = req.headers?.authorization;
|
||||
if (!auth || !auth.startsWith('Basic ')) {
|
||||
res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
|
||||
res.status(401).send('Authentifizierung erforderlich');
|
||||
res.status(401).send('Authentication required');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -98,13 +98,13 @@ function basicAuth(req, res, next) {
|
||||
pass = colon >= 0 ? decoded.slice(colon + 1) : '';
|
||||
} catch (_) {
|
||||
res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
|
||||
res.status(401).send('Ungültige Credentials');
|
||||
res.status(401).send('Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
if (authStrict && (user !== webdavUser || pass !== webdavPass)) {
|
||||
res.set('WWW-Authenticate', 'Basic realm="Internxt WebDAV"');
|
||||
res.status(401).send('Ungültige Credentials');
|
||||
res.status(401).send('Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ async function handlePropfind(req, res) {
|
||||
const user = refresh.user;
|
||||
const rootUuid = user?.rootFolderUuid || user?.rootFolderId || user?.root_folder_id;
|
||||
if (!rootUuid) {
|
||||
res.status(500).send('Root-Ordner nicht gefunden');
|
||||
res.status(500).send('Root folder not found');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -262,7 +262,7 @@ async function handlePropfind(req, res) {
|
||||
|
||||
const listing = await listFolder(storage, rootUuid, path);
|
||||
if (!listing) {
|
||||
res.status(404).send('Nicht gefunden');
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ async function handlePropfind(req, res) {
|
||||
// Kinder bei depth 1
|
||||
if (depth !== '0') {
|
||||
for (const f of listing.folders) {
|
||||
const safeName = sanitizeForPath(f.name) || 'Unbenannt';
|
||||
const safeName = sanitizeForPath(f.name) || 'Unnamed';
|
||||
const childPath = path === '/' ? '/' + safeName : path + '/' + safeName;
|
||||
items.push({
|
||||
path: childPath,
|
||||
@@ -293,7 +293,7 @@ async function handlePropfind(req, res) {
|
||||
});
|
||||
}
|
||||
for (const f of listing.files) {
|
||||
const rawName = sanitizeForPath(f.name) || 'Unbenannt';
|
||||
const rawName = sanitizeForPath(f.name) || 'Unnamed';
|
||||
const useUuidPath = /[+=]/.test(rawName) || rawName.length > 80;
|
||||
const pathSegment = useUuidPath ? `_.${f.uuid}` : rawName;
|
||||
const childPath = path === '/' ? '/' + pathSegment : path + '/' + pathSegment;
|
||||
@@ -313,12 +313,12 @@ async function handlePropfind(req, res) {
|
||||
res.set('Content-Type', 'application/xml; charset="utf-8"');
|
||||
res.status(207).send(xml);
|
||||
} catch (err) {
|
||||
console.error('PROPFIND Fehler:', err.message);
|
||||
console.error('PROPFIND error:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token abgelaufen. Neu einloggen: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Token expired. Log in again: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +330,7 @@ async function getContext() {
|
||||
const refresh = await refreshUser(token);
|
||||
const user = refresh.user;
|
||||
const rootUuid = user?.rootFolderUuid || user?.rootFolderId || user?.root_folder_id;
|
||||
if (!rootUuid) throw new Error('Root-Ordner nicht gefunden');
|
||||
if (!rootUuid) throw new Error('Root folder not found');
|
||||
return { storage, rootUuid };
|
||||
}
|
||||
|
||||
@@ -374,14 +374,14 @@ async function handleMkcol(req, res) {
|
||||
} catch (_) {}
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht erstellt werden');
|
||||
res.status(403).send('Root cannot be created');
|
||||
return;
|
||||
}
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
|
||||
const segments = pathToSegments(path);
|
||||
if (segments.length === 0) {
|
||||
res.status(403).send('Root bereits vorhanden');
|
||||
res.status(403).send('Root already exists');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ async function handleMkcol(req, res) {
|
||||
? await ensureFolderExists(storage, rootUuid, parentPath)
|
||||
: { uuid: rootUuid };
|
||||
if (!parent) {
|
||||
res.status(409).send('Übergeordneter Ordner existiert nicht');
|
||||
res.status(409).send('Parent folder does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ async function handleMkcol(req, res) {
|
||||
res.status(201).send();
|
||||
return;
|
||||
}
|
||||
res.status(405).send('Ressource existiert bereits (kein Ordner)');
|
||||
res.status(405).send('Resource already exists (not a folder)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -420,12 +420,12 @@ async function handleMkcol(req, res) {
|
||||
res.status(201).send();
|
||||
return;
|
||||
}
|
||||
console.error('MKCOL Fehler:', err.message);
|
||||
console.error('MKCOL error:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Refresh token: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +440,7 @@ async function handleDelete(req, res) {
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht gelöscht werden');
|
||||
res.status(403).send('Root cannot be deleted');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -448,7 +448,7 @@ async function handleDelete(req, res) {
|
||||
const { storage, rootUuid } = await getContext();
|
||||
const resource = await resolveResource(storage, rootUuid, path);
|
||||
if (!resource) {
|
||||
res.status(404).send('Nicht gefunden');
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -459,12 +459,12 @@ async function handleDelete(req, res) {
|
||||
}
|
||||
res.status(204).send();
|
||||
} catch (err) {
|
||||
console.error('DELETE Fehler:', err.message);
|
||||
console.error('DELETE error:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Refresh token: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ async function handleMove(req, res) {
|
||||
let path = req.url || '/';
|
||||
const destinationHeader = req.headers['destination'];
|
||||
if (!destinationHeader) {
|
||||
res.status(400).send('Destination-Header fehlt');
|
||||
res.status(400).send('Destination header missing');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -485,7 +485,7 @@ async function handleMove(req, res) {
|
||||
if (!path.startsWith('/')) path = '/' + path;
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht verschoben werden');
|
||||
res.status(403).send('Root cannot be moved');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -494,7 +494,7 @@ async function handleMove(req, res) {
|
||||
const destUrl = new URL(destinationHeader);
|
||||
destPath = decodeURIComponent(destUrl.pathname || '/');
|
||||
} catch (_) {
|
||||
res.status(400).send('Ungültige Destination-URL');
|
||||
res.status(400).send('Invalid destination URL');
|
||||
return;
|
||||
}
|
||||
if (!destPath.startsWith('/')) destPath = '/' + destPath;
|
||||
@@ -506,7 +506,7 @@ async function handleMove(req, res) {
|
||||
const { storage, rootUuid } = await getContext();
|
||||
const source = await resolveResource(storage, rootUuid, path);
|
||||
if (!source) {
|
||||
res.status(404).send('Quelle nicht gefunden');
|
||||
res.status(404).send('Source not found');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -516,14 +516,14 @@ async function handleMove(req, res) {
|
||||
|
||||
const destParent = await resolveFolder(storage, rootUuid, destParentPath);
|
||||
if (!destParent) {
|
||||
res.status(409).send('Zielordner existiert nicht');
|
||||
res.status(409).send('Destination folder does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
const existingDest = await resolveResource(storage, rootUuid, destPath);
|
||||
if (existingDest) {
|
||||
if (!overwrite) {
|
||||
res.status(412).send('Ziel existiert, Overwrite nicht erlaubt');
|
||||
res.status(412).send('Destination exists, overwrite not allowed');
|
||||
return;
|
||||
}
|
||||
if (existingDest.type === 'folder') {
|
||||
@@ -545,12 +545,12 @@ async function handleMove(req, res) {
|
||||
}
|
||||
res.status(201).send();
|
||||
} catch (err) {
|
||||
console.error('MOVE Fehler:', err.message);
|
||||
console.error('MOVE error:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Refresh token: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
res.status(500).send(err.message || 'Interner Fehler');
|
||||
res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -563,12 +563,12 @@ async function handleGet(req, res) {
|
||||
if (path.endsWith('/')) path = path.slice(0, -1);
|
||||
path = sanitizeForPath(path);
|
||||
if (path === '/') {
|
||||
res.status(405).send('Verzeichnis kann nicht heruntergeladen werden');
|
||||
res.status(405).send('Directory cannot be downloaded');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mnemonic) {
|
||||
res.status(500).send('INXT_MNEMONIC fehlt für Datei-Entschlüsselung');
|
||||
res.status(500).send('INXT_MNEMONIC required for file decryption');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -580,15 +580,15 @@ async function handleGet(req, res) {
|
||||
if (resource) logToFile('GET cache hit', path);
|
||||
}
|
||||
if (!resource) {
|
||||
res.status(404).send('Nicht gefunden');
|
||||
res.status(404).send('Not found');
|
||||
return;
|
||||
}
|
||||
if (resource.type !== 'file') {
|
||||
res.status(405).send('Keine Datei');
|
||||
res.status(405).send('Not a file');
|
||||
return;
|
||||
}
|
||||
if (!resource.bucket || !resource.fileId) {
|
||||
res.status(404).send('Datei hat keinen Inhalt (leere Datei)');
|
||||
res.status(404).send('File has no content (empty file)');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -598,7 +598,7 @@ async function handleGet(req, res) {
|
||||
const bridgePass = user?.userId;
|
||||
|
||||
if (!bridgeUser || !bridgePass) {
|
||||
res.status(500).send('Bridge-Credentials fehlen');
|
||||
res.status(500).send('Bridge credentials missing');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -617,12 +617,12 @@ async function handleGet(req, res) {
|
||||
else res.destroy();
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('GET Fehler:', err.message);
|
||||
console.error('GET error:', err.message);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Refresh token: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
if (!res.headersSent) res.status(500).send(err.message || 'Interner Fehler');
|
||||
if (!res.headersSent) res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -660,12 +660,12 @@ async function handleHead(req, res) {
|
||||
|
||||
/** Parst Dateiname in plainName + Typ (Extension) */
|
||||
function parseFileName(name) {
|
||||
if (!name || typeof name !== 'string') return { plainName: 'Unbenannt', type: '' };
|
||||
if (!name || typeof name !== 'string') return { plainName: 'Unnamed', type: '' };
|
||||
const s = sanitizeForPath(name);
|
||||
const lastDot = s.lastIndexOf('.');
|
||||
if (lastDot <= 0) return { plainName: s || 'Unbenannt', type: '' };
|
||||
if (lastDot <= 0) return { plainName: s || 'Unnamed', type: '' };
|
||||
return {
|
||||
plainName: s.slice(0, lastDot) || 'Unbenannt',
|
||||
plainName: s.slice(0, lastDot) || 'Unnamed',
|
||||
type: s.slice(lastDot + 1).toLowerCase() || '',
|
||||
};
|
||||
}
|
||||
@@ -684,18 +684,18 @@ async function handlePut(req, res) {
|
||||
}
|
||||
|
||||
if (path === '/') {
|
||||
res.status(403).send('Root kann nicht überschrieben werden');
|
||||
res.status(403).send('Root cannot be overwritten');
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = req.body;
|
||||
if (!Buffer.isBuffer(buffer)) {
|
||||
res.status(400).send('Kein Dateiinhalt erhalten');
|
||||
res.status(400).send('No file content received');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mnemonic) {
|
||||
res.status(500).send('INXT_MNEMONIC fehlt für Datei-Verschlüsselung');
|
||||
res.status(500).send('INXT_MNEMONIC required for file encryption');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -712,7 +712,7 @@ async function handlePut(req, res) {
|
||||
parent = await ensureFolderExists(storage, rootUuid, parentPath);
|
||||
}
|
||||
if (!parent) {
|
||||
res.status(409).send('Zielordner existiert nicht');
|
||||
res.status(409).send('Destination folder does not exist');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -721,7 +721,7 @@ async function handlePut(req, res) {
|
||||
if (existing.type === 'file') {
|
||||
await storage.deleteFileByUuid(existing.uuid);
|
||||
} else {
|
||||
res.status(409).send('Ziel ist ein Ordner');
|
||||
res.status(409).send('Destination is a folder');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -736,7 +736,7 @@ async function handlePut(req, res) {
|
||||
const bucketId = user?.bucket;
|
||||
|
||||
if (!bridgeUser || !bridgePass || !bucketId) {
|
||||
res.status(500).send('Bridge-Credentials oder Bucket fehlen');
|
||||
res.status(500).send('Bridge credentials or bucket missing');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -752,7 +752,7 @@ async function handlePut(req, res) {
|
||||
buffer: uploadBuffer,
|
||||
});
|
||||
} catch (uploadErr) {
|
||||
logError('PUT Upload (Bridge) fehlgeschlagen', path, uploadErr.message);
|
||||
logError('PUT Upload (Bridge) failed', path, uploadErr.message);
|
||||
throw uploadErr;
|
||||
}
|
||||
logToFile('PUT Upload OK', path);
|
||||
@@ -780,7 +780,7 @@ async function handlePut(req, res) {
|
||||
const fullName = type ? `${plainName}.${type}` : plainName;
|
||||
cacheRecentFile(path, { type: 'file', bucket: bucketId, fileId, name: fullName, size: buffer.length });
|
||||
} catch (createErr) {
|
||||
logError('PUT createFileEntry fehlgeschlagen', path, createErr.message);
|
||||
logError('PUT createFileEntry failed', path, createErr.message);
|
||||
// "File already exists" – Datei per Namen löschen und erneut versuchen
|
||||
if (createErr?.message?.toLowerCase().includes('already exists')) {
|
||||
const [contentPromise] = storage.getFolderContentByUuid({ folderUuid: parent.uuid });
|
||||
@@ -809,13 +809,13 @@ async function handlePut(req, res) {
|
||||
const apiErr = err.response?.data ? JSON.stringify(err.response.data) : '';
|
||||
const status = err.response?.status;
|
||||
if (LOG_ERROR) logError('Stack:', err.stack);
|
||||
console.error('PUT Fehler:', path, err.message, status ? `HTTP ${status}` : '', apiErr || '');
|
||||
console.error('PUT error:', path, err.message, status ? `HTTP ${status}` : '', apiErr || '');
|
||||
if (LOG_DEBUG) console.error(err.stack);
|
||||
if (err.message?.includes('Token') || err.response?.status === 401) {
|
||||
res.status(401).send('Nicht autorisiert – Token erneuern: https://drive.internxt.com');
|
||||
res.status(401).send('Unauthorized – Refresh token: https://drive.internxt.com');
|
||||
return;
|
||||
}
|
||||
if (!res.headersSent) res.status(500).send(err.message || 'Interner Fehler');
|
||||
if (!res.headersSent) res.status(500).send(err.message || 'Internal error');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -879,7 +879,7 @@ app.use((req, res, next) => {
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Internxt WebDAV Server – http://127.0.0.1:${PORT}`);
|
||||
console.log('Phase 1–4: PROPFIND, MKCOL, DELETE, MOVE, GET, PUT aktiv.');
|
||||
console.log(`rclone/restic: URL muss http://127.0.0.1:${PORT} sein (gleicher Port!)`);
|
||||
console.log('Verwendung: z.B. Windows Explorer → Netzlaufwerk verbinden');
|
||||
console.log('Phase 1–4: PROPFIND, MKCOL, DELETE, MOVE, GET, PUT active.');
|
||||
console.log(`rclone/restic: URL must be http://127.0.0.1:${PORT} (same port!)`);
|
||||
console.log('Usage: e.g. Windows Explorer → Map network drive');
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ function updateEnv(token, mnemonic) {
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Starte Browser – bitte auf', DRIVE_URL, 'einloggen.\n');
|
||||
console.log('Starting browser – please log in at', DRIVE_URL, '\n');
|
||||
|
||||
const browser = await puppeteer.launch({
|
||||
headless: false,
|
||||
@@ -76,14 +76,14 @@ async function main() {
|
||||
const { token, mnemonic } = await getTokens(page);
|
||||
if (token && mnemonic) {
|
||||
updateEnv(token, mnemonic);
|
||||
console.log('\n.env aktualisiert. Server neu starten.\n');
|
||||
console.log('\n.env updated. Restart the server.\n');
|
||||
await browser.close();
|
||||
return;
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, POLL_MS));
|
||||
}
|
||||
|
||||
console.log('Timeout – keine Tokens gefunden. Bitte einloggen und erneut ausführen.');
|
||||
console.log('Timeout – no tokens found. Please log in and run again.');
|
||||
await browser.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -14,18 +14,18 @@ const DRIVE_API_URL = process.env.DRIVE_API_URL || 'https://gateway.internxt.com
|
||||
const token = process.env.INXT_TOKEN;
|
||||
|
||||
if (!token) {
|
||||
console.error('Fehler: INXT_TOKEN muss gesetzt sein (aus Browser localStorage)');
|
||||
console.error('Error: INXT_TOKEN must be set (from browser localStorage)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const appDetails = { clientName: 'drive-web', clientVersion: '1.0' };
|
||||
const apiSecurity = {
|
||||
token,
|
||||
unauthorizedCallback: () => console.error('Token abgelaufen oder ungültig'),
|
||||
unauthorizedCallback: () => console.error('Token expired or invalid'),
|
||||
};
|
||||
|
||||
async function main() {
|
||||
console.log('Token-Test – Drive API mit Browser-Token');
|
||||
console.log('Token test – Drive API with browser token');
|
||||
console.log('');
|
||||
|
||||
const usersClient = Users.client(DRIVE_API_URL, appDetails, apiSecurity);
|
||||
@@ -42,15 +42,15 @@ async function main() {
|
||||
if (rootUuid) {
|
||||
const [content] = storageClient.getFolderContentByUuid({ folderUuid: rootUuid });
|
||||
const folderContent = await content;
|
||||
console.log('Dateien/Ordner im Root:', folderContent.children?.length ?? 0);
|
||||
console.log('Files/folders in root:', folderContent.children?.length ?? 0);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('Token funktioniert – WebDAV-Server kann gestartet werden.');
|
||||
console.log('Token works – WebDAV server can be started.');
|
||||
} catch (err) {
|
||||
console.error('Fehler:', err.message);
|
||||
console.error('Error:', err.message);
|
||||
if (err.response?.status === 401) {
|
||||
console.error('Token abgelaufen – bitte erneut auf drive.internxt.com einloggen und Token aktualisieren.');
|
||||
console.error('Token expired – please log in again at drive.internxt.com and update token.');
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ async function uploadToUrl(buffer, url, signal) {
|
||||
body: buffer,
|
||||
signal,
|
||||
});
|
||||
if (!res.ok) throw new Error(`Upload fehlgeschlagen: ${res.status}`);
|
||||
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,7 +58,7 @@ export async function uploadFileBuffer(params) {
|
||||
const fileSize = buffer.length;
|
||||
|
||||
if (!validateMnemonic(mnemonic)) {
|
||||
throw new Error('Ungültiges Mnemonic');
|
||||
throw new Error('Invalid mnemonic');
|
||||
}
|
||||
|
||||
const auth = await getAuth(bridgeUser, bridgePass);
|
||||
|
||||
Reference in New Issue
Block a user