Use new API in DirectDownload, improve redirection
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
DirectDownload now uses the new Fling API, including one-time tokens for download. When accessing a fling, while loading the fling, instead of showing the ArtifactList an empty page is shown. This way the redirection does not leak through so visibly when using direct download and the loading of the fling takes a bit longer.
This commit is contained in:
parent
0824d8367f
commit
e9ad499850
8 changed files with 119 additions and 59 deletions
|
@ -16,13 +16,30 @@ Content-Type: application/json
|
||||||
{"adminName": "admin", "adminPassword":"123"}
|
{"adminName": "admin", "adminPassword":"123"}
|
||||||
-> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position)))
|
-> run-hook (restclient-set-var ":token" (buffer-substring-no-properties 1 (line-end-position)))
|
||||||
|
|
||||||
:token = Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTUxNzY4OTksImV4cCI6MTU5NTM1Njg5OSwic3ViIjoiYWRtaW4ifQ.uRh_xBCrBiLQEBah9I8bYWM-Zph-V_pzQVdaGSU5Mlc
|
:token = Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1OTU2NzUxMjAsImV4cCI6MTU5NTg1NTEyMCwic3ViIjoiYWRtaW4ifQ.WzrGTTZTYHYOw8SskHQ2_sob2tzLIF6q8y8_2oyuafs
|
||||||
|
|
||||||
# Get all flings
|
# Get all flings
|
||||||
GET http://localhost:8080/api/fling
|
GET http://localhost:8080/api/fling
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
:token
|
:token
|
||||||
|
|
||||||
|
:flingid = 9f7353a3-efaa-41af-9f93-61e02dc5e440
|
||||||
|
|
||||||
|
# Put a fling
|
||||||
|
PUT http://localhost:8080/api/fling/:flingid
|
||||||
|
Content-Type: application/json
|
||||||
|
:token
|
||||||
|
{
|
||||||
|
"id": "9f7353a3-efaa-41af-9f93-61e02dc5e440",
|
||||||
|
"name": "Shared Fling from querysheetsdfasfd",
|
||||||
|
"creationTime": 1595253659362,
|
||||||
|
"shareId": "WWgTlNZJPZDQ6oowUYfxcQqq",
|
||||||
|
"directDownload": false,
|
||||||
|
"allowUpload": false,
|
||||||
|
"shared": true,
|
||||||
|
"expirationClicks": 12
|
||||||
|
}
|
||||||
|
|
||||||
# Add a new fling
|
# Add a new fling
|
||||||
POST http://localhost:8080/api/fling
|
POST http://localhost:8080/api/fling
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
|
@ -101,11 +101,14 @@ public class FlingController {
|
||||||
public ResponseEntity<Resource> getFlingData(@PathVariable UUID id) throws IOException {
|
public ResponseEntity<Resource> getFlingData(@PathVariable UUID id) throws IOException {
|
||||||
FlingDto flingDto = flingService.getById(id);
|
FlingDto flingDto = flingService.getById(id);
|
||||||
InputStreamResource data = new InputStreamResource(archiveService.getFling(id));
|
InputStreamResource data = new InputStreamResource(archiveService.getFling(id));
|
||||||
|
Long length = data.contentLength();
|
||||||
|
|
||||||
|
data = new InputStreamResource(archiveService.getFling(id));
|
||||||
|
|
||||||
return ResponseEntity.ok()
|
return ResponseEntity.ok()
|
||||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||||
"attachment;filename=\"" + flingDto.getName() + ".zip" + "\"")
|
"attachment;filename=\"" + flingDto.getName() + ".zip" + "\"")
|
||||||
.contentLength(200L) // FIXME
|
.contentLength(length)
|
||||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||||
.body(data);
|
.body(data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ import io.jsonwebtoken.JwtParser;
|
||||||
import io.jsonwebtoken.Jwts;
|
import io.jsonwebtoken.Jwts;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import net.friedl.fling.model.dto.AdminAuthDto;
|
import net.friedl.fling.model.dto.AdminAuthDto;
|
||||||
import net.friedl.fling.model.dto.FlingDto;
|
|
||||||
import net.friedl.fling.model.dto.UserAuthDto;
|
import net.friedl.fling.model.dto.UserAuthDto;
|
||||||
import net.friedl.fling.persistence.entities.FlingEntity;
|
import net.friedl.fling.persistence.entities.FlingEntity;
|
||||||
import net.friedl.fling.persistence.entities.TokenEntity;
|
import net.friedl.fling.persistence.entities.TokenEntity;
|
||||||
|
|
|
@ -275,7 +275,11 @@ public class FlingControllerTest {
|
||||||
byte[] testZip = new byte[testZipInt.length];
|
byte[] testZip = new byte[testZipInt.length];
|
||||||
for (int idx = 0; idx < testZip.length; idx++) testZip[idx] = (byte) testZipInt[idx];
|
for (int idx = 0; idx < testZip.length; idx++) testZip[idx] = (byte) testZipInt[idx];
|
||||||
|
|
||||||
when(archiveService.getFling(any())).thenReturn(new ByteArrayInputStream(testZip));
|
when(archiveService.getFling(any()))
|
||||||
|
.thenAnswer((invocation) -> {
|
||||||
|
// need to use thenAnswer here to always return a fresh new (unclosed) input stream
|
||||||
|
return new ByteArrayInputStream(testZip);
|
||||||
|
});
|
||||||
|
|
||||||
mockMvc.perform(get("/api/fling/{id}/data", flingId))
|
mockMvc.perform(get("/api/fling/{id}/data", flingId))
|
||||||
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
|
.andExpect(content().contentType(MediaType.APPLICATION_OCTET_STREAM))
|
||||||
|
|
|
@ -26,7 +26,7 @@ function FlingArtifactControl(props) {
|
||||||
log.trace(`Generated download url: ${url}`);
|
log.trace(`Generated download url: ${url}`);
|
||||||
frame.src = url;
|
frame.src = url;
|
||||||
iframeContainer.current.appendChild(frame);
|
iframeContainer.current.appendChild(frame);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,27 +1,38 @@
|
||||||
|
import log from 'loglevel';
|
||||||
import React, { useRef, useState, useEffect } from 'react';
|
import React, { useRef, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import {flingClient} from '../../util/flingclient';
|
import { AuthClient } from '../../util/fc';
|
||||||
|
|
||||||
export default function FlingUser(props) {
|
export default function FlingUser(props) {
|
||||||
let iframeContainer = useRef(null);
|
let iframeContainer = useRef(null);
|
||||||
let [packaging, setPackaging] = useState(true);
|
let [packaging, setPackaging] = useState(true);
|
||||||
|
let [done, setDone] = useState(false);
|
||||||
let [waitingMessage, setWaitingMessage] = useState("");
|
let [waitingMessage, setWaitingMessage] = useState("");
|
||||||
let [downloadUrl, setDownloadUrl] = useState("");
|
let [downloadUrl, setDownloadUrl] = useState("");
|
||||||
|
|
||||||
useEffect(handleDownload, []);
|
useEffect(handleDownload, []);
|
||||||
|
|
||||||
function handleDownload() {
|
function handleDownload() {
|
||||||
flingClient.packageFling(props.fling.id)
|
let authClient = new AuthClient();
|
||||||
.then(downloadUrl => {
|
authClient.deriveToken({ singleUse: true })
|
||||||
|
.then(token => {
|
||||||
|
let url = `${process.env.REACT_APP_API.replace(/\/+$/, '')}/api/fling/${props.fling.id}/data?derivedToken=${token}`;
|
||||||
|
log.trace(`Generated download url for link: ${url}`);
|
||||||
|
setDownloadUrl(url);
|
||||||
|
})
|
||||||
|
.then(
|
||||||
|
authClient.deriveToken({ singleUse: true })
|
||||||
|
.then(token => {
|
||||||
setPackaging(false);
|
setPackaging(false);
|
||||||
// We need this iframe hack because with a regular href, while
|
// We need this iframe hack because with a regular href, while
|
||||||
// the browser downloads the file fine, it also reloads the page, hence
|
// the browser downloads the file fine, it also reloads the page, hence
|
||||||
// loosing all logs and state
|
// loosing all logs and state
|
||||||
let frame = document.createElement("iframe");
|
let frame = document.createElement("iframe");
|
||||||
frame.src = downloadUrl;
|
let url = `${process.env.REACT_APP_API.replace(/\/+$/, '')}/api/fling/${props.fling.id}/data?derivedToken=${token}`;
|
||||||
|
log.trace(`Generated download url: ${url}`);
|
||||||
|
frame.src = url;
|
||||||
iframeContainer.current.appendChild(frame);
|
iframeContainer.current.appendChild(frame);
|
||||||
setDownloadUrl(downloadUrl);
|
}));
|
||||||
});
|
|
||||||
|
|
||||||
let randMsg = ["Please stay patient...",
|
let randMsg = ["Please stay patient...",
|
||||||
"Your download will be ready soon...",
|
"Your download will be ready soon...",
|
||||||
|
@ -30,6 +41,15 @@ export default function FlingUser(props) {
|
||||||
setInterval(() => setWaitingMessage(randMsg[Math.floor(Math.random() * randMsg.length)]), 10000);
|
setInterval(() => setWaitingMessage(randMsg[Math.floor(Math.random() * randMsg.length)]), 10000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function invalidateLink(ev) {
|
||||||
|
setDone(true);
|
||||||
|
window.location.href = downloadUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
function reloadPage(ev) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="container-center">
|
<div className="container-center">
|
||||||
|
@ -39,10 +59,18 @@ export default function FlingUser(props) {
|
||||||
? <><div className="loading loading-lg" />
|
? <><div className="loading loading-lg" />
|
||||||
{waitingMessage ? waitingMessage : "Packaging up your files..."}
|
{waitingMessage ? waitingMessage : "Packaging up your files..."}
|
||||||
</>
|
</>
|
||||||
: <>
|
: !done
|
||||||
|
? <>
|
||||||
<h5>Your download is <span className="text-primary">ready!</span></h5>
|
<h5>Your download is <span className="text-primary">ready!</span></h5>
|
||||||
<i className="icon icon-check icon-2x text-primary" /><br />
|
<i className="icon icon-check icon-2x text-primary" /><br />
|
||||||
<span className="text-dark">Download doesn't start? <br/><a href={downloadUrl}>Click here</a></span>
|
<span className="text-dark">Download doesn't start? <br />
|
||||||
|
<button className="btn btn-link" onClick={invalidateLink}>Click here</button></span>
|
||||||
|
</>
|
||||||
|
: <>
|
||||||
|
<h5>Thanks for <span className="text-primary">downloading!</span></h5>
|
||||||
|
<i className="icon icon-check icon-2x text-primary" /><br />
|
||||||
|
<span className="text-dark">Want to download again? <br />
|
||||||
|
<button className="btn btn-link" onClick={reloadPage}>Reload page</button></span>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -10,18 +10,27 @@ import FlingUserList from './FlingUserList';
|
||||||
export default function FlingUser() {
|
export default function FlingUser() {
|
||||||
let { shareId } = useParams();
|
let { shareId } = useParams();
|
||||||
let [fling, setFling] = useState({});
|
let [fling, setFling] = useState({});
|
||||||
|
let [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let flingClient = new FlingClient();
|
let flingClient = new FlingClient();
|
||||||
flingClient.getFlingByShareId(shareId)
|
flingClient.getFlingByShareId(shareId)
|
||||||
.then(f => setFling(f));
|
.then(f => {
|
||||||
|
setFling(f);
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
}, [shareId]);
|
}, [shareId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
{fling.sharing && fling.sharing.directDownload
|
{loading
|
||||||
|
? <div></div>
|
||||||
|
: <div>
|
||||||
|
{fling.shared && fling.directDownload
|
||||||
? <DirectDownload fling={fling} />
|
? <DirectDownload fling={fling} />
|
||||||
: <FlingUserList fling={fling} />}
|
: <FlingUserList fling={fling} />}
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default function Unlock() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let authClient = new AuthClient();
|
let authClient = new AuthClient();
|
||||||
let userAuth = new fc.UserAuth(location.state.shareId, "")
|
let userAuth = new fc.UserAuth(location.state.shareId, "");
|
||||||
|
|
||||||
authClient.authenticateUser({ 'userAuth': userAuth })
|
authClient.authenticateUser({ 'userAuth': userAuth })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
@ -20,7 +20,7 @@ export default function Unlock() {
|
||||||
sessionStorage.setItem('token', response);
|
sessionStorage.setItem('token', response);
|
||||||
history.replace(location.state.from);
|
history.replace(location.state.from);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
log.info("Fling protected. Could not unlock without code.")
|
log.info("Fling protected. Could not unlock without code.");
|
||||||
});
|
});
|
||||||
}, [location, history]);
|
}, [location, history]);
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ export default function Unlock() {
|
||||||
function handleSubmit(ev) {
|
function handleSubmit(ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
let authClient = new AuthClient();
|
let authClient = new AuthClient();
|
||||||
let userAuth = new fc.UserAuth(shareId, authCode)
|
let userAuth = new fc.UserAuth(shareId, authCode);
|
||||||
|
|
||||||
authClient.authenticateUser({ 'userAuth': userAuth })
|
authClient.authenticateUser({ 'userAuth': userAuth })
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
Loading…
Reference in a new issue