UNPKG

@uppy/companion

Version:

OAuth helper and remote fetcher for Uppy's (https://uppy.io) extensible file upload widget with support for drag&drop, resumable uploads, previews, restrictions, file processing/encoding, remote providers like Dropbox and Google Drive, S3 and more :dog:

181 lines (156 loc) 5.07 kB
import { AuthType, createClient } from 'webdav' import { getProtectedHttpAgent, validateURL } from '../../helpers/request.js' import logger from '../../logger.js' import { ProviderApiError, ProviderAuthError, ProviderUserError, } from '../error.js' import Provider from '../Provider.js' const defaultDirectory = '/' /** * Adapter for WebDAV servers that support simple auth (non-OAuth). */ export default class WebdavProvider extends Provider { static get hasSimpleAuth() { return true } isAuthenticated({ providerUserSession }) { return providerUserSession.webdavUrl != null } async getClient({ providerUserSession }) { const webdavUrl = providerUserSession?.webdavUrl const { allowLocalUrls } = this if (!validateURL(webdavUrl, allowLocalUrls)) { throw new Error('invalid public link url') } // Is this an ownCloud or Nextcloud public link URL? e.g. https://example.com/s/kFy9Lek5sm928xP // they have specific urls that we can identify // todo not sure if this is the right way to support nextcloud and other webdavs if (/\/s\/([^/]+)/.test(webdavUrl)) { const [baseURL, publicLinkToken] = webdavUrl.split('/s/') return this.getClientHelper({ url: `${baseURL.replace('/index.php', '')}/public.php/webdav/`, authType: AuthType.Password, username: publicLinkToken, password: 'null', }) } // normal public WebDAV urls return this.getClientHelper({ url: webdavUrl, authType: AuthType.None, }) } async logout() { return { revoked: true } } async simpleAuth({ requestBody }) { try { const providerUserSession = { webdavUrl: requestBody.form.webdavUrl } const client = await this.getClient({ providerUserSession }) // call the list operation as a way to validate the url await client.getDirectoryContents(defaultDirectory) return providerUserSession } catch (err) { logger.error(err, 'provider.webdav.error') if (['ECONNREFUSED', 'ENOTFOUND'].includes(err.code)) { throw new ProviderUserError({ message: 'Cannot connect to server' }) } throw err } } async getClientHelper({ url, ...options }) { const { allowLocalUrls } = this if (!validateURL(url, allowLocalUrls)) { throw new Error('invalid webdav url') } const { protocol } = new URL(url) const HttpAgentClass = getProtectedHttpAgent({ protocol, allowLocalIPs: !allowLocalUrls, }) return createClient(url, { ...options, [`${protocol}Agent`]: new HttpAgentClass(), }) } async list({ directory, providerUserSession }) { return this.withErrorHandling('provider.webdav.list.error', async () => { // @ts-ignore if (!this.isAuthenticated({ providerUserSession })) { throw new ProviderAuthError() } const data = { items: [] } const client = await this.getClient({ providerUserSession }) /** @type {any} */ const dir = await client.getDirectoryContents(directory || '/') dir.forEach((item) => { const isFolder = item.type === 'directory' const requestPath = encodeURIComponent( `${directory || ''}/${item.basename}`, ) let modifiedDate try { modifiedDate = new Date(item.lastmod).toISOString() } catch (_e) { // ignore invalid date from server } // Determine icon based on type and MIME type let icon = 'file' if (isFolder) { icon = 'folder' } else if (item.mime?.startsWith('video/')) { icon = 'video' } data.items.push({ isFolder, icon, id: requestPath, name: item.basename, modifiedDate, requestPath, ...(!isFolder && { mimeType: item.mime, size: item.size, thumbnail: null, }), }) }) return data }) } async download({ id, providerUserSession }) { return this.withErrorHandling( 'provider.webdav.download.error', async () => { const client = await this.getClient({ providerUserSession }) /** @type {any} */ const stat = await client.stat(id) const stream = client.createReadStream(`/${id}`) return { stream, size: stat.size } }, ) } async thumbnail({ id, providerUserSession }) { // not implementing this because a public thumbnail from webdav will be used instead logger.error( 'call to thumbnail is not implemented', 'provider.webdav.thumbnail.error', ) throw new Error('call to thumbnail is not implemented') } async withErrorHandling(tag, fn) { try { return await fn() } catch (err) { let err2 = err if (err.status === 401) err2 = new ProviderAuthError() if (err.response) { err2 = new ProviderApiError('WebDAV API error', err.status) } logger.error(err2, tag) throw err2 } } }