From b2d0c1c9ddbbfe87d5a905d420d615821610e825 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20F=C3=BCllemann?= Date: Fri, 20 Aug 2021 14:39:23 +0200 Subject: [PATCH] add support for basic auth when downloading images from URL When selecting "Flash from URL" the user can optionally provide a username and password for basic authentication. The authentication input fields are collapsed by default. When the authentication input fields are collapsed after entering values the values are cleared to ensure that the user sees all parameter passed to the server. Change-Type: minor Changelog-Entry: Add support for basic auth when downloading images from URL. --- .../source-selector/source-selector.tsx | 75 +++++++++++++++++-- 1 file changed, 68 insertions(+), 7 deletions(-) diff --git a/lib/gui/app/components/source-selector/source-selector.tsx b/lib/gui/app/components/source-selector/source-selector.tsx index 50902ed6..d3e62fdb 100644 --- a/lib/gui/app/components/source-selector/source-selector.tsx +++ b/lib/gui/app/components/source-selector/source-selector.tsx @@ -18,6 +18,8 @@ import CopySvg from '@fortawesome/fontawesome-free/svgs/solid/copy.svg'; import FileSvg from '@fortawesome/fontawesome-free/svgs/solid/file.svg'; import LinkSvg from '@fortawesome/fontawesome-free/svgs/solid/link.svg'; import ExclamationTriangleSvg from '@fortawesome/fontawesome-free/svgs/solid/exclamation-triangle.svg'; +import ChevronDownSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-down.svg'; +import ChevronRightSvg from '@fortawesome/fontawesome-free/svgs/solid/chevron-right.svg'; import { sourceDestination } from 'etcher-sdk'; import { ipcRenderer, IpcRendererEvent } from 'electron'; import * as _ from 'lodash'; @@ -33,6 +35,7 @@ import { Card as BaseCard, Input, Spinner, + Link, } from 'rendition'; import styled from 'styled-components'; @@ -134,12 +137,15 @@ const URLSelector = ({ done, cancel, }: { - done: (imageURL: string) => void; + done: (imageURL: string, auth?: Authentication) => void; cancel: () => void; }) => { const [imageURL, setImageURL] = React.useState(''); const [recentImages, setRecentImages] = React.useState([]); const [loading, setLoading] = React.useState(false); + const [showBasicAuth, setShowBasicAuth] = React.useState(false); + const [username, setUsername] = React.useState(''); + const [password, setPassword] = React.useState(''); React.useEffect(() => { const fetchRecentUrlImages = async () => { const recentUrlImages: URL[] = await getRecentUrlImages(); @@ -162,11 +168,12 @@ const URLSelector = ({ imageURL, ]); setRecentUrlImages(normalizedRecentUrls); - await done(imageURL); + const auth = username ? { username, password } : undefined; + await done(imageURL, auth); }} > - + Use Image URL @@ -178,6 +185,49 @@ const URLSelector = ({ setImageURL(evt.target.value) } /> + { + if (showBasicAuth) { + setUsername(''); + setPassword(''); + } + setShowBasicAuth(!showBasicAuth); + }} + > + + {showBasicAuth && ( + + )} + {!showBasicAuth && ( + + )} + Authentication + + + {showBasicAuth && ( + + ) => + setUsername(evt.target.value) + } + /> + ) => + setPassword(evt.target.value) + } + /> + + )} {recentImages.length > 0 && ( @@ -283,6 +333,11 @@ interface SourceSelectorState { imageLoading: boolean; } +interface Authentication { + username: string; + password: string; +} + export class SourceSelector extends React.Component< SourceSelectorProps, SourceSelectorState @@ -328,7 +383,11 @@ export class SourceSelector extends React.Component< this.setState({ imageLoading: false }); } - private async createSource(selected: string, SourceType: Source) { + private async createSource( + selected: string, + SourceType: Source, + auth?: Authentication, + ) { try { selected = await replaceWindowsNetworkDriveLetter(selected); } catch (error) { @@ -351,7 +410,7 @@ export class SourceSelector extends React.Component< }); } - return new sourceDestination.Http({ url: selected }); + return new sourceDestination.Http({ url: selected, auth }); } public isJson(jsonString: string) { @@ -374,6 +433,7 @@ export class SourceSelector extends React.Component< private selectSource( selected: string | DrivelistDrive, SourceType: Source, + auth?: Authentication, ): { promise: Promise; cancel: () => void } { let cancelled = false; return { @@ -403,7 +463,7 @@ export class SourceSelector extends React.Component< }, }); } - source = await this.createSource(selected, SourceType); + source = await this.createSource(selected, SourceType, auth); if (cancelled) { return; @@ -750,7 +810,7 @@ export class SourceSelector extends React.Component< showURLSelector: false, }); }} - done={async (imageURL: string) => { + done={async (imageURL: string, auth?: Authentication) => { // Avoid analytics and selection state changes // if no file was resolved from the dialog. if (!imageURL) { @@ -760,6 +820,7 @@ export class SourceSelector extends React.Component< ({ promise, cancel: cancelURLSelection } = this.selectSource( imageURL, sourceDestination.Http, + auth, )); await promise; }